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 
62 QT_BEGIN_NAMESPACE
63 
64 
65 class QObject;
66 
67 // Use pointers since we subclass QAccessibleEvent
68 using EventList = QList<QAccessibleEvent*>;
69 
70 bool 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 
121 class QTestAccessibility
122 {
123 public:
initialize()124     static void initialize()
125     {
126         if (!instance()) {
127             instance() = new QTestAccessibility;
128             qAddPostRoutine(cleanup);
129         }
130     }
131 
cleanup()132     static void cleanup()
133     {
134         delete instance();
135         instance() = nullptr;
136     }
clearEvents()137     static void clearEvents() { eventList().clear(); }
events()138     static EventList events() { return eventList(); }
verifyEvent(QAccessibleEvent * ev)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     }
containsEvent(QAccessibleEvent * event)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 
161 private:
QTestAccessibility()162     QTestAccessibility()
163     {
164         QAccessible::installUpdateHandler(updateHandler);
165         QAccessible::installRootObjectHandler(rootObjectHandler);
166     }
167 
~QTestAccessibility()168     ~QTestAccessibility()
169     {
170         QAccessible::installUpdateHandler(nullptr);
171         QAccessible::installRootObjectHandler(nullptr);
172     }
173 
rootObjectHandler(QObject * object)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 
updateHandler(QAccessibleEvent * event)186     static void updateHandler(QAccessibleEvent *event)
187     {
188         eventList().append(copyEvent(event));
189     }
copyEvent(QAccessibleEvent * event)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 
eventList()268     static EventList &eventList()
269     {
270         static EventList list;
271         return list;
272     }
273 
instance()274     static QTestAccessibility *&instance()
275     {
276         static QTestAccessibility *ta = nullptr;
277         return ta;
278     }
279 
280 private:
msgAccessibilityEventListMismatch(const EventList & haystack,const QAccessibleEvent * needle)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 
295 QT_END_NAMESPACE
296 
297 #endif // QT_CONFIG(accessibility)
298 #endif // QTESTACCESSIBLE_H
299