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.focused = (m_index == m_parent->currentIndex()); |
82 | return s; |
83 | } |
84 | QRect rect() const override { |
85 | if (!isValid()) |
86 | return QRect(); |
87 | |
88 | QPoint tp = m_parent->mapToGlobal(QPoint(0,0)); |
89 | QRect rec = m_parent->tabRect(index: m_index); |
90 | rec = QRect(tp.x() + rec.x(), tp.y() + rec.y(), rec.width(), rec.height()); |
91 | return rec; |
92 | } |
93 | |
94 | bool isValid() const override { |
95 | if (m_parent) { |
96 | if (static_cast<QWidget *>(m_parent.data())->d_func()->data.in_destructor) |
97 | return false; |
98 | return m_parent->count() > m_index; |
99 | } |
100 | return false; |
101 | } |
102 | |
103 | QAccessibleInterface *childAt(int, int) const override { return nullptr; } |
104 | int childCount() const override { return 0; } |
105 | int indexOfChild(const QAccessibleInterface *) const override { return -1; } |
106 | |
107 | QString text(QAccessible::Text t) const override |
108 | { |
109 | if (!isValid()) |
110 | return QString(); |
111 | QString str; |
112 | switch (t) { |
113 | case QAccessible::Name: |
114 | str = m_parent->accessibleTabName(index: m_index); |
115 | if (str.isEmpty()) |
116 | str = qt_accStripAmp(text: m_parent->tabText(index: m_index)); |
117 | break; |
118 | case QAccessible::Accelerator: |
119 | str = qt_accHotKey(text: m_parent->tabText(index: m_index)); |
120 | break; |
121 | #if QT_CONFIG(tooltip) |
122 | case QAccessible::Description: |
123 | str = m_parent->tabToolTip(index: m_index); |
124 | break; |
125 | #endif |
126 | #if QT_CONFIG(whatsthis) |
127 | case QAccessible::Help: |
128 | str = m_parent->tabWhatsThis(index: m_index); |
129 | break; |
130 | #endif |
131 | default: |
132 | break; |
133 | } |
134 | return str; |
135 | } |
136 | |
137 | void setText(QAccessible::Text, const QString &) override {} |
138 | |
139 | QAccessibleInterface *parent() const override { |
140 | return QAccessible::queryAccessibleInterface(m_parent.data()); |
141 | } |
142 | QAccessibleInterface *child(int) const override { return nullptr; } |
143 | |
144 | // action interface |
145 | QStringList actionNames() const override |
146 | { |
147 | return QStringList(pressAction()); |
148 | } |
149 | |
150 | void doAction(const QString &actionName) override |
151 | { |
152 | if (isValid() && actionName == pressAction()) |
153 | m_parent->setCurrentIndex(m_index); |
154 | } |
155 | |
156 | QStringList keyBindingsForAction(const QString &) const override |
157 | { |
158 | return QStringList(); |
159 | } |
160 | |
161 | int index() const { return m_index; } |
162 | |
163 | private: |
164 | QPointer<QTabBar> m_parent; |
165 | int m_index; |
166 | |
167 | }; |
168 | |
169 | /*! |
170 | Constructs a QAccessibleTabBar object for \a w. |
171 | */ |
172 | QAccessibleTabBar::QAccessibleTabBar(QWidget *w) |
173 | : QAccessibleWidget(w, QAccessible::PageTabList) |
174 | { |
175 | Q_ASSERT(tabBar()); |
176 | } |
177 | |
178 | QAccessibleTabBar::~QAccessibleTabBar() |
179 | { |
180 | for (QAccessible::Id id : std::as_const(t&: m_childInterfaces)) |
181 | QAccessible::deleteAccessibleInterface(uniqueId: id); |
182 | } |
183 | |
184 | /*! Returns the QTabBar. */ |
185 | QTabBar *QAccessibleTabBar::tabBar() const |
186 | { |
187 | return qobject_cast<QTabBar*>(object: object()); |
188 | } |
189 | |
190 | QAccessibleInterface* QAccessibleTabBar::focusChild() const |
191 | { |
192 | for (int i = 0; i < childCount(); ++i) { |
193 | if (child(index: i)->state().focused) |
194 | return child(index: i); |
195 | } |
196 | |
197 | return nullptr; |
198 | } |
199 | |
200 | QAccessibleInterface* QAccessibleTabBar::child(int index) const |
201 | { |
202 | if (QAccessible::Id id = m_childInterfaces.value(key: index)) |
203 | return QAccessible::accessibleInterface(uniqueId: id); |
204 | |
205 | // first the tabs, then 2 buttons |
206 | if (index < tabBar()->count()) { |
207 | QAccessibleTabButton *button = new QAccessibleTabButton(tabBar(), index); |
208 | QAccessible::registerAccessibleInterface(iface: button); |
209 | m_childInterfaces.insert(key: index, value: QAccessible::uniqueId(iface: button)); |
210 | return button; |
211 | } else if (index >= tabBar()->count()) { |
212 | // left button |
213 | if (index - tabBar()->count() == 0) { |
214 | return QAccessible::queryAccessibleInterface(tabBar()->d_func()->leftB); |
215 | } |
216 | // right button |
217 | if (index - tabBar()->count() == 1) { |
218 | return QAccessible::queryAccessibleInterface(tabBar()->d_func()->rightB); |
219 | } |
220 | } |
221 | return nullptr; |
222 | } |
223 | |
224 | int QAccessibleTabBar::indexOfChild(const QAccessibleInterface *child) const |
225 | { |
226 | if (child->object() && child->object() == tabBar()->d_func()->leftB) |
227 | return tabBar()->count(); |
228 | if (child->object() && child->object() == tabBar()->d_func()->rightB) |
229 | return tabBar()->count() + 1; |
230 | if (child->role() == QAccessible::PageTab) { |
231 | QAccessibleInterface *parent = child->parent(); |
232 | if (parent == this) { |
233 | const QAccessibleTabButton *tabButton = static_cast<const QAccessibleTabButton *>(child); |
234 | return tabButton->index(); |
235 | } |
236 | } |
237 | return -1; |
238 | } |
239 | |
240 | int QAccessibleTabBar::childCount() const |
241 | { |
242 | // tabs + scroll buttons |
243 | return tabBar()->count() + 2; |
244 | } |
245 | |
246 | QString QAccessibleTabBar::text(QAccessible::Text t) const |
247 | { |
248 | if (t == QAccessible::Name) { |
249 | const QTabBar *tBar = tabBar(); |
250 | int idx = tBar->currentIndex(); |
251 | QString str = tBar->accessibleTabName(index: idx); |
252 | if (str.isEmpty()) |
253 | str = qt_accStripAmp(text: tBar->tabText(index: idx)); |
254 | return str; |
255 | } else if (t == QAccessible::Accelerator) { |
256 | return qt_accHotKey(text: tabBar()->tabText(index: tabBar()->currentIndex())); |
257 | } |
258 | return QString(); |
259 | } |
260 | |
261 | #endif // QT_CONFIG(tabbar) |
262 | |
263 | #if QT_CONFIG(combobox) |
264 | /*! |
265 | \class QAccessibleComboBox |
266 | \brief The QAccessibleComboBox class implements the QAccessibleInterface for editable and read-only combo boxes. |
267 | \internal |
268 | |
269 | \ingroup accessibility |
270 | */ |
271 | |
272 | /*! |
273 | Constructs a QAccessibleComboBox object for \a w. |
274 | */ |
275 | QAccessibleComboBox::QAccessibleComboBox(QWidget *w) |
276 | : QAccessibleWidget(w, QAccessible::ComboBox) |
277 | { |
278 | Q_ASSERT(comboBox()); |
279 | } |
280 | |
281 | /*! |
282 | Returns the combobox. |
283 | */ |
284 | QComboBox *QAccessibleComboBox::comboBox() const |
285 | { |
286 | return qobject_cast<QComboBox*>(object: object()); |
287 | } |
288 | |
289 | QAccessibleInterface *QAccessibleComboBox::child(int index) const |
290 | { |
291 | if (index == 0) { |
292 | QAbstractItemView *view = comboBox()->view(); |
293 | //QWidget *parent = view ? view->parentWidget() : 0; |
294 | return QAccessible::queryAccessibleInterface(view); |
295 | } else if (index == 1 && comboBox()->isEditable()) { |
296 | return QAccessible::queryAccessibleInterface(comboBox()->lineEdit()); |
297 | } |
298 | return nullptr; |
299 | } |
300 | |
301 | int QAccessibleComboBox::childCount() const |
302 | { |
303 | // list and text edit |
304 | return comboBox()->isEditable() ? 2 : 1; |
305 | } |
306 | |
307 | QAccessibleInterface *QAccessibleComboBox::childAt(int x, int y) const |
308 | { |
309 | if (comboBox()->isEditable() && comboBox()->lineEdit()->rect().contains(ax: x, ay: y)) |
310 | return child(index: 1); |
311 | return nullptr; |
312 | } |
313 | |
314 | int QAccessibleComboBox::indexOfChild(const QAccessibleInterface *child) const |
315 | { |
316 | if (comboBox()->view() == child->object()) |
317 | return 0; |
318 | if (comboBox()->isEditable() && comboBox()->lineEdit() == child->object()) |
319 | return 1; |
320 | return -1; |
321 | } |
322 | |
323 | QAccessibleInterface *QAccessibleComboBox::focusChild() const |
324 | { |
325 | // The editable combobox is the focus proxy of its lineedit, so the |
326 | // lineedit itself never gets focus. But it is the accessible focus |
327 | // child of an editable combobox. |
328 | if (comboBox()->isEditable()) |
329 | return child(index: 1); |
330 | return nullptr; |
331 | } |
332 | |
333 | /*! \reimp */ |
334 | QString QAccessibleComboBox::text(QAccessible::Text t) const |
335 | { |
336 | QString str; |
337 | |
338 | switch (t) { |
339 | case QAccessible::Name: |
340 | #ifndef Q_OS_UNIX // on Linux we use relations for this, name is text (fall through to Value) |
341 | str = QAccessibleWidget::text(t); |
342 | break; |
343 | #endif |
344 | case QAccessible::Value: |
345 | if (comboBox()->isEditable()) |
346 | str = comboBox()->lineEdit()->text(); |
347 | else |
348 | str = comboBox()->currentText(); |
349 | break; |
350 | #ifndef QT_NO_SHORTCUT |
351 | case QAccessible::Accelerator: |
352 | str = QKeySequence(Qt::Key_Down).toString(format: QKeySequence::NativeText); |
353 | break; |
354 | #endif |
355 | default: |
356 | break; |
357 | } |
358 | if (str.isEmpty()) |
359 | str = QAccessibleWidget::text(t); |
360 | return str; |
361 | } |
362 | |
363 | QAccessible::State QAccessibleComboBox::state() const |
364 | { |
365 | QAccessible::State s = QAccessibleWidget::state(); |
366 | |
367 | s.expandable = true; |
368 | s.expanded = isValid() && comboBox()->view()->isVisible(); |
369 | s.editable = comboBox()->isEditable(); |
370 | |
371 | return s; |
372 | } |
373 | |
374 | QStringList QAccessibleComboBox::actionNames() const |
375 | { |
376 | return QStringList() << showMenuAction() << pressAction(); |
377 | } |
378 | |
379 | QString QAccessibleComboBox::localizedActionDescription(const QString &actionName) const |
380 | { |
381 | if (actionName == showMenuAction() || actionName == pressAction()) |
382 | return QComboBox::tr(s: "Open the combo box selection popup" ); |
383 | return QString(); |
384 | } |
385 | |
386 | void QAccessibleComboBox::doAction(const QString &actionName) |
387 | { |
388 | if (actionName == showMenuAction() || actionName == pressAction()) { |
389 | if (comboBox()->view()->isVisible()) { |
390 | #if defined(Q_OS_ANDROID) |
391 | const auto list = child(0)->tableInterface(); |
392 | if (list && list->selectedRowCount() > 0) { |
393 | comboBox()->setCurrentIndex(list->selectedRows().at(0)); |
394 | } |
395 | comboBox()->setFocus(); |
396 | #endif |
397 | comboBox()->hidePopup(); |
398 | } else { |
399 | comboBox()->showPopup(); |
400 | #if defined(Q_OS_ANDROID) |
401 | const auto list = child(0)->tableInterface(); |
402 | if (list && list->selectedRowCount() > 0) { |
403 | auto selectedCells = list->selectedCells(); |
404 | QAccessibleEvent ev(selectedCells.at(0),QAccessible::Focus); |
405 | QAccessible::updateAccessibility(&ev); |
406 | } |
407 | #endif |
408 | } |
409 | } |
410 | } |
411 | |
412 | QStringList QAccessibleComboBox::keyBindingsForAction(const QString &/*actionName*/) const |
413 | { |
414 | return QStringList(); |
415 | } |
416 | |
417 | #endif // QT_CONFIG(combobox) |
418 | |
419 | #if QT_CONFIG(scrollarea) |
420 | // ======================= QAccessibleAbstractScrollArea ======================= |
421 | QAccessibleAbstractScrollArea::QAccessibleAbstractScrollArea(QWidget *widget) |
422 | : QAccessibleWidget(widget, QAccessible::Client) |
423 | { |
424 | Q_ASSERT(qobject_cast<QAbstractScrollArea *>(widget)); |
425 | } |
426 | |
427 | QAccessibleInterface *QAccessibleAbstractScrollArea::child(int index) const |
428 | { |
429 | return QAccessible::queryAccessibleInterface(accessibleChildren().at(i: index)); |
430 | } |
431 | |
432 | int QAccessibleAbstractScrollArea::childCount() const |
433 | { |
434 | return accessibleChildren().size(); |
435 | } |
436 | |
437 | int QAccessibleAbstractScrollArea::indexOfChild(const QAccessibleInterface *child) const |
438 | { |
439 | if (!child || !child->object()) |
440 | return -1; |
441 | return accessibleChildren().indexOf(t: qobject_cast<QWidget *>(o: child->object())); |
442 | } |
443 | |
444 | bool QAccessibleAbstractScrollArea::isValid() const |
445 | { |
446 | return (QAccessibleWidget::isValid() && abstractScrollArea() && abstractScrollArea()->viewport()); |
447 | } |
448 | |
449 | QAccessibleInterface *QAccessibleAbstractScrollArea::childAt(int x, int y) const |
450 | { |
451 | if (!abstractScrollArea()->isVisible()) |
452 | return nullptr; |
453 | |
454 | for (int i = 0; i < childCount(); ++i) { |
455 | QPoint wpos = accessibleChildren().at(i)->mapToGlobal(QPoint(0, 0)); |
456 | QRect rect = QRect(wpos, accessibleChildren().at(i)->size()); |
457 | if (rect.contains(ax: x, ay: y)) |
458 | return child(index: i); |
459 | } |
460 | return nullptr; |
461 | } |
462 | |
463 | QAbstractScrollArea *QAccessibleAbstractScrollArea::abstractScrollArea() const |
464 | { |
465 | return static_cast<QAbstractScrollArea *>(object()); |
466 | } |
467 | |
468 | QWidgetList QAccessibleAbstractScrollArea::accessibleChildren() const |
469 | { |
470 | QWidgetList children; |
471 | |
472 | // Viewport. |
473 | QWidget * viewport = abstractScrollArea()->viewport(); |
474 | if (viewport) |
475 | children.append(t: viewport); |
476 | |
477 | // Horizontal scrollBar container. |
478 | QScrollBar *horizontalScrollBar = abstractScrollArea()->horizontalScrollBar(); |
479 | if (horizontalScrollBar && horizontalScrollBar->isVisible()) { |
480 | QWidget *scrollBarParent = horizontalScrollBar->parentWidget(); |
481 | // Add container only if scroll bar is in the container |
482 | if (elementType(widget: scrollBarParent) == HorizontalContainer) |
483 | children.append(t: scrollBarParent); |
484 | } |
485 | |
486 | // Vertical scrollBar container. |
487 | QScrollBar *verticalScrollBar = abstractScrollArea()->verticalScrollBar(); |
488 | if (verticalScrollBar && verticalScrollBar->isVisible()) { |
489 | QWidget *scrollBarParent = verticalScrollBar->parentWidget(); |
490 | // Add container only if scroll bar is in the container |
491 | if (elementType(widget: scrollBarParent) == VerticalContainer) |
492 | children.append(t: scrollBarParent); |
493 | } |
494 | |
495 | // CornerWidget. |
496 | QWidget *cornerWidget = abstractScrollArea()->cornerWidget(); |
497 | if (cornerWidget && cornerWidget->isVisible()) |
498 | children.append(t: cornerWidget); |
499 | |
500 | return children; |
501 | } |
502 | |
503 | QAccessibleAbstractScrollArea::AbstractScrollAreaElement |
504 | QAccessibleAbstractScrollArea::elementType(QWidget *widget) const |
505 | { |
506 | if (!widget) |
507 | return Undefined; |
508 | |
509 | if (widget == abstractScrollArea()) |
510 | return Self; |
511 | if (widget == abstractScrollArea()->viewport()) |
512 | return Viewport; |
513 | if (widget->objectName() == "qt_scrollarea_hcontainer"_L1 ) |
514 | return HorizontalContainer; |
515 | if (widget->objectName() == "qt_scrollarea_vcontainer"_L1 ) |
516 | return VerticalContainer; |
517 | if (widget == abstractScrollArea()->cornerWidget()) |
518 | return CornerWidget; |
519 | |
520 | return Undefined; |
521 | } |
522 | |
523 | bool QAccessibleAbstractScrollArea::isLeftToRight() const |
524 | { |
525 | return abstractScrollArea()->isLeftToRight(); |
526 | } |
527 | |
528 | // ======================= QAccessibleScrollArea =========================== |
529 | QAccessibleScrollArea::QAccessibleScrollArea(QWidget *widget) |
530 | : QAccessibleAbstractScrollArea(widget) |
531 | { |
532 | Q_ASSERT(qobject_cast<QScrollArea *>(widget)); |
533 | } |
534 | #endif // QT_CONFIG(scrollarea) |
535 | |
536 | QT_END_NAMESPACE |
537 | |
538 | #endif // QT_CONFIG(accessibility) |
539 | |