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
41QT_BEGIN_NAMESPACE
42
43using namespace Qt::StringLiterals;
44
45QString qt_accStripAmp(const QString &text);
46QString 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
57class QAccessibleTabButton: public QAccessibleInterface, public QAccessibleActionInterface
58{
59public:
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
163private:
164 QPointer<QTabBar> m_parent;
165 int m_index;
166
167};
168
169/*!
170 Constructs a QAccessibleTabBar object for \a w.
171*/
172QAccessibleTabBar::QAccessibleTabBar(QWidget *w)
173: QAccessibleWidget(w, QAccessible::PageTabList)
174{
175 Q_ASSERT(tabBar());
176}
177
178QAccessibleTabBar::~QAccessibleTabBar()
179{
180 for (QAccessible::Id id : std::as_const(t&: m_childInterfaces))
181 QAccessible::deleteAccessibleInterface(uniqueId: id);
182}
183
184/*! Returns the QTabBar. */
185QTabBar *QAccessibleTabBar::tabBar() const
186{
187 return qobject_cast<QTabBar*>(object: object());
188}
189
190QAccessibleInterface* 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
200QAccessibleInterface* 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
224int 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
240int QAccessibleTabBar::childCount() const
241{
242 // tabs + scroll buttons
243 return tabBar()->count() + 2;
244}
245
246QString 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*/
275QAccessibleComboBox::QAccessibleComboBox(QWidget *w)
276: QAccessibleWidget(w, QAccessible::ComboBox)
277{
278 Q_ASSERT(comboBox());
279}
280
281/*!
282 Returns the combobox.
283*/
284QComboBox *QAccessibleComboBox::comboBox() const
285{
286 return qobject_cast<QComboBox*>(object: object());
287}
288
289QAccessibleInterface *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
301int QAccessibleComboBox::childCount() const
302{
303 // list and text edit
304 return comboBox()->isEditable() ? 2 : 1;
305}
306
307QAccessibleInterface *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
314int 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
323QAccessibleInterface *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 */
334QString 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
363QAccessible::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
374QStringList QAccessibleComboBox::actionNames() const
375{
376 return QStringList() << showMenuAction() << pressAction();
377}
378
379QString 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
386void 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
412QStringList 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 =======================
421QAccessibleAbstractScrollArea::QAccessibleAbstractScrollArea(QWidget *widget)
422 : QAccessibleWidget(widget, QAccessible::Client)
423{
424 Q_ASSERT(qobject_cast<QAbstractScrollArea *>(widget));
425}
426
427QAccessibleInterface *QAccessibleAbstractScrollArea::child(int index) const
428{
429 return QAccessible::queryAccessibleInterface(accessibleChildren().at(i: index));
430}
431
432int QAccessibleAbstractScrollArea::childCount() const
433{
434 return accessibleChildren().size();
435}
436
437int 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
444bool QAccessibleAbstractScrollArea::isValid() const
445{
446 return (QAccessibleWidget::isValid() && abstractScrollArea() && abstractScrollArea()->viewport());
447}
448
449QAccessibleInterface *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
463QAbstractScrollArea *QAccessibleAbstractScrollArea::abstractScrollArea() const
464{
465 return static_cast<QAbstractScrollArea *>(object());
466}
467
468QWidgetList 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
503QAccessibleAbstractScrollArea::AbstractScrollAreaElement
504QAccessibleAbstractScrollArea::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
523bool QAccessibleAbstractScrollArea::isLeftToRight() const
524{
525 return abstractScrollArea()->isLeftToRight();
526}
527
528// ======================= QAccessibleScrollArea ===========================
529QAccessibleScrollArea::QAccessibleScrollArea(QWidget *widget)
530 : QAccessibleAbstractScrollArea(widget)
531{
532 Q_ASSERT(qobject_cast<QScrollArea *>(widget));
533}
534#endif // QT_CONFIG(scrollarea)
535
536QT_END_NAMESPACE
537
538#endif // QT_CONFIG(accessibility)
539

source code of qtbase/src/widgets/accessible/complexwidgets.cpp