1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtTest module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#ifndef QTESTACCESSIBLE_H
41#define QTESTACCESSIBLE_H
42
43#if 0
44// inform syncqt
45#pragma qt_no_master_include
46#endif
47
48#include <QtCore/qglobal.h>
49
50#define QVERIFY_EVENT(event) \
51 QVERIFY(QTestAccessibility::verifyEvent(event))
52
53#include <QtCore/qlist.h>
54#include <QtCore/qdebug.h>
55#include <QtGui/qaccessible.h>
56#include <QtGui/qguiapplication.h>
57#include <QtTest/qttestglobal.h>
58#include <QtTest/qtestsystem.h>
59
60#if QT_CONFIG(accessibility)
61
62QT_BEGIN_NAMESPACE
63
64
65class QObject;
66
67// Use pointers since we subclass QAccessibleEvent
68using EventList = QList<QAccessibleEvent*>;
69
70bool operator==(const QAccessibleEvent &l, const QAccessibleEvent &r)
71{
72 if (l.type() != r.type()) {
73// qDebug() << "QAccessibleEvent with wrong type: " << qAccessibleEventString(l.type()) << " and " << qAccessibleEventString(r.type());
74 return false;
75 }
76 if (l.object() != r.object() ||
77 l.child() != r.child()) {
78// qDebug() << "QAccessibleEvent for wrong object: " << l.object() << " and " << r.object() << " child: " << l.child() << " and " << r.child();
79 return false;
80 }
81
82 if (l.type() == QAccessible::StateChanged) {
83 return static_cast<const QAccessibleStateChangeEvent*>(&l)->changedStates()
84 == static_cast<const QAccessibleStateChangeEvent*>(&r)->changedStates();
85 } else if (l.type() == QAccessible::TextCaretMoved) {
86 return static_cast<const QAccessibleTextCursorEvent*>(&l)->cursorPosition()
87 == static_cast<const QAccessibleTextCursorEvent*>(&r)->cursorPosition();
88 } else if (l.type() == QAccessible::TextSelectionChanged) {
89 const QAccessibleTextSelectionEvent *le = static_cast<const QAccessibleTextSelectionEvent*>(&l);
90 const QAccessibleTextSelectionEvent *re = static_cast<const QAccessibleTextSelectionEvent*>(&r);
91 return le->cursorPosition() == re->cursorPosition() &&
92 le->selectionStart() == re->selectionStart() &&
93 le->selectionEnd() == re->selectionEnd();
94 } else if (l.type() == QAccessible::TextInserted) {
95 const QAccessibleTextInsertEvent *le = static_cast<const QAccessibleTextInsertEvent*>(&l);
96 const QAccessibleTextInsertEvent *re = static_cast<const QAccessibleTextInsertEvent*>(&r);
97 return le->cursorPosition() == re->cursorPosition() &&
98 le->changePosition() == re->changePosition() &&
99 le->textInserted() == re->textInserted();
100 } else if (l.type() == QAccessible::TextRemoved) {
101 const QAccessibleTextRemoveEvent *le = static_cast<const QAccessibleTextRemoveEvent*>(&l);
102 const QAccessibleTextRemoveEvent *re = static_cast<const QAccessibleTextRemoveEvent*>(&r);
103 return le->cursorPosition() == re->cursorPosition() &&
104 le->changePosition() == re->changePosition() &&
105 le->textRemoved() == re->textRemoved();
106 } else if (l.type() == QAccessible::TextUpdated) {
107 const QAccessibleTextUpdateEvent *le = static_cast<const QAccessibleTextUpdateEvent*>(&l);
108 const QAccessibleTextUpdateEvent *re = static_cast<const QAccessibleTextUpdateEvent*>(&r);
109 return le->cursorPosition() == re->cursorPosition() &&
110 le->changePosition() == re->changePosition() &&
111 le->textInserted() == re->textInserted() &&
112 le->textRemoved() == re->textRemoved();
113 } else if (l.type() == QAccessible::ValueChanged) {
114 const QAccessibleValueChangeEvent *le = static_cast<const QAccessibleValueChangeEvent*>(&l);
115 const QAccessibleValueChangeEvent *re = static_cast<const QAccessibleValueChangeEvent*>(&r);
116 return le->value() == re->value();
117 }
118 return true;
119}
120
121class QTestAccessibility
122{
123public:
124 static void initialize()
125 {
126 if (!instance()) {
127 instance() = new QTestAccessibility;
128 qAddPostRoutine(cleanup);
129 }
130 }
131
132 static void cleanup()
133 {
134 delete instance();
135 instance() = nullptr;
136 }
137 static void clearEvents() { eventList().clear(); }
138 static EventList events() { return eventList(); }
139 static bool verifyEvent(QAccessibleEvent *ev)
140 {
141 for (int i = 0; eventList().isEmpty() && i < 5; ++i)
142 QTest::qWait(50);
143 if (eventList().isEmpty()) {
144 qWarning("Timeout waiting for accessibility event.");
145 return false;
146 }
147 const bool res = *eventList().first() == *ev;
148 if (!res)
149 qWarning("%s", qPrintable(msgAccessibilityEventListMismatch(eventList(), ev)));
150 delete eventList().takeFirst();
151 return res;
152 }
153 static bool containsEvent(QAccessibleEvent *event) {
154 for (const QAccessibleEvent *ev : qAsConst(eventList())) {
155 if (*ev == *event)
156 return true;
157 }
158 return false;
159 }
160
161private:
162 QTestAccessibility()
163 {
164 QAccessible::installUpdateHandler(updateHandler);
165 QAccessible::installRootObjectHandler(rootObjectHandler);
166 }
167
168 ~QTestAccessibility()
169 {
170 QAccessible::installUpdateHandler(nullptr);
171 QAccessible::installRootObjectHandler(nullptr);
172 }
173
174 static void rootObjectHandler(QObject *object)
175 {
176 // qDebug("rootObjectHandler called %p", object);
177 if (object) {
178 QGuiApplication* app = qobject_cast<QGuiApplication*>(object);
179 if ( !app )
180 qWarning("root Object is not a QGuiApplication!");
181 } else {
182 qWarning("root Object called with 0 pointer");
183 }
184 }
185
186 static void updateHandler(QAccessibleEvent *event)
187 {
188 eventList().append(copyEvent(event));
189 }
190 static QAccessibleEvent *copyEvent(QAccessibleEvent *event)
191 {
192 QAccessibleEvent *ev;
193 if (event->type() == QAccessible::StateChanged) {
194 if (event->object())
195 ev = new QAccessibleStateChangeEvent(event->object(),
196 static_cast<QAccessibleStateChangeEvent*>(event)->changedStates());
197 else
198 ev = new QAccessibleStateChangeEvent(event->accessibleInterface(),
199 static_cast<QAccessibleStateChangeEvent*>(event)->changedStates());
200 } else if (event->type() == QAccessible::TextCaretMoved) {
201 if (event->object())
202 ev = new QAccessibleTextCursorEvent(event->object(), static_cast<QAccessibleTextCursorEvent*>(event)->cursorPosition());
203 else
204 ev = new QAccessibleTextCursorEvent(event->accessibleInterface(), static_cast<QAccessibleTextCursorEvent*>(event)->cursorPosition());
205 } else if (event->type() == QAccessible::TextSelectionChanged) {
206 const QAccessibleTextSelectionEvent *original = static_cast<QAccessibleTextSelectionEvent*>(event);
207 QAccessibleTextSelectionEvent *sel;
208 if (event->object())
209 sel = new QAccessibleTextSelectionEvent(event->object(), original->selectionStart(), original->selectionEnd());
210 else
211 sel = new QAccessibleTextSelectionEvent(event->accessibleInterface(), original->selectionStart(), original->selectionEnd());
212 sel->setCursorPosition(original->cursorPosition());
213 ev = sel;
214 } else if (event->type() == QAccessible::TextInserted) {
215 const QAccessibleTextInsertEvent *original = static_cast<QAccessibleTextInsertEvent*>(event);
216 QAccessibleTextInsertEvent *ins;
217 if (original->object())
218 ins = new QAccessibleTextInsertEvent(event->object(), original->changePosition(), original->textInserted());
219 else
220 ins = new QAccessibleTextInsertEvent(event->accessibleInterface(), original->changePosition(), original->textInserted());
221 ins->setCursorPosition(original->cursorPosition());
222 ev = ins;
223 } else if (event->type() == QAccessible::TextRemoved) {
224 const QAccessibleTextRemoveEvent *original = static_cast<QAccessibleTextRemoveEvent*>(event);
225 QAccessibleTextRemoveEvent *rem;
226 if (event->object())
227 rem = new QAccessibleTextRemoveEvent(event->object(), original->changePosition(), original->textRemoved());
228 else
229 rem = new QAccessibleTextRemoveEvent(event->accessibleInterface(), original->changePosition(), original->textRemoved());
230 rem->setCursorPosition(original->cursorPosition());
231 ev = rem;
232 } else if (event->type() == QAccessible::TextUpdated) {
233 const QAccessibleTextUpdateEvent *original = static_cast<QAccessibleTextUpdateEvent*>(event);
234 QAccessibleTextUpdateEvent *upd;
235 if (event->object())
236 upd = new QAccessibleTextUpdateEvent(event->object(), original->changePosition(), original->textRemoved(), original->textInserted());
237 else
238 upd = new QAccessibleTextUpdateEvent(event->accessibleInterface(), original->changePosition(), original->textRemoved(), original->textInserted());
239 upd->setCursorPosition(original->cursorPosition());
240 ev = upd;
241 } else if (event->type() == QAccessible::ValueChanged) {
242 if (event->object())
243 ev = new QAccessibleValueChangeEvent(event->object(), static_cast<QAccessibleValueChangeEvent*>(event)->value());
244 else
245 ev = new QAccessibleValueChangeEvent(event->accessibleInterface(), static_cast<QAccessibleValueChangeEvent*>(event)->value());
246 } else if (event->type() == QAccessible::TableModelChanged) {
247 QAccessibleTableModelChangeEvent *oldEvent = static_cast<QAccessibleTableModelChangeEvent*>(event);
248 QAccessibleTableModelChangeEvent *newEvent;
249 if (event->object())
250 newEvent = new QAccessibleTableModelChangeEvent(event->object(), oldEvent->modelChangeType());
251 else
252 newEvent = new QAccessibleTableModelChangeEvent(event->accessibleInterface(), oldEvent->modelChangeType());
253 newEvent->setFirstRow(oldEvent->firstRow());
254 newEvent->setFirstColumn(oldEvent->firstColumn());
255 newEvent->setLastRow(oldEvent->lastRow());
256 newEvent->setLastColumn(oldEvent->lastColumn());
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 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
287 << " not found at head of event list of size " << haystack.size() << " :";
288 for (const QAccessibleEvent *e : haystack)
289 str << ' ' << *e;
290 return rc;
291 }
292
293};
294
295QT_END_NAMESPACE
296
297#endif // QT_CONFIG(accessibility)
298#endif // QTESTACCESSIBLE_H
299