Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qaccessiblecache.cpp
Go to the documentation of this file.
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
5#include <QtCore/qdebug.h>
6#include <QtCore/qloggingcategory.h>
7
8#if QT_CONFIG(accessibility)
9
11
12Q_LOGGING_CATEGORY(lcAccessibilityCache, "qt.accessibility.cache");
13
20static QAccessibleCache *accessibleCache = nullptr;
21
22static void cleanupAccessibleCache()
23{
24 delete accessibleCache;
25 accessibleCache = nullptr;
26}
27
28QAccessibleCache::~QAccessibleCache()
29{
30 for (QAccessible::Id id: idToInterface.keys())
31 deleteInterface(id);
32}
33
34QAccessibleCache *QAccessibleCache::instance()
35{
36 if (!accessibleCache) {
37 accessibleCache = new QAccessibleCache;
38 qAddPostRoutine(cleanupAccessibleCache);
39 }
40 return accessibleCache;
41}
42
43/*
44 The ID is always in the range [INT_MAX+1, UINT_MAX].
45 This makes it easy on windows to reserve the positive integer range
46 for the index of a child and not clash with the unique ids.
47*/
48QAccessible::Id QAccessibleCache::acquireId() const
49{
50 static const QAccessible::Id FirstId = QAccessible::Id(INT_MAX) + 1;
51 static QAccessible::Id nextId = FirstId;
52
53 while (idToInterface.contains(nextId)) {
54 // (wrap back when when we reach UINT_MAX - 1)
55 // -1 because on Android -1 is taken for the "View" so just avoid it completely for consistency
56 if (nextId == UINT_MAX - 1)
57 nextId = FirstId;
58 else
59 ++nextId;
60 }
61
62 return nextId++;
63}
64
65QAccessibleInterface *QAccessibleCache::interfaceForId(QAccessible::Id id) const
66{
67 return idToInterface.value(id);
68}
69
70QAccessible::Id QAccessibleCache::idForInterface(QAccessibleInterface *iface) const
71{
72 return interfaceToId.value(iface);
73}
74
75QAccessible::Id QAccessibleCache::idForObject(QObject *obj) const
76{
77 if (obj) {
78 const QMetaObject *mo = obj->metaObject();
79 for (auto pair : objectToId.values(obj)) {
80 if (pair.second == mo) {
81 return pair.first;
82 }
83 }
84 }
85 return 0;
86}
87
93bool QAccessibleCache::containsObject(QObject *obj) const
94{
95 if (obj) {
96 const QMetaObject *mo = obj->metaObject();
97 for (auto pair : objectToId.values(obj)) {
98 if (pair.second == mo) {
99 return true;
100 }
101 }
102 }
103 return false;
104}
105
106QAccessible::Id QAccessibleCache::insert(QObject *object, QAccessibleInterface *iface) const
107{
108 Q_ASSERT(iface);
109 Q_UNUSED(object);
110
111 // object might be 0
112 Q_ASSERT(!containsObject(object));
113 Q_ASSERT_X(!interfaceToId.contains(iface), "", "Accessible interface inserted into cache twice!");
114
115 QAccessible::Id id = acquireId();
116 QObject *obj = iface->object();
117 Q_ASSERT(object == obj);
118 if (obj) {
119 objectToId.insert(obj, qMakePair(id, obj->metaObject()));
120 connect(obj, &QObject::destroyed, this, &QAccessibleCache::objectDestroyed);
121 }
122 idToInterface.insert(id, iface);
123 interfaceToId.insert(iface, id);
124 qCDebug(lcAccessibilityCache) << "insert - id:" << id << " iface:" << iface;
125 return id;
126}
127
128void QAccessibleCache::objectDestroyed(QObject* obj)
129{
130 /*
131 In some cases we might add a not fully-constructed object to the cache. This might happen with
132 for instance QWidget subclasses that are in the construction phase. If updateAccessibility() is
133 called in the constructor of QWidget (directly or indirectly), it will end up asking for the
134 classname of that widget in order to know which accessibility interface subclass the
135 accessibility factory should instantiate and return. However, since that requires a virtual
136 call to metaObject(), it will return the metaObject() of QWidget (not for the subclass), and so
137 the factory will ultimately return a rather generic QAccessibleWidget instead of a more
138 specialized interface. Even though it is a "incomplete" interface it will be put in the cache
139 and it will be usable as if the object is a widget. In order for the cache to not just return
140 the same generic QAccessibleWidget for that object, we have to check if the cache matches
141 the objects QMetaObject. We therefore use a QMultiHash and also store the QMetaObject * in
142 the value. We therefore might potentially store several values for the corresponding object
143 (in theory one for each level in the class inheritance chain)
144
145 This means that after the object have been fully constructed, we will at some point again query
146 for the interface for the same object, but now its metaObject() returns the correct
147 QMetaObject, so it won't return the QAccessibleWidget that is associated with the object in the
148 cache. Instead it will go to the factory and create the _correct_ specialized interface for the
149 object. If that succeeded, it will also put that entry in the cache. We will therefore in those
150 cases insert *two* cache entries for the same object (using QMultiHash). They both must live
151 until the object is destroyed.
152
153 So when the object is destroyed we might have to delete two entries from the cache.
154 */
155 for (auto pair : objectToId.values(obj)) {
156 QAccessible::Id id = pair.first;
157 Q_ASSERT_X(idToInterface.contains(id), "", "QObject with accessible interface deleted, where interface not in cache!");
158 deleteInterface(id, obj);
159 }
160}
161
162void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj)
163{
164 QAccessibleInterface *iface = idToInterface.take(id);
165 qCDebug(lcAccessibilityCache) << "delete - id:" << id << " iface:" << iface;
166 if (!iface) // the interface may be deleted already
167 return;
168 interfaceToId.take(iface);
169 if (!obj)
170 obj = iface->object();
171 if (obj)
172 objectToId.remove(obj);
173 delete iface;
174
175#ifdef Q_OS_MAC
176 removeCocoaElement(id);
177#endif
178}
179
181
182#include "moc_qaccessiblecache_p.cpp"
183
184#endif
\inmodule QtCore
Definition qobject.h:103
void destroyed(QObject *=nullptr)
This signal is emitted immediately before the object obj is destroyed, after any instances of QPointe...
auto mo
[7]
Combined button and popup list for selecting options.
constexpr QBindableInterface iface
Definition qproperty.h:666
void qAddPostRoutine(QtCleanUpFunction p)
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
GLenum GLsizei GLsizei GLint * values
[15]
GLenum GLuint id
[7]
GLhandleARB obj
[2]
QT_BEGIN_NAMESPACE constexpr decltype(auto) qMakePair(T1 &&value1, T2 &&value2) noexcept(noexcept(std::make_pair(std::forward< T1 >(value1), std::forward< T2 >(value2))))
Definition qpair.h:19
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
#define Q_UNUSED(x)
static uint nextId
QStringList keys
connect(quitButton, &QPushButton::clicked, &app, &QCoreApplication::quit, Qt::QueuedConnection)
\inmodule QtCore