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

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