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