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 QtWidgets module 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 "qaccessiblewidget.h"
41
42#ifndef QT_NO_ACCESSIBILITY
43
44#include "qapplication.h"
45#if QT_CONFIG(groupbox)
46#include "qgroupbox.h"
47#endif
48#if QT_CONFIG(label)
49#include "qlabel.h"
50#endif
51#if QT_CONFIG(tooltip)
52#include "qtooltip.h"
53#endif
54#if QT_CONFIG(whatsthis)
55#include "qwhatsthis.h"
56#endif
57#include "qwidget.h"
58#include "qdebug.h"
59#include <qmath.h>
60#if QT_CONFIG(rubberband)
61#include <QRubberBand>
62#endif
63#include <QFocusFrame>
64#if QT_CONFIG(menu)
65#include <QMenu>
66#endif
67#include <QtWidgets/private/qwidget_p.h>
68
69QT_BEGIN_NAMESPACE
70
71static QList<QWidget*> childWidgets(const QWidget *widget)
72{
73 QList<QWidget*> widgets;
74 for (QObject *o : widget->children()) {
75 QWidget *w = qobject_cast<QWidget *>(o);
76 if (w && !w->isWindow()
77 && !qobject_cast<QFocusFrame*>(w)
78#if QT_CONFIG(menu)
79 && !qobject_cast<QMenu*>(w)
80#endif
81 && w->objectName() != QLatin1String("qt_rubberband")
82 && w->objectName() != QLatin1String("qt_spinbox_lineedit"))
83 widgets.append(w);
84 }
85 return widgets;
86}
87
88static QString buddyString(const QWidget *widget)
89{
90 if (!widget)
91 return QString();
92 QWidget *parent = widget->parentWidget();
93 if (!parent)
94 return QString();
95#if QT_CONFIG(shortcut) && QT_CONFIG(label)
96 for (QObject *o : parent->children()) {
97 QLabel *label = qobject_cast<QLabel*>(o);
98 if (label && label->buddy() == widget)
99 return label->text();
100 }
101#endif
102
103#if QT_CONFIG(groupbox)
104 QGroupBox *groupbox = qobject_cast<QGroupBox*>(parent);
105 if (groupbox)
106 return groupbox->title();
107#endif
108
109 return QString();
110}
111
112/* This function will return the offset of the '&' in the text that would be
113 preceding the accelerator character.
114 If this text does not have an accelerator, -1 will be returned. */
115static int qt_accAmpIndex(const QString &text)
116{
117#ifndef QT_NO_SHORTCUT
118 if (text.isEmpty())
119 return -1;
120
121 int fa = 0;
122 while ((fa = text.indexOf(QLatin1Char('&'), fa)) != -1) {
123 ++fa;
124 if (fa < text.length()) {
125 // ignore "&&"
126 if (text.at(fa) == QLatin1Char('&')) {
127
128 ++fa;
129 continue;
130 } else {
131 return fa - 1;
132 break;
133 }
134 }
135 }
136
137 return -1;
138#else
139 Q_UNUSED(text);
140 return -1;
141#endif
142}
143
144QString qt_accStripAmp(const QString &text)
145{
146 QString newText(text);
147 int ampIndex = qt_accAmpIndex(newText);
148 if (ampIndex != -1)
149 newText.remove(ampIndex, 1);
150
151 return newText.replace(QLatin1String("&&"), QLatin1String("&"));
152}
153
154QString qt_accHotKey(const QString &text)
155{
156#ifndef QT_NO_SHORTCUT
157 int ampIndex = qt_accAmpIndex(text);
158 if (ampIndex != -1)
159 return QKeySequence(Qt::ALT).toString(QKeySequence::NativeText) + text.at(ampIndex + 1);
160#else
161 Q_UNUSED(text);
162#endif
163
164 return QString();
165}
166
167// ### inherit QAccessibleObjectPrivate
168class QAccessibleWidgetPrivate
169{
170public:
171 QAccessibleWidgetPrivate()
172 :role(QAccessible::Client)
173 {}
174
175 QAccessible::Role role;
176 QString name;
177 QStringList primarySignals;
178};
179
180/*!
181 \class QAccessibleWidget
182 \brief The QAccessibleWidget class implements the QAccessibleInterface for QWidgets.
183
184 \ingroup accessibility
185 \inmodule QtWidgets
186
187 This class is part of \l {Accessibility for QWidget Applications}.
188
189 This class is convenient to use as a base class for custom
190 implementations of QAccessibleInterfaces that provide information
191 about widget objects.
192
193 The class provides functions to retrieve the parentObject() (the
194 widget's parent widget), and the associated widget(). Controlling
195 signals can be added with addControllingSignal(), and setters are
196 provided for various aspects of the interface implementation, for
197 example setValue(), setDescription(), setAccelerator(), and
198 setHelp().
199
200 \sa QAccessible, QAccessibleObject
201*/
202
203/*!
204 Creates a QAccessibleWidget object for widget \a w.
205 \a role and \a name are optional parameters that set the object's
206 role and name properties.
207*/
208QAccessibleWidget::QAccessibleWidget(QWidget *w, QAccessible::Role role, const QString &name)
209: QAccessibleObject(w)
210{
211 Q_ASSERT(widget());
212 d = new QAccessibleWidgetPrivate();
213 d->role = role;
214 d->name = name;
215}
216
217/*! \reimp */
218bool QAccessibleWidget::isValid() const
219{
220 if (!object() || static_cast<QWidget *>(object())->d_func()->data.in_destructor)
221 return false;
222 return QAccessibleObject::isValid();
223}
224
225/*! \reimp */
226QWindow *QAccessibleWidget::window() const
227{
228 const QWidget *w = widget();
229 Q_ASSERT(w);
230 QWindow *result = w->windowHandle();
231 if (!result) {
232 if (const QWidget *nativeParent = w->nativeParentWidget())
233 result = nativeParent->windowHandle();
234 }
235 return result;
236}
237
238/*!
239 Destroys this object.
240*/
241QAccessibleWidget::~QAccessibleWidget()
242{
243 delete d;
244}
245
246/*!
247 Returns the associated widget.
248*/
249QWidget *QAccessibleWidget::widget() const
250{
251 return qobject_cast<QWidget*>(object());
252}
253
254/*!
255 Returns the associated widget's parent object, which is either the
256 parent widget, or qApp for top-level widgets.
257*/
258QObject *QAccessibleWidget::parentObject() const
259{
260 QWidget *w = widget();
261 if (!w || w->isWindow() || !w->parentWidget())
262 return qApp;
263 return w->parent();
264}
265
266/*! \reimp */
267QRect QAccessibleWidget::rect() const
268{
269 QWidget *w = widget();
270 if (!w->isVisible())
271 return QRect();
272 QPoint wpos = w->mapToGlobal(QPoint(0, 0));
273
274 return QRect(wpos.x(), wpos.y(), w->width(), w->height());
275}
276
277/*!
278 Registers \a signal as a controlling signal.
279
280 An object is a Controller to any other object connected to a
281 controlling signal.
282*/
283void QAccessibleWidget::addControllingSignal(const QString &signal)
284{
285 QByteArray s = QMetaObject::normalizedSignature(signal.toLatin1());
286 if (Q_UNLIKELY(object()->metaObject()->indexOfSignal(s) < 0))
287 qWarning("Signal %s unknown in %s", s.constData(), object()->metaObject()->className());
288 d->primarySignals << QLatin1String(s);
289}
290
291static inline bool isAncestor(const QObject *obj, const QObject *child)
292{
293 while (child) {
294 if (child == obj)
295 return true;
296 child = child->parent();
297 }
298 return false;
299}
300
301/*! \reimp */
302QList<QPair<QAccessibleInterface *, QAccessible::Relation>>
303QAccessibleWidget::relations(QAccessible::Relation match /*= QAccessible::AllRelations*/) const
304{
305 QList<QPair<QAccessibleInterface *, QAccessible::Relation>> rels;
306 if (match & QAccessible::Label) {
307 const QAccessible::Relation rel = QAccessible::Label;
308 if (QWidget *parent = widget()->parentWidget()) {
309#if QT_CONFIG(shortcut) && QT_CONFIG(label)
310 // first check for all siblings that are labels to us
311 // ideally we would go through all objects and check, but that
312 // will be too expensive
313 const QList<QWidget*> kids = childWidgets(parent);
314 for (QWidget *kid : kids) {
315 if (QLabel *labelSibling = qobject_cast<QLabel*>(kid)) {
316 if (labelSibling->buddy() == widget()) {
317 QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(labelSibling);
318 rels.append(qMakePair(iface, rel));
319 }
320 }
321 }
322#endif
323#if QT_CONFIG(groupbox)
324 QGroupBox *groupbox = qobject_cast<QGroupBox*>(parent);
325 if (groupbox && !groupbox->title().isEmpty()) {
326 QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(groupbox);
327 rels.append(qMakePair(iface, rel));
328 }
329#endif
330 }
331 }
332
333 if (match & QAccessible::Controlled) {
334 QObjectList allReceivers;
335 QObject *connectionObject = object();
336 for (int sig = 0; sig < d->primarySignals.count(); ++sig) {
337 const QObjectList receivers = connectionObject->d_func()->receiverList(d->primarySignals.at(sig).toLatin1());
338 allReceivers += receivers;
339 }
340
341 allReceivers.removeAll(object()); //### The object might connect to itself internally
342
343 for (int i = 0; i < allReceivers.count(); ++i) {
344 const QAccessible::Relation rel = QAccessible::Controlled;
345 QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(allReceivers.at(i));
346 if (iface)
347 rels.append(qMakePair(iface, rel));
348 }
349 }
350
351 return rels;
352}
353
354/*! \reimp */
355QAccessibleInterface *QAccessibleWidget::parent() const
356{
357 return QAccessible::queryAccessibleInterface(parentObject());
358}
359
360/*! \reimp */
361QAccessibleInterface *QAccessibleWidget::child(int index) const
362{
363 Q_ASSERT(widget());
364 QWidgetList childList = childWidgets(widget());
365 if (index >= 0 && index < childList.size())
366 return QAccessible::queryAccessibleInterface(childList.at(index));
367 return nullptr;
368}
369
370/*! \reimp */
371QAccessibleInterface *QAccessibleWidget::focusChild() const
372{
373 if (widget()->hasFocus())
374 return QAccessible::queryAccessibleInterface(object());
375
376 QWidget *fw = widget()->focusWidget();
377 if (!fw)
378 return nullptr;
379
380 if (isAncestor(widget(), fw)) {
381 QAccessibleInterface *iface = QAccessible::queryAccessibleInterface(fw);
382 if (!iface || iface == this || !iface->focusChild())
383 return iface;
384 return iface->focusChild();
385 }
386 return nullptr;
387}
388
389/*! \reimp */
390int QAccessibleWidget::childCount() const
391{
392 QWidgetList cl = childWidgets(widget());
393 return cl.size();
394}
395
396/*! \reimp */
397int QAccessibleWidget::indexOfChild(const QAccessibleInterface *child) const
398{
399 if (!child)
400 return -1;
401 QWidgetList cl = childWidgets(widget());
402 return cl.indexOf(qobject_cast<QWidget *>(child->object()));
403}
404
405// from qwidget.cpp
406extern QString qt_setWindowTitle_helperHelper(const QString &, const QWidget*);
407
408/*! \reimp */
409QString QAccessibleWidget::text(QAccessible::Text t) const
410{
411 QString str;
412
413 switch (t) {
414 case QAccessible::Name:
415 if (!d->name.isEmpty()) {
416 str = d->name;
417 } else if (!widget()->accessibleName().isEmpty()) {
418 str = widget()->accessibleName();
419 } else if (widget()->isWindow()) {
420 if (widget()->isMinimized())
421 str = qt_setWindowTitle_helperHelper(widget()->windowIconText(), widget());
422 else
423 str = qt_setWindowTitle_helperHelper(widget()->windowTitle(), widget());
424 } else {
425 str = qt_accStripAmp(buddyString(widget()));
426 }
427 break;
428 case QAccessible::Description:
429 str = widget()->accessibleDescription();
430#if QT_CONFIG(tooltip)
431 if (str.isEmpty())
432 str = widget()->toolTip();
433#endif
434 break;
435 case QAccessible::Help:
436#if QT_CONFIG(whatsthis)
437 str = widget()->whatsThis();
438#endif
439 break;
440 case QAccessible::Accelerator:
441 str = qt_accHotKey(buddyString(widget()));
442 break;
443 case QAccessible::Value:
444 break;
445 default:
446 break;
447 }
448 return str;
449}
450
451/*! \reimp */
452QStringList QAccessibleWidget::actionNames() const
453{
454 QStringList names;
455 if (widget()->isEnabled()) {
456 if (widget()->focusPolicy() != Qt::NoFocus)
457 names << setFocusAction();
458 }
459 return names;
460}
461
462/*! \reimp */
463void QAccessibleWidget::doAction(const QString &actionName)
464{
465 if (!widget()->isEnabled())
466 return;
467
468 if (actionName == setFocusAction()) {
469 if (widget()->isWindow())
470 widget()->activateWindow();
471 widget()->setFocus();
472 }
473}
474
475/*! \reimp */
476QStringList QAccessibleWidget::keyBindingsForAction(const QString & /* actionName */) const
477{
478 return QStringList();
479}
480
481/*! \reimp */
482QAccessible::Role QAccessibleWidget::role() const
483{
484 return d->role;
485}
486
487/*! \reimp */
488QAccessible::State QAccessibleWidget::state() const
489{
490 QAccessible::State state;
491
492 QWidget *w = widget();
493 if (w->testAttribute(Qt::WA_WState_Visible) == false)
494 state.invisible = true;
495 if (w->focusPolicy() != Qt::NoFocus)
496 state.focusable = true;
497 if (w->hasFocus())
498 state.focused = true;
499 if (!w->isEnabled())
500 state.disabled = true;
501 if (w->isWindow()) {
502 if (w->windowFlags() & Qt::WindowSystemMenuHint)
503 state.movable = true;
504 if (w->minimumSize() != w->maximumSize())
505 state.sizeable = true;
506 if (w->isActiveWindow())
507 state.active = true;
508 }
509
510 return state;
511}
512
513/*! \reimp */
514QColor QAccessibleWidget::foregroundColor() const
515{
516 return widget()->palette().color(widget()->foregroundRole());
517}
518
519/*! \reimp */
520QColor QAccessibleWidget::backgroundColor() const
521{
522 return widget()->palette().color(widget()->backgroundRole());
523}
524
525/*! \reimp */
526void *QAccessibleWidget::interface_cast(QAccessible::InterfaceType t)
527{
528 if (t == QAccessible::ActionInterface)
529 return static_cast<QAccessibleActionInterface*>(this);
530 return nullptr;
531}
532
533QT_END_NAMESPACE
534
535#endif //QT_NO_ACCESSIBILITY
536