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