Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qtestaccessible.h
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
4#ifndef QTESTACCESSIBLE_H
5#define QTESTACCESSIBLE_H
6
7#if 0
8// inform syncqt
9#pragma qt_no_master_include
10#endif
11
12#include <QtCore/qglobal.h>
13
14#define QVERIFY_EVENT(event)
15 QVERIFY(QTestAccessibility::verifyEvent(event))
16
17#include <QtCore/qlist.h>
18#include <QtCore/qdebug.h>
19#include <QtGui/qaccessible.h>
20#include <QtGui/qguiapplication.h>
21#include <QtTest/qttestglobal.h>
22#include <QtTest/qtestsystem.h>
23
24#if QT_CONFIG(accessibility)
25
26QT_BEGIN_NAMESPACE
27
28class QObject;
29
30// Use pointers since we subclass QAccessibleEvent
31using EventList = QList<QAccessibleEvent*>;
32
33bool operator==(const QAccessibleEvent &l, const QAccessibleEvent &r)
34{
35 if (l.type() != r.type()) {
36// qDebug() << "QAccessibleEvent with wrong type: " << qAccessibleEventString(l.type()) << " and " << qAccessibleEventString(r.type());
37 return false;
38 }
39 if (l.object() != r.object() ||
40 l.child() != r.child()) {
41// qDebug() << "QAccessibleEvent for wrong object: " << l.object() << " and " << r.object() << " child: " << l.child() << " and " << r.child();
42 return false;
43 }
44
45 if (l.type() == QAccessible::StateChanged) {
46 return static_cast<const QAccessibleStateChangeEvent*>(&l)->changedStates()
47 == static_cast<const QAccessibleStateChangeEvent*>(&r)->changedStates();
48 } else if (l.type() == QAccessible::TextCaretMoved) {
49 return static_cast<const QAccessibleTextCursorEvent*>(&l)->cursorPosition()
50 == static_cast<const QAccessibleTextCursorEvent*>(&r)->cursorPosition();
51 } else if (l.type() == QAccessible::TextSelectionChanged) {
52 const QAccessibleTextSelectionEvent *le = static_cast<const QAccessibleTextSelectionEvent*>(&l);
53 const QAccessibleTextSelectionEvent *re = static_cast<const QAccessibleTextSelectionEvent*>(&r);
54 return le->cursorPosition() == re->cursorPosition() &&
55 le->selectionStart() == re->selectionStart() &&
56 le->selectionEnd() == re->selectionEnd();
57 } else if (l.type() == QAccessible::TextInserted) {
58 const QAccessibleTextInsertEvent *le = static_cast<const QAccessibleTextInsertEvent*>(&l);
59 const QAccessibleTextInsertEvent *re = static_cast<const QAccessibleTextInsertEvent*>(&r);
60 return le->cursorPosition() == re->cursorPosition() &&
61 le->changePosition() == re->changePosition() &&
62 le->textInserted() == re->textInserted();
63 } else if (l.type() == QAccessible::TextRemoved) {
64 const QAccessibleTextRemoveEvent *le = static_cast<const QAccessibleTextRemoveEvent*>(&l);
65 const QAccessibleTextRemoveEvent *re = static_cast<const QAccessibleTextRemoveEvent*>(&r);
66 return le->cursorPosition() == re->cursorPosition() &&
67 le->changePosition() == re->changePosition() &&
68 le->textRemoved() == re->textRemoved();
69 } else if (l.type() == QAccessible::TextUpdated) {
70 const QAccessibleTextUpdateEvent *le = static_cast<const QAccessibleTextUpdateEvent*>(&l);
71 const QAccessibleTextUpdateEvent *re = static_cast<const QAccessibleTextUpdateEvent*>(&r);
72 return le->cursorPosition() == re->cursorPosition() &&
73 le->changePosition() == re->changePosition() &&
74 le->textInserted() == re->textInserted() &&
75 le->textRemoved() == re->textRemoved();
76 } else if (l.type() == QAccessible::ValueChanged) {
77 const QAccessibleValueChangeEvent *le = static_cast<const QAccessibleValueChangeEvent*>(&l);
78 const QAccessibleValueChangeEvent *re = static_cast<const QAccessibleValueChangeEvent*>(&r);
79 return le->value() == re->value();
80 }
81 return true;
82}
83
84class QTestAccessibility
85{
86public:
87 static void initialize()
88 {
89 if (!instance()) {
90 instance() = new QTestAccessibility;
91 qAddPostRoutine(cleanup);
92 }
93 }
94
95 static void cleanup()
96 {
97 delete instance();
98 instance() = nullptr;
99 }
100 static void clearEvents() { eventList().clear(); }
101 static EventList events() { return eventList(); }
102 static bool verifyEvent(QAccessibleEvent *ev)
103 {
104 for (int i = 0; eventList().isEmpty() && i < 5; ++i)
105 QTest::qWait(50);
106 if (eventList().isEmpty()) {
107 qWarning("Timeout waiting for accessibility event.");
108 return false;
109 }
110
111 for (int i = 0; i < eventList().size(); ++i) {
112 if (*eventList()[i] == *ev) {
113 if (i != 0) {
114 qWarning() << " Found event at position " << i;
115 qWarning("%s", qPrintable(msgAccessibilityEventListMismatch(eventList(), ev)));
116 }
117 delete eventList().takeAt(i);
118 return true;
119 }
120 }
121
122 qWarning("%s", qPrintable(msgAccessibilityEventListMismatch(eventList(), ev)));
123 return false;
124 }
125 static bool containsEvent(QAccessibleEvent *event) {
126 for (const QAccessibleEvent *ev : std::as_const(eventList())) {
127 if (*ev == *event)
128 return true;
129 }
130 return false;
131 }
132 static void setUpdateHandler(std::function<void(QAccessibleEvent *event)> updateHandler)
133 {
134 instance()->m_updateHandler = updateHandler;
135 }
136
137private:
138 QTestAccessibility()
139 {
140 QAccessible::installUpdateHandler(updateHandler);
141 QAccessible::installRootObjectHandler(rootObjectHandler);
142 }
143
144 ~QTestAccessibility()
145 {
146 QAccessible::installUpdateHandler(nullptr);
147 QAccessible::installRootObjectHandler(nullptr);
148 }
149
150 static void rootObjectHandler(QObject *object)
151 {
152 // qDebug("rootObjectHandler called %p", object);
153 if (object) {
154 QGuiApplication* app = qobject_cast<QGuiApplication*>(object);
155 if ( !app )
156 qWarning("root Object is not a QGuiApplication!");
157 } else {
158 qWarning("root Object called with 0 pointer");
159 }
160 }
161
162 static void updateHandler(QAccessibleEvent *event)
163 {
164 instance()->m_updateHandler(event);
165
166 auto ev = copyEvent(event);
167 if (auto obj = ev->object()) {
168 QObject::connect(obj, &QObject::destroyed, obj, [&, ev](){
169 auto index= eventList().indexOf(ev);
170 if (index == -1)
171 return;
172 eventList().at(index)->m_object = nullptr;
173 });
174 }
175 eventList().append(ev);
176 }
177 static QAccessibleEvent *copyEvent(QAccessibleEvent *event)
178 {
179 QAccessibleEvent *ev;
180 if (event->type() == QAccessible::StateChanged) {
181 if (event->object())
182 ev = new QAccessibleStateChangeEvent(event->object(),
183 static_cast<QAccessibleStateChangeEvent*>(event)->changedStates());
184 else
185 ev = new QAccessibleStateChangeEvent(event->accessibleInterface(),
186 static_cast<QAccessibleStateChangeEvent*>(event)->changedStates());
187 } else if (event->type() == QAccessible::TextCaretMoved) {
188 if (event->object())
189 ev = new QAccessibleTextCursorEvent(event->object(), static_cast<QAccessibleTextCursorEvent*>(event)->cursorPosition());
190 else
191 ev = new QAccessibleTextCursorEvent(event->accessibleInterface(), static_cast<QAccessibleTextCursorEvent*>(event)->cursorPosition());
192 } else if (event->type() == QAccessible::TextSelectionChanged) {
193 const QAccessibleTextSelectionEvent *original = static_cast<QAccessibleTextSelectionEvent*>(event);
194 QAccessibleTextSelectionEvent *sel;
195 if (event->object())
196 sel = new QAccessibleTextSelectionEvent(event->object(), original->selectionStart(), original->selectionEnd());
197 else
198 sel = new QAccessibleTextSelectionEvent(event->accessibleInterface(), original->selectionStart(), original->selectionEnd());
199 sel->setCursorPosition(original->cursorPosition());
200 ev = sel;
201 } else if (event->type() == QAccessible::TextInserted) {
202 const QAccessibleTextInsertEvent *original = static_cast<QAccessibleTextInsertEvent*>(event);
203 QAccessibleTextInsertEvent *ins;
204 if (original->object())
205 ins = new QAccessibleTextInsertEvent(event->object(), original->changePosition(), original->textInserted());
206 else
207 ins = new QAccessibleTextInsertEvent(event->accessibleInterface(), original->changePosition(), original->textInserted());
208 ins->setCursorPosition(original->cursorPosition());
209 ev = ins;
210 } else if (event->type() == QAccessible::TextRemoved) {
211 const QAccessibleTextRemoveEvent *original = static_cast<QAccessibleTextRemoveEvent*>(event);
212 QAccessibleTextRemoveEvent *rem;
213 if (event->object())
214 rem = new QAccessibleTextRemoveEvent(event->object(), original->changePosition(), original->textRemoved());
215 else
216 rem = new QAccessibleTextRemoveEvent(event->accessibleInterface(), original->changePosition(), original->textRemoved());
217 rem->setCursorPosition(original->cursorPosition());
218 ev = rem;
219 } else if (event->type() == QAccessible::TextUpdated) {
220 const QAccessibleTextUpdateEvent *original = static_cast<QAccessibleTextUpdateEvent*>(event);
221 QAccessibleTextUpdateEvent *upd;
222 if (event->object())
223 upd = new QAccessibleTextUpdateEvent(event->object(), original->changePosition(), original->textRemoved(), original->textInserted());
224 else
225 upd = new QAccessibleTextUpdateEvent(event->accessibleInterface(), original->changePosition(), original->textRemoved(), original->textInserted());
226 upd->setCursorPosition(original->cursorPosition());
227 ev = upd;
228 } else if (event->type() == QAccessible::ValueChanged) {
229 if (event->object())
230 ev = new QAccessibleValueChangeEvent(event->object(), static_cast<QAccessibleValueChangeEvent*>(event)->value());
231 else
232 ev = new QAccessibleValueChangeEvent(event->accessibleInterface(), static_cast<QAccessibleValueChangeEvent*>(event)->value());
233 } else if (event->type() == QAccessible::TableModelChanged) {
234 QAccessibleTableModelChangeEvent *oldEvent = static_cast<QAccessibleTableModelChangeEvent*>(event);
235 QAccessibleTableModelChangeEvent *newEvent;
236 if (event->object())
237 newEvent = new QAccessibleTableModelChangeEvent(event->object(), oldEvent->modelChangeType());
238 else
239 newEvent = new QAccessibleTableModelChangeEvent(event->accessibleInterface(), oldEvent->modelChangeType());
240 newEvent->setFirstRow(oldEvent->firstRow());
241 newEvent->setFirstColumn(oldEvent->firstColumn());
242 newEvent->setLastRow(oldEvent->lastRow());
243 newEvent->setLastColumn(oldEvent->lastColumn());
244 ev = newEvent;
245 } else if (event->type() == QAccessible::Announcement) {
246 QAccessibleAnnouncementEvent *oldEvent =
247 static_cast<QAccessibleAnnouncementEvent *>(event);
248 QAccessibleAnnouncementEvent *newEvent;
249 if (event->object())
250 newEvent = new QAccessibleAnnouncementEvent(event->object(), oldEvent->message());
251 else
252 newEvent = new QAccessibleAnnouncementEvent(event->accessibleInterface(),
253 oldEvent->message());
254 newEvent->setPoliteness(oldEvent->politeness());
255 ev = newEvent;
256 } else {
257 if (event->object())
258 ev = new QAccessibleEvent(event->object(), event->type());
259 else
260 ev = new QAccessibleEvent(event->accessibleInterface(), event->type());
261 }
262
263 if (ev->type() != QAccessible::ObjectDestroyed)
264 ev->setChild(event->child());
265 return ev;
266 }
267
268 static EventList &eventList()
269 {
270 static EventList list;
271 return list;
272 }
273
274 static QTestAccessibility *&instance()
275 {
276 static QTestAccessibility *ta = nullptr;
277 return ta;
278 }
279
280private:
281 static QString msgAccessibilityEventListMismatch(const EventList &haystack,
282 const QAccessibleEvent *needle)
283 {
284 QString rc;
285 QDebug str = QDebug(&rc).nospace();
286 str << "Event " << *needle << "\n"
287 << " not found at head of event list of size " << haystack.size() << " :\n";
288 for (const QAccessibleEvent *e : haystack)
289 str << ' ' << *e << "\n";
290 return rc;
291 }
292 std::function<void(QAccessibleEvent *event)> m_updateHandler = [](QAccessibleEvent *) { ; };
293
294};
295
296QT_END_NAMESPACE
297
298#endif // QT_CONFIG(accessibility)
299#endif // QTESTACCESSIBLE_H