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

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