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

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