1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "complexwidgets_p.h" |
5 | |
6 | #include <qaccessible.h> |
7 | #include <qapplication.h> |
8 | #include <qevent.h> |
9 | #if QT_CONFIG(itemviews) |
10 | #include <qheaderview.h> |
11 | #endif |
12 | #if QT_CONFIG(tabbar) |
13 | #include <qtabbar.h> |
14 | #include <private/qtabbar_p.h> |
15 | #endif |
16 | #if QT_CONFIG(combobox) |
17 | #include <qcombobox.h> |
18 | #endif |
19 | #if QT_CONFIG(lineedit) |
20 | #include <qlineedit.h> |
21 | #endif |
22 | #include <qstyle.h> |
23 | #include <qstyleoption.h> |
24 | #if QT_CONFIG(tooltip) |
25 | #include <qtooltip.h> |
26 | #endif |
27 | #if QT_CONFIG(whatsthis) |
28 | #include <qwhatsthis.h> |
29 | #endif |
30 | #include <QAbstractScrollArea> |
31 | #if QT_CONFIG(scrollarea) |
32 | #include <QScrollArea> |
33 | #endif |
34 | #if QT_CONFIG(scrollbar) |
35 | #include <QScrollBar> |
36 | #endif |
37 | #include <QDebug> |
38 | |
39 | #if QT_CONFIG(accessibility) |
40 | |
41 | QT_BEGIN_NAMESPACE |
42 | |
43 | using namespace Qt::StringLiterals; |
44 | |
45 | QString qt_accStripAmp(const QString &text); |
46 | QString qt_accHotKey(const QString &text); |
47 | |
48 | #if QT_CONFIG(tabbar) |
49 | /*! |
50 | \class QAccessibleTabBar |
51 | \brief The QAccessibleTabBar class implements the QAccessibleInterface for tab bars. |
52 | \internal |
53 | |
54 | \ingroup accessibility |
55 | */ |
56 | |
57 | class QAccessibleTabButton: public QAccessibleInterface, public QAccessibleActionInterface |
58 | { |
59 | public: |
60 | QAccessibleTabButton(QTabBar *parent, int index) |
61 | : m_parent(parent), m_index(index) |
62 | {} |
63 | |
64 | void *interface_cast(QAccessible::InterfaceType t) override { |
65 | if (t == QAccessible::ActionInterface) { |
66 | return static_cast<QAccessibleActionInterface*>(this); |
67 | } |
68 | return nullptr; |
69 | } |
70 | |
71 | QObject *object() const override { return nullptr; } |
72 | QAccessible::Role role() const override { return QAccessible::PageTab; } |
73 | QAccessible::State state() const override { |
74 | if (!isValid()) { |
75 | QAccessible::State s; |
76 | s.invalid = true; |
77 | return s; |
78 | } |
79 | |
80 | QAccessible::State s = parent()->state(); |
81 | s.selectable = true; |
82 | s.focused = (m_index == m_parent->currentIndex()); |
83 | s.selected = s.focused; |
84 | return s; |
85 | } |
86 | QRect rect() const override { |
87 | if (!isValid()) |
88 | return QRect(); |
89 | |
90 | QPoint tp = m_parent->mapToGlobal(QPoint(0,0)); |
91 | QRect rec = m_parent->tabRect(index: m_index); |
92 | rec = QRect(tp.x() + rec.x(), tp.y() + rec.y(), rec.width(), rec.height()); |
93 | return rec; |
94 | } |
95 | |
96 | bool isValid() const override { |
97 | if (m_parent) { |
98 | if (static_cast<QWidget *>(m_parent.data())->d_func()->data.in_destructor) |
99 | return false; |
100 | return m_parent->count() > m_index; |
101 | } |
102 | return false; |
103 | } |
104 | |
105 | QAccessibleInterface *childAt(int, int) const override { return nullptr; } |
106 | int childCount() const override { return 0; } |
107 | int indexOfChild(const QAccessibleInterface *) const override { return -1; } |
108 | |
109 | QString text(QAccessible::Text t) const override |
110 | { |
111 | if (!isValid()) |
112 | return QString(); |
113 | QString str; |
114 | switch (t) { |
115 | case QAccessible::Name: |
116 | str = m_parent->accessibleTabName(index: m_index); |
117 | if (str.isEmpty()) |
118 | str = qt_accStripAmp(text: m_parent->tabText(index: m_index)); |
119 | break; |
120 | case QAccessible::Accelerator: |
121 | str = qt_accHotKey(text: m_parent->tabText(index: m_index)); |
122 | break; |
123 | #if QT_CONFIG(tooltip) |
124 | case QAccessible::Description: |
125 | str = m_parent->tabToolTip(index: m_index); |
126 | break; |
127 | #endif |
128 | #if QT_CONFIG(whatsthis) |
129 | case QAccessible::Help: |
130 | str = m_parent->tabWhatsThis(index: m_index); |
131 | break; |
132 | #endif |
133 | default: |
134 | break; |
135 | } |
136 | return str; |
137 | } |
138 | |
139 | void setText(QAccessible::Text, const QString &) override {} |
140 | |
141 | QAccessibleInterface *parent() const override { |
142 | return QAccessible::queryAccessibleInterface(m_parent.data()); |
143 | } |
144 | QAccessibleInterface *child(int) const override { return nullptr; } |
145 | |
146 | // action interface |
147 | QStringList actionNames() const override |
148 | { |
149 | return QStringList(pressAction()); |
150 | } |
151 | |
152 | void doAction(const QString &actionName) override |
153 | { |
154 | if (isValid() && actionName == pressAction()) |
155 | m_parent->setCurrentIndex(m_index); |
156 | } |
157 | |
158 | QStringList keyBindingsForAction(const QString &) const override |
159 | { |
160 | return QStringList(); |
161 | } |
162 | |
163 | int index() const { return m_index; } |
164 | |
165 | private: |
166 | QPointer<QTabBar> m_parent; |
167 | int m_index; |
168 | |
169 | }; |
170 | |
171 | /*! |
172 | Constructs a QAccessibleTabBar object for \a w. |
173 | */ |
174 | QAccessibleTabBar::QAccessibleTabBar(QWidget *w) |
175 | : QAccessibleWidget(w, QAccessible::PageTabList) |
176 | { |
177 | Q_ASSERT(tabBar()); |
178 | } |
179 | |
180 | QAccessibleTabBar::~QAccessibleTabBar() |
181 | { |
182 | for (QAccessible::Id id : std::as_const(t&: m_childInterfaces)) |
183 | QAccessible::deleteAccessibleInterface(uniqueId: id); |
184 | } |
185 | |
186 | void *QAccessibleTabBar::interface_cast(QAccessible::InterfaceType t) |
187 | { |
188 | if (t == QAccessible::SelectionInterface) { |
189 | return static_cast<QAccessibleSelectionInterface*>(this); |
190 | } |
191 | return QAccessibleWidget::interface_cast(t); |
192 | } |
193 | |
194 | /*! Returns the QTabBar. */ |
195 | QTabBar *QAccessibleTabBar::tabBar() const |
196 | { |
197 | return qobject_cast<QTabBar*>(object: object()); |
198 | } |
199 | |
200 | QAccessibleInterface* QAccessibleTabBar::focusChild() const |
201 | { |
202 | for (int i = 0; i < childCount(); ++i) { |
203 | if (child(index: i)->state().focused) |
204 | return child(index: i); |
205 | } |
206 | |
207 | return nullptr; |
208 | } |
209 | |
210 | QAccessibleInterface* QAccessibleTabBar::child(int index) const |
211 | { |
212 | if (QAccessible::Id id = m_childInterfaces.value(key: index)) |
213 | return QAccessible::accessibleInterface(uniqueId: id); |
214 | |
215 | // first the tabs, then 2 buttons |
216 | if (index < tabBar()->count()) { |
217 | QAccessibleTabButton *button = new QAccessibleTabButton(tabBar(), index); |
218 | QAccessible::registerAccessibleInterface(iface: button); |
219 | m_childInterfaces.insert(key: index, value: QAccessible::uniqueId(iface: button)); |
220 | return button; |
221 | } else if (index >= tabBar()->count()) { |
222 | // left button |
223 | if (index - tabBar()->count() == 0) { |
224 | return QAccessible::queryAccessibleInterface(tabBar()->d_func()->leftB); |
225 | } |
226 | // right button |
227 | if (index - tabBar()->count() == 1) { |
228 | return QAccessible::queryAccessibleInterface(tabBar()->d_func()->rightB); |
229 | } |
230 | } |
231 | return nullptr; |
232 | } |
233 | |
234 | int QAccessibleTabBar::indexOfChild(const QAccessibleInterface *child) const |
235 | { |
236 | if (child->object() && child->object() == tabBar()->d_func()->leftB) |
237 | return tabBar()->count(); |
238 | if (child->object() && child->object() == tabBar()->d_func()->rightB) |
239 | return tabBar()->count() + 1; |
240 | if (child->role() == QAccessible::PageTab) { |
241 | QAccessibleInterface *parent = child->parent(); |
242 | if (parent == this) { |
243 | const QAccessibleTabButton *tabButton = static_cast<const QAccessibleTabButton *>(child); |
244 | return tabButton->index(); |
245 | } |
246 | } |
247 | return -1; |
248 | } |
249 | |
250 | int QAccessibleTabBar::childCount() const |
251 | { |
252 | // tabs + scroll buttons |
253 | return tabBar()->count() + 2; |
254 | } |
255 | |
256 | QString QAccessibleTabBar::text(QAccessible::Text t) const |
257 | { |
258 | if (t == QAccessible::Name) { |
259 | const QTabBar *tBar = tabBar(); |
260 | int idx = tBar->currentIndex(); |
261 | QString str = tBar->accessibleTabName(index: idx); |
262 | if (str.isEmpty()) |
263 | str = qt_accStripAmp(text: tBar->tabText(index: idx)); |
264 | return str; |
265 | } else if (t == QAccessible::Accelerator) { |
266 | return qt_accHotKey(text: tabBar()->tabText(index: tabBar()->currentIndex())); |
267 | } |
268 | return QString(); |
269 | } |
270 | |
271 | int QAccessibleTabBar::selectedItemCount() const |
272 | { |
273 | if (tabBar()->currentIndex() >= 0) |
274 | return 1; |
275 | return 0; |
276 | } |
277 | |
278 | QList<QAccessibleInterface*> QAccessibleTabBar::selectedItems() const |
279 | { |
280 | QList<QAccessibleInterface*> items; |
281 | QAccessibleInterface *selected = selectedItem(selectionIndex: 0); |
282 | if (selected) |
283 | items.push_back(t: selected); |
284 | return items; |
285 | } |
286 | |
287 | QAccessibleInterface* QAccessibleTabBar::selectedItem(int selectionIndex) const |
288 | { |
289 | const int currentIndex = tabBar()->currentIndex(); |
290 | if (selectionIndex != 0 || currentIndex < 0) |
291 | return nullptr; |
292 | return child(index: currentIndex); |
293 | } |
294 | |
295 | bool QAccessibleTabBar::isSelected(QAccessibleInterface *childItem) const |
296 | { |
297 | return childItem && selectedItem(selectionIndex: 0) == childItem; |
298 | } |
299 | |
300 | bool QAccessibleTabBar::select(QAccessibleInterface *childItem) |
301 | { |
302 | const int childIndex = indexOfChild(child: childItem); |
303 | if (childIndex >= 0) { |
304 | tabBar()->setCurrentIndex(childIndex); |
305 | return true; |
306 | } |
307 | return false; |
308 | } |
309 | |
310 | bool QAccessibleTabBar::unselect(QAccessibleInterface *) |
311 | { |
312 | return false; |
313 | } |
314 | |
315 | bool QAccessibleTabBar::selectAll() |
316 | { |
317 | return false; |
318 | } |
319 | |
320 | bool QAccessibleTabBar::clear() |
321 | { |
322 | return false; |
323 | } |
324 | |
325 | #endif // QT_CONFIG(tabbar) |
326 | |
327 | #if QT_CONFIG(combobox) |
328 | /*! |
329 | \class QAccessibleComboBox |
330 | \brief The QAccessibleComboBox class implements the QAccessibleInterface for editable and read-only combo boxes. |
331 | \internal |
332 | |
333 | \ingroup accessibility |
334 | */ |
335 | |
336 | /*! |
337 | Constructs a QAccessibleComboBox object for \a w. |
338 | */ |
339 | QAccessibleComboBox::QAccessibleComboBox(QWidget *w) |
340 | : QAccessibleWidget(w, QAccessible::ComboBox) |
341 | { |
342 | Q_ASSERT(comboBox()); |
343 | } |
344 | |
345 | /*! |
346 | Returns the combobox. |
347 | */ |
348 | QComboBox *QAccessibleComboBox::comboBox() const |
349 | { |
350 | return qobject_cast<QComboBox *>(object: object()); |
351 | } |
352 | |
353 | QAccessibleInterface *QAccessibleComboBox::child(int index) const |
354 | { |
355 | if (QComboBox *cBox = comboBox()) { |
356 | if (index == 0) { |
357 | QAbstractItemView *view = cBox->view(); |
358 | //QWidget *parent = view ? view->parentWidget() : 0; |
359 | return QAccessible::queryAccessibleInterface(view); |
360 | } else if (index == 1 && cBox->isEditable()) { |
361 | return QAccessible::queryAccessibleInterface(cBox->lineEdit()); |
362 | } |
363 | } |
364 | return nullptr; |
365 | } |
366 | |
367 | int QAccessibleComboBox::childCount() const |
368 | { |
369 | // list and text edit |
370 | if (QComboBox *cBox = comboBox()) |
371 | return (cBox->isEditable()) ? 2 : 1; |
372 | return 0; |
373 | } |
374 | |
375 | QAccessibleInterface *QAccessibleComboBox::childAt(int x, int y) const |
376 | { |
377 | if (QComboBox *cBox = comboBox()) { |
378 | if (cBox->isEditable() && cBox->lineEdit()->rect().contains(ax: x, ay: y)) |
379 | return child(index: 1); |
380 | } |
381 | return nullptr; |
382 | } |
383 | |
384 | int QAccessibleComboBox::indexOfChild(const QAccessibleInterface *child) const |
385 | { |
386 | if (QComboBox *cBox = comboBox()) { |
387 | if (cBox->view() == child->object()) |
388 | return 0; |
389 | if (cBox->isEditable() && cBox->lineEdit() == child->object()) |
390 | return 1; |
391 | } |
392 | return -1; |
393 | } |
394 | |
395 | QAccessibleInterface *QAccessibleComboBox::focusChild() const |
396 | { |
397 | // The editable combobox is the focus proxy of its lineedit, so the |
398 | // lineedit itself never gets focus. But it is the accessible focus |
399 | // child of an editable combobox. |
400 | if (QComboBox *cBox = comboBox()) { |
401 | if (cBox->isEditable()) |
402 | return child(index: 1); |
403 | } |
404 | return nullptr; |
405 | } |
406 | |
407 | /*! \reimp */ |
408 | QString QAccessibleComboBox::text(QAccessible::Text t) const |
409 | { |
410 | QString str; |
411 | if (QComboBox *cBox = comboBox()) { |
412 | switch (t) { |
413 | case QAccessible::Name: |
414 | #ifndef Q_OS_UNIX // on Linux we use relations for this, name is text (fall through to Value) |
415 | str = QAccessibleWidget::text(t); |
416 | break; |
417 | #endif |
418 | case QAccessible::Value: |
419 | if (cBox->isEditable()) |
420 | str = cBox->lineEdit()->text(); |
421 | else |
422 | str = cBox->currentText(); |
423 | break; |
424 | #ifndef QT_NO_SHORTCUT |
425 | case QAccessible::Accelerator: |
426 | str = QKeySequence(Qt::Key_Down).toString(format: QKeySequence::NativeText); |
427 | break; |
428 | #endif |
429 | default: |
430 | break; |
431 | } |
432 | if (str.isEmpty()) |
433 | str = QAccessibleWidget::text(t); |
434 | } |
435 | return str; |
436 | } |
437 | |
438 | QAccessible::State QAccessibleComboBox::state() const |
439 | { |
440 | QAccessible::State s = QAccessibleWidget::state(); |
441 | |
442 | if (QComboBox *cBox = comboBox()) { |
443 | s.expandable = true; |
444 | s.expanded = isValid() && cBox->view()->isVisible(); |
445 | s.editable = cBox->isEditable(); |
446 | } |
447 | return s; |
448 | } |
449 | |
450 | QStringList QAccessibleComboBox::actionNames() const |
451 | { |
452 | return QStringList() << showMenuAction() << pressAction(); |
453 | } |
454 | |
455 | QString QAccessibleComboBox::localizedActionDescription(const QString &actionName) const |
456 | { |
457 | if (actionName == showMenuAction() || actionName == pressAction()) |
458 | return QComboBox::tr(s: "Open the combo box selection popup" ); |
459 | return QString(); |
460 | } |
461 | |
462 | void QAccessibleComboBox::doAction(const QString &actionName) |
463 | { |
464 | if (QComboBox *cBox = comboBox()) { |
465 | if (actionName == showMenuAction() || actionName == pressAction()) { |
466 | if (cBox->view()->isVisible()) { |
467 | #if defined(Q_OS_ANDROID) |
468 | const auto list = child(0)->tableInterface(); |
469 | if (list && list->selectedRowCount() > 0) { |
470 | cBox->setCurrentIndex(list->selectedRows().at(0)); |
471 | } |
472 | cBox->setFocus(); |
473 | #endif |
474 | cBox->hidePopup(); |
475 | } else { |
476 | cBox->showPopup(); |
477 | #if defined(Q_OS_ANDROID) |
478 | const auto list = child(0)->tableInterface(); |
479 | if (list && list->selectedRowCount() > 0) { |
480 | auto selectedCells = list->selectedCells(); |
481 | QAccessibleEvent ev(selectedCells.at(0),QAccessible::Focus); |
482 | QAccessible::updateAccessibility(&ev); |
483 | } |
484 | #endif |
485 | } |
486 | } |
487 | } |
488 | } |
489 | |
490 | QStringList QAccessibleComboBox::keyBindingsForAction(const QString &/*actionName*/) const |
491 | { |
492 | return QStringList(); |
493 | } |
494 | |
495 | #endif // QT_CONFIG(combobox) |
496 | |
497 | #if QT_CONFIG(scrollarea) |
498 | // ======================= QAccessibleAbstractScrollArea ======================= |
499 | QAccessibleAbstractScrollArea::QAccessibleAbstractScrollArea(QWidget *widget) |
500 | : QAccessibleWidget(widget, QAccessible::Client) |
501 | { |
502 | Q_ASSERT(qobject_cast<QAbstractScrollArea *>(widget)); |
503 | } |
504 | |
505 | QAccessibleInterface *QAccessibleAbstractScrollArea::child(int index) const |
506 | { |
507 | return QAccessible::queryAccessibleInterface(accessibleChildren().at(i: index)); |
508 | } |
509 | |
510 | int QAccessibleAbstractScrollArea::childCount() const |
511 | { |
512 | return accessibleChildren().size(); |
513 | } |
514 | |
515 | int QAccessibleAbstractScrollArea::indexOfChild(const QAccessibleInterface *child) const |
516 | { |
517 | if (!child || !child->object()) |
518 | return -1; |
519 | return accessibleChildren().indexOf(t: qobject_cast<QWidget *>(o: child->object())); |
520 | } |
521 | |
522 | bool QAccessibleAbstractScrollArea::isValid() const |
523 | { |
524 | return (QAccessibleWidget::isValid() && abstractScrollArea() && abstractScrollArea()->viewport()); |
525 | } |
526 | |
527 | QAccessibleInterface *QAccessibleAbstractScrollArea::childAt(int x, int y) const |
528 | { |
529 | if (!abstractScrollArea()->isVisible()) |
530 | return nullptr; |
531 | |
532 | for (int i = 0; i < childCount(); ++i) { |
533 | QPoint wpos = accessibleChildren().at(i)->mapToGlobal(QPoint(0, 0)); |
534 | QRect rect = QRect(wpos, accessibleChildren().at(i)->size()); |
535 | if (rect.contains(ax: x, ay: y)) |
536 | return child(index: i); |
537 | } |
538 | return nullptr; |
539 | } |
540 | |
541 | QAbstractScrollArea *QAccessibleAbstractScrollArea::abstractScrollArea() const |
542 | { |
543 | return static_cast<QAbstractScrollArea *>(object()); |
544 | } |
545 | |
546 | QWidgetList QAccessibleAbstractScrollArea::accessibleChildren() const |
547 | { |
548 | QWidgetList children; |
549 | |
550 | // Viewport. |
551 | QWidget * viewport = abstractScrollArea()->viewport(); |
552 | if (viewport) |
553 | children.append(t: viewport); |
554 | |
555 | // Horizontal scrollBar container. |
556 | QScrollBar *horizontalScrollBar = abstractScrollArea()->horizontalScrollBar(); |
557 | if (horizontalScrollBar && horizontalScrollBar->isVisible()) { |
558 | QWidget *scrollBarParent = horizontalScrollBar->parentWidget(); |
559 | // Add container only if scroll bar is in the container |
560 | if (elementType(widget: scrollBarParent) == HorizontalContainer) |
561 | children.append(t: scrollBarParent); |
562 | } |
563 | |
564 | // Vertical scrollBar container. |
565 | QScrollBar *verticalScrollBar = abstractScrollArea()->verticalScrollBar(); |
566 | if (verticalScrollBar && verticalScrollBar->isVisible()) { |
567 | QWidget *scrollBarParent = verticalScrollBar->parentWidget(); |
568 | // Add container only if scroll bar is in the container |
569 | if (elementType(widget: scrollBarParent) == VerticalContainer) |
570 | children.append(t: scrollBarParent); |
571 | } |
572 | |
573 | // CornerWidget. |
574 | QWidget *cornerWidget = abstractScrollArea()->cornerWidget(); |
575 | if (cornerWidget && cornerWidget->isVisible()) |
576 | children.append(t: cornerWidget); |
577 | |
578 | return children; |
579 | } |
580 | |
581 | QAccessibleAbstractScrollArea::AbstractScrollAreaElement |
582 | QAccessibleAbstractScrollArea::elementType(QWidget *widget) const |
583 | { |
584 | if (!widget) |
585 | return Undefined; |
586 | |
587 | if (widget == abstractScrollArea()) |
588 | return Self; |
589 | if (widget == abstractScrollArea()->viewport()) |
590 | return Viewport; |
591 | if (widget->objectName() == "qt_scrollarea_hcontainer"_L1 ) |
592 | return HorizontalContainer; |
593 | if (widget->objectName() == "qt_scrollarea_vcontainer"_L1 ) |
594 | return VerticalContainer; |
595 | if (widget == abstractScrollArea()->cornerWidget()) |
596 | return CornerWidget; |
597 | |
598 | return Undefined; |
599 | } |
600 | |
601 | bool QAccessibleAbstractScrollArea::isLeftToRight() const |
602 | { |
603 | return abstractScrollArea()->isLeftToRight(); |
604 | } |
605 | |
606 | // ======================= QAccessibleScrollArea =========================== |
607 | QAccessibleScrollArea::QAccessibleScrollArea(QWidget *widget) |
608 | : QAccessibleAbstractScrollArea(widget) |
609 | { |
610 | Q_ASSERT(qobject_cast<QScrollArea *>(widget)); |
611 | } |
612 | #endif // QT_CONFIG(scrollarea) |
613 | |
614 | QT_END_NAMESPACE |
615 | |
616 | #endif // QT_CONFIG(accessibility) |
617 | |