1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "complexwidgets_p.h"
41 
42 #include <qaccessible.h>
43 #include <qapplication.h>
44 #include <qevent.h>
45 #if QT_CONFIG(itemviews)
46 #include <qheaderview.h>
47 #endif
48 #if QT_CONFIG(tabbar)
49 #include <qtabbar.h>
50 #include <private/qtabbar_p.h>
51 #endif
52 #if QT_CONFIG(combobox)
53 #include <qcombobox.h>
54 #endif
55 #if QT_CONFIG(lineedit)
56 #include <qlineedit.h>
57 #endif
58 #include <qstyle.h>
59 #include <qstyleoption.h>
60 #include <qtooltip.h>
61 #if QT_CONFIG(whatsthis)
62 #include <qwhatsthis.h>
63 #endif
64 #include <QAbstractScrollArea>
65 #if QT_CONFIG(scrollarea)
66 #include <QScrollArea>
67 #endif
68 #if QT_CONFIG(scrollbar)
69 #include <QScrollBar>
70 #endif
71 #include <QDebug>
72 
73 #ifndef QT_NO_ACCESSIBILITY
74 
75 QT_BEGIN_NAMESPACE
76 
77 QString qt_accStripAmp(const QString &text);
78 QString qt_accHotKey(const QString &text);
79 
80 #if QT_CONFIG(tabbar)
81 /*!
82   \class QAccessibleTabBar
83   \brief The QAccessibleTabBar class implements the QAccessibleInterface for tab bars.
84   \internal
85 
86   \ingroup accessibility
87 */
88 
89 class QAccessibleTabButton: public QAccessibleInterface, public QAccessibleActionInterface
90 {
91 public:
QAccessibleTabButton(QTabBar * parent,int index)92     QAccessibleTabButton(QTabBar *parent, int index)
93         : m_parent(parent), m_index(index)
94     {}
95 
interface_cast(QAccessible::InterfaceType t)96     void *interface_cast(QAccessible::InterfaceType t) override {
97         if (t == QAccessible::ActionInterface) {
98             return static_cast<QAccessibleActionInterface*>(this);
99         }
100         return nullptr;
101     }
102 
object() const103     QObject *object() const override { return nullptr; }
role() const104     QAccessible::Role role() const override { return QAccessible::PageTab; }
state() const105     QAccessible::State state() const override {
106         if (!isValid()) {
107             QAccessible::State s;
108             s.invalid = true;
109             return s;
110         }
111 
112         QAccessible::State s = parent()->state();
113         s.focused = (m_index == m_parent->currentIndex());
114         return s;
115     }
rect() const116     QRect rect() const override {
117         if (!isValid())
118             return QRect();
119 
120         QPoint tp = m_parent->mapToGlobal(QPoint(0,0));
121         QRect rec = m_parent->tabRect(m_index);
122         rec = QRect(tp.x() + rec.x(), tp.y() + rec.y(), rec.width(), rec.height());
123         return rec;
124     }
125 
isValid() const126     bool isValid() const override {
127         if (m_parent) {
128             if (static_cast<QWidget *>(m_parent.data())->d_func()->data.in_destructor)
129                 return false;
130             return m_parent->count() > m_index;
131         }
132         return false;
133     }
134 
childAt(int,int) const135     QAccessibleInterface *childAt(int, int) const override { return nullptr; }
childCount() const136     int childCount() const override { return 0; }
indexOfChild(const QAccessibleInterface *) const137     int indexOfChild(const QAccessibleInterface *) const override  { return -1; }
138 
text(QAccessible::Text t) const139     QString text(QAccessible::Text t) const override
140     {
141         if (!isValid())
142             return QString();
143         QString str;
144         switch (t) {
145         case QAccessible::Name:
146             str = m_parent->accessibleTabName(m_index);
147             if (str.isEmpty())
148                 str = qt_accStripAmp(m_parent->tabText(m_index));
149             break;
150         case QAccessible::Accelerator:
151             str = qt_accHotKey(m_parent->tabText(m_index));
152             break;
153 #if QT_CONFIG(tooltip)
154         case QAccessible::Description:
155             str = m_parent->tabToolTip(m_index);
156             break;
157 #endif
158 #if QT_CONFIG(whatsthis)
159         case QAccessible::Help:
160             str = m_parent->tabWhatsThis(m_index);
161             break;
162 #endif
163         default:
164             break;
165         }
166         return str;
167     }
168 
setText(QAccessible::Text,const QString &)169     void setText(QAccessible::Text, const QString &) override {}
170 
parent() const171     QAccessibleInterface *parent() const override {
172         return QAccessible::queryAccessibleInterface(m_parent.data());
173     }
child(int) const174     QAccessibleInterface *child(int) const override { return nullptr; }
175 
176     // action interface
actionNames() const177     QStringList actionNames() const override
178     {
179         return QStringList(pressAction());
180     }
181 
doAction(const QString & actionName)182     void doAction(const QString &actionName) override
183     {
184         if (isValid() && actionName == pressAction())
185             m_parent->setCurrentIndex(m_index);
186     }
187 
keyBindingsForAction(const QString &) const188     QStringList keyBindingsForAction(const QString &) const override
189     {
190         return QStringList();
191     }
192 
index() const193     int index() const { return m_index; }
194 
195 private:
196     QPointer<QTabBar> m_parent;
197     int m_index;
198 
199 };
200 
201 /*!
202   Constructs a QAccessibleTabBar object for \a w.
203 */
QAccessibleTabBar(QWidget * w)204 QAccessibleTabBar::QAccessibleTabBar(QWidget *w)
205 : QAccessibleWidget(w, QAccessible::PageTabList)
206 {
207     Q_ASSERT(tabBar());
208 }
209 
~QAccessibleTabBar()210 QAccessibleTabBar::~QAccessibleTabBar()
211 {
212     for (QAccessible::Id id : qAsConst(m_childInterfaces))
213         QAccessible::deleteAccessibleInterface(id);
214 }
215 
216 /*! Returns the QTabBar. */
tabBar() const217 QTabBar *QAccessibleTabBar::tabBar() const
218 {
219     return qobject_cast<QTabBar*>(object());
220 }
221 
focusChild() const222 QAccessibleInterface* QAccessibleTabBar::focusChild() const
223 {
224     for (int i = 0; i < childCount(); ++i) {
225         if (child(i)->state().focused)
226             return child(i);
227     }
228 
229     return nullptr;
230 }
231 
child(int index) const232 QAccessibleInterface* QAccessibleTabBar::child(int index) const
233 {
234     if (QAccessible::Id id = m_childInterfaces.value(index))
235         return QAccessible::accessibleInterface(id);
236 
237     // first the tabs, then 2 buttons
238     if (index < tabBar()->count()) {
239         QAccessibleTabButton *button = new QAccessibleTabButton(tabBar(), index);
240         QAccessible::registerAccessibleInterface(button);
241         m_childInterfaces.insert(index, QAccessible::uniqueId(button));
242         return button;
243     } else if (index >= tabBar()->count()) {
244         // left button
245         if (index - tabBar()->count() == 0) {
246             return QAccessible::queryAccessibleInterface(tabBar()->d_func()->leftB);
247         }
248         // right button
249         if (index - tabBar()->count() == 1) {
250             return QAccessible::queryAccessibleInterface(tabBar()->d_func()->rightB);
251         }
252     }
253     return nullptr;
254 }
255 
indexOfChild(const QAccessibleInterface * child) const256 int QAccessibleTabBar::indexOfChild(const QAccessibleInterface *child) const
257 {
258     if (child->object() && child->object() == tabBar()->d_func()->leftB)
259         return tabBar()->count();
260     if (child->object() && child->object() == tabBar()->d_func()->rightB)
261         return tabBar()->count() + 1;
262     if (child->role() == QAccessible::PageTab) {
263         QAccessibleInterface *parent = child->parent();
264         if (parent == this) {
265             const QAccessibleTabButton *tabButton = static_cast<const QAccessibleTabButton *>(child);
266             return tabButton->index();
267         }
268     }
269     return -1;
270 }
271 
childCount() const272 int QAccessibleTabBar::childCount() const
273 {
274     // tabs + scroll buttons
275     return tabBar()->count() + 2;
276 }
277 
text(QAccessible::Text t) const278 QString QAccessibleTabBar::text(QAccessible::Text t) const
279 {
280     if (t == QAccessible::Name) {
281         const QTabBar *tBar = tabBar();
282         int idx = tBar->currentIndex();
283         QString str = tBar->accessibleTabName(idx);
284         if (str.isEmpty())
285             str = qt_accStripAmp(tBar->tabText(idx));
286         return str;
287     } else if (t == QAccessible::Accelerator) {
288         return qt_accHotKey(tabBar()->tabText(tabBar()->currentIndex()));
289     }
290     return QString();
291 }
292 
293 #endif // QT_CONFIG(tabbar)
294 
295 #if QT_CONFIG(combobox)
296 /*!
297   \class QAccessibleComboBox
298   \brief The QAccessibleComboBox class implements the QAccessibleInterface for editable and read-only combo boxes.
299   \internal
300 
301   \ingroup accessibility
302 */
303 
304 /*!
305   Constructs a QAccessibleComboBox object for \a w.
306 */
QAccessibleComboBox(QWidget * w)307 QAccessibleComboBox::QAccessibleComboBox(QWidget *w)
308 : QAccessibleWidget(w, QAccessible::ComboBox)
309 {
310     Q_ASSERT(comboBox());
311 }
312 
313 /*!
314   Returns the combobox.
315 */
comboBox() const316 QComboBox *QAccessibleComboBox::comboBox() const
317 {
318     return qobject_cast<QComboBox*>(object());
319 }
320 
child(int index) const321 QAccessibleInterface *QAccessibleComboBox::child(int index) const
322 {
323     if (index == 0) {
324         QAbstractItemView *view = comboBox()->view();
325         //QWidget *parent = view ? view->parentWidget() : 0;
326         return QAccessible::queryAccessibleInterface(view);
327     } else if (index == 1 && comboBox()->isEditable()) {
328         return QAccessible::queryAccessibleInterface(comboBox()->lineEdit());
329     }
330     return nullptr;
331 }
332 
childCount() const333 int QAccessibleComboBox::childCount() const
334 {
335     // list and text edit
336     return comboBox()->isEditable() ? 2 : 1;
337 }
338 
childAt(int x,int y) const339 QAccessibleInterface *QAccessibleComboBox::childAt(int x, int y) const
340 {
341     if (comboBox()->isEditable() && comboBox()->lineEdit()->rect().contains(x, y))
342         return child(1);
343     return nullptr;
344 }
345 
indexOfChild(const QAccessibleInterface * child) const346 int QAccessibleComboBox::indexOfChild(const QAccessibleInterface *child) const
347 {
348     if (comboBox()->view() == child->object())
349         return 0;
350     if (comboBox()->isEditable() && comboBox()->lineEdit() == child->object())
351         return 1;
352     return -1;
353 }
354 
355 /*! \reimp */
text(QAccessible::Text t) const356 QString QAccessibleComboBox::text(QAccessible::Text t) const
357 {
358     QString str;
359 
360     switch (t) {
361     case QAccessible::Name:
362 #ifndef Q_OS_UNIX // on Linux we use relations for this, name is text (fall through to Value)
363         str = QAccessibleWidget::text(t);
364         break;
365 #endif
366     case QAccessible::Value:
367         if (comboBox()->isEditable())
368             str = comboBox()->lineEdit()->text();
369         else
370             str = comboBox()->currentText();
371         break;
372 #ifndef QT_NO_SHORTCUT
373     case QAccessible::Accelerator:
374         str = QKeySequence(Qt::Key_Down).toString(QKeySequence::NativeText);
375         break;
376 #endif
377     default:
378         break;
379     }
380     if (str.isEmpty())
381         str = QAccessibleWidget::text(t);
382     return str;
383 }
384 
actionNames() const385 QStringList QAccessibleComboBox::actionNames() const
386 {
387     return QStringList() << showMenuAction() << pressAction();
388 }
389 
localizedActionDescription(const QString & actionName) const390 QString QAccessibleComboBox::localizedActionDescription(const QString &actionName) const
391 {
392     if (actionName == showMenuAction() || actionName == pressAction())
393         return QComboBox::tr("Open the combo box selection popup");
394     return QString();
395 }
396 
doAction(const QString & actionName)397 void QAccessibleComboBox::doAction(const QString &actionName)
398 {
399     if (actionName == showMenuAction() || actionName == pressAction()) {
400         if (comboBox()->view()->isVisible()) {
401 #if defined(Q_OS_ANDROID)
402             const auto list = child(0)->tableInterface();
403             if (list && list->selectedRowCount() > 0) {
404                 comboBox()->setCurrentIndex(list->selectedRows().at(0));
405             }
406             comboBox()->setFocus();
407 #endif
408             comboBox()->hidePopup();
409         } else {
410             comboBox()->showPopup();
411 #if defined(Q_OS_ANDROID)
412             const auto list = child(0)->tableInterface();
413             if (list && list->selectedRowCount() > 0) {
414                 auto selectedCells = list->selectedCells();
415                 QAccessibleEvent ev(selectedCells.at(0),QAccessible::Focus);
416                 QAccessible::updateAccessibility(&ev);
417             }
418 #endif
419         }
420     }
421 }
422 
keyBindingsForAction(const QString &) const423 QStringList QAccessibleComboBox::keyBindingsForAction(const QString &/*actionName*/) const
424 {
425     return QStringList();
426 }
427 
428 #endif // QT_CONFIG(combobox)
429 
430 #if QT_CONFIG(scrollarea)
431 // ======================= QAccessibleAbstractScrollArea =======================
QAccessibleAbstractScrollArea(QWidget * widget)432 QAccessibleAbstractScrollArea::QAccessibleAbstractScrollArea(QWidget *widget)
433     : QAccessibleWidget(widget, QAccessible::Client)
434 {
435     Q_ASSERT(qobject_cast<QAbstractScrollArea *>(widget));
436 }
437 
child(int index) const438 QAccessibleInterface *QAccessibleAbstractScrollArea::child(int index) const
439 {
440     return QAccessible::queryAccessibleInterface(accessibleChildren().at(index));
441 }
442 
childCount() const443 int QAccessibleAbstractScrollArea::childCount() const
444 {
445     return accessibleChildren().count();
446 }
447 
indexOfChild(const QAccessibleInterface * child) const448 int QAccessibleAbstractScrollArea::indexOfChild(const QAccessibleInterface *child) const
449 {
450     if (!child || !child->object())
451         return -1;
452     return accessibleChildren().indexOf(qobject_cast<QWidget *>(child->object()));
453 }
454 
isValid() const455 bool QAccessibleAbstractScrollArea::isValid() const
456 {
457     return (QAccessibleWidget::isValid() && abstractScrollArea() && abstractScrollArea()->viewport());
458 }
459 
childAt(int x,int y) const460 QAccessibleInterface *QAccessibleAbstractScrollArea::childAt(int x, int y) const
461 {
462     if (!abstractScrollArea()->isVisible())
463         return nullptr;
464 
465     for (int i = 0; i < childCount(); ++i) {
466         QPoint wpos = accessibleChildren().at(i)->mapToGlobal(QPoint(0, 0));
467         QRect rect = QRect(wpos, accessibleChildren().at(i)->size());
468         if (rect.contains(x, y))
469             return child(i);
470     }
471     return nullptr;
472 }
473 
abstractScrollArea() const474 QAbstractScrollArea *QAccessibleAbstractScrollArea::abstractScrollArea() const
475 {
476     return static_cast<QAbstractScrollArea *>(object());
477 }
478 
accessibleChildren() const479 QWidgetList QAccessibleAbstractScrollArea::accessibleChildren() const
480 {
481     QWidgetList children;
482 
483     // Viewport.
484     QWidget * viewport = abstractScrollArea()->viewport();
485     if (viewport)
486         children.append(viewport);
487 
488     // Horizontal scrollBar container.
489     QScrollBar *horizontalScrollBar = abstractScrollArea()->horizontalScrollBar();
490     if (horizontalScrollBar && horizontalScrollBar->isVisible()) {
491         children.append(horizontalScrollBar->parentWidget());
492     }
493 
494     // Vertical scrollBar container.
495     QScrollBar *verticalScrollBar = abstractScrollArea()->verticalScrollBar();
496     if (verticalScrollBar && verticalScrollBar->isVisible()) {
497         children.append(verticalScrollBar->parentWidget());
498     }
499 
500     // CornerWidget.
501     QWidget *cornerWidget = abstractScrollArea()->cornerWidget();
502     if (cornerWidget && cornerWidget->isVisible())
503         children.append(cornerWidget);
504 
505     return children;
506 }
507 
508 QAccessibleAbstractScrollArea::AbstractScrollAreaElement
elementType(QWidget * widget) const509 QAccessibleAbstractScrollArea::elementType(QWidget *widget) const
510 {
511     if (!widget)
512         return Undefined;
513 
514     if (widget == abstractScrollArea())
515         return Self;
516     if (widget == abstractScrollArea()->viewport())
517         return Viewport;
518     if (widget->objectName() == QLatin1String("qt_scrollarea_hcontainer"))
519         return HorizontalContainer;
520     if (widget->objectName() == QLatin1String("qt_scrollarea_vcontainer"))
521         return VerticalContainer;
522     if (widget == abstractScrollArea()->cornerWidget())
523         return CornerWidget;
524 
525     return Undefined;
526 }
527 
isLeftToRight() const528 bool QAccessibleAbstractScrollArea::isLeftToRight() const
529 {
530     return abstractScrollArea()->isLeftToRight();
531 }
532 
533 // ======================= QAccessibleScrollArea ===========================
QAccessibleScrollArea(QWidget * widget)534 QAccessibleScrollArea::QAccessibleScrollArea(QWidget *widget)
535     : QAccessibleAbstractScrollArea(widget)
536 {
537     Q_ASSERT(qobject_cast<QScrollArea *>(widget));
538 }
539 #endif // QT_CONFIG(scrollarea)
540 
541 QT_END_NAMESPACE
542 
543 #endif // QT_NO_ACCESSIBILITY
544