summaryrefslogtreecommitdiffstats
path: root/src/gui/accessible/qaccessiblecache.cpp
blob: 84d0936386f2e5f459f8fee23b9a75554a36f816 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qaccessiblecache_p.h"
#include <QtCore/qdebug.h>
#include <QtCore/qloggingcategory.h>

#ifndef QT_NO_ACCESSIBILITY

QT_BEGIN_NAMESPACE

Q_LOGGING_CATEGORY(lcAccessibilityCache, "qt.accessibility.cache");

/*!
    \class QAccessibleCache
    \internal
    \brief Maintains a cache of accessible interfaces.
*/

static QAccessibleCache *accessibleCache = nullptr;

static void cleanupAccessibleCache()
{
    delete accessibleCache;
    accessibleCache = nullptr;
}

QAccessibleCache::~QAccessibleCache()
{
    for (QAccessible::Id id: idToInterface.keys())
        deleteInterface(id);
}

QAccessibleCache *QAccessibleCache::instance()
{
    if (!accessibleCache) {
        accessibleCache = new QAccessibleCache;
        qAddPostRoutine(cleanupAccessibleCache);
    }
    return accessibleCache;
}

/*
  The ID is always in the range [INT_MAX+1, UINT_MAX].
  This makes it easy on windows to reserve the positive integer range
  for the index of a child and not clash with the unique ids.
*/
QAccessible::Id QAccessibleCache::acquireId() const
{
    static const QAccessible::Id FirstId = QAccessible::Id(INT_MAX) + 1;
    static QAccessible::Id lastUsedId = FirstId;

    while (idToInterface.contains(lastUsedId)) {
        // (wrap back when when we reach UINT_MAX - 1)
        // -1 because on Android -1 is taken for the "View" so just avoid it completely for consistency
        if (lastUsedId == UINT_MAX - 1)
            lastUsedId = FirstId;
        else
            ++lastUsedId;
    }

    return lastUsedId;
}

QAccessibleInterface *QAccessibleCache::interfaceForId(QAccessible::Id id) const
{
    return idToInterface.value(id);
}

QAccessible::Id QAccessibleCache::idForInterface(QAccessibleInterface *iface) const
{
    return interfaceToId.value(iface);
}

QAccessible::Id QAccessibleCache::idForObject(QObject *obj) const
{
    if (obj) {
        const QMetaObject *mo = obj->metaObject();
        for (auto pair : objectToId.values(obj)) {
            if (pair.second == mo) {
                return pair.first;
            }
        }
    }
    return 0;
}

/*!
 * \internal
 *
 * returns true if the cache has an interface for the object and its corresponding QMetaObject
 */
bool QAccessibleCache::containsObject(QObject *obj) const
{
    if (obj) {
        const QMetaObject *mo = obj->metaObject();
        for (auto pair : objectToId.values(obj)) {
            if (pair.second == mo) {
                return true;
            }
        }
    }
    return false;
}

QAccessible::Id QAccessibleCache::insert(QObject *object, QAccessibleInterface *iface) const
{
    Q_ASSERT(iface);
    Q_UNUSED(object);

    // object might be 0
    Q_ASSERT(!containsObject(object));
    Q_ASSERT_X(!interfaceToId.contains(iface), "", "Accessible interface inserted into cache twice!");

    QAccessible::Id id = acquireId();
    QObject *obj = iface->object();
    Q_ASSERT(object == obj);
    if (obj) {
        objectToId.insert(obj, qMakePair(id, obj->metaObject()));
        connect(obj, &QObject::destroyed, this, &QAccessibleCache::objectDestroyed);
    }
    idToInterface.insert(id, iface);
    interfaceToId.insert(iface, id);
    qCDebug(lcAccessibilityCache) << "insert - id:" << id << " iface:" << iface;
    return id;
}

void QAccessibleCache::objectDestroyed(QObject* obj)
{
    /*
    In some cases we might add a not fully-constructed object to the cache. This might happen with
    for instance QWidget subclasses that are in the construction phase. If updateAccessibility() is
    called in the constructor of QWidget (directly or indirectly), it it will end up asking for the
    classname of that widget in order to know which accessibility interface subclass the
    accessibility factory should instantiate and return. However, since that requires a virtual
    call to metaObject(), it will return the metaObject() of QWidget (not for the subclass), and so
    the factory will ultimately return a rather generic QAccessibleWidget instead of a more
    specialized interface. Even though it is a "incomplete" interface it will be put in the cache
    and it will be usable as if the object is a widget. In order for the cache to not just return
    the same generic QAccessibleWidget for that object, we have to check if the cache matches
    the objects QMetaObject. We therefore use a QMultiHash and also store the QMetaObject * in
    the value. We therefore might potentially store several values for the corresponding object
    (in theory one for each level in the class inheritance chain)

    This means that after the object have been fully constructed, we will at some point again query
    for the interface for the same object, but now its metaObject() returns the correct
    QMetaObject, so it won't return the QAccessibleWidget that is associated with the object in the
    cache. Instead it will go to the factory and create the _correct_ specialized interface for the
    object. If that succeeded, it will also put that entry in the cache. We will therefore in those
    cases insert *two* cache entries for the same object (using QMultiHash). They both must live
    until the object is destroyed.

    So when the object is destroyed we might have to delete two entries from the cache.
    */
    for (auto pair : objectToId.values(obj)) {
        QAccessible::Id id = pair.first;
        Q_ASSERT_X(idToInterface.contains(id), "", "QObject with accessible interface deleted, where interface not in cache!");
        deleteInterface(id, obj);
    }
}

void QAccessibleCache::deleteInterface(QAccessible::Id id, QObject *obj)
{
    QAccessibleInterface *iface = idToInterface.take(id);
    qCDebug(lcAccessibilityCache) << "delete - id:" << id << " iface:" << iface;
    if (!iface) // the interface may be deleted already
        return;
    interfaceToId.take(iface);
    if (!obj)
        obj = iface->object();
    if (obj)
        objectToId.remove(obj);
    delete iface;

#ifdef Q_OS_MAC
    removeCocoaElement(id);
#endif
}

QT_END_NAMESPACE

#endif