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 QtQuick module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
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 General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtTest/qtest.h> |
30 | #include <QtQuick/qquickwindow.h> |
31 | #include <QtQml/qqmlapplicationengine.h> |
32 | #include <QtQuick/qquickitem.h> |
33 | #include <QtTest/qsignalspy.h> |
34 | #ifdef QT_QUICKWIDGETS_LIB |
35 | #include <QtQuickWidgets/qquickwidget.h> |
36 | #endif |
37 | |
38 | #include "../../shared/util.h" |
39 | |
40 | class tst_QQuickShortcut : public QQmlDataTest |
41 | { |
42 | Q_OBJECT |
43 | |
44 | private slots: |
45 | void standardShortcuts_data(); |
46 | void standardShortcuts(); |
47 | void shortcuts_data(); |
48 | void shortcuts(); |
49 | void sequence_data(); |
50 | void sequence(); |
51 | void context_data(); |
52 | void context(); |
53 | void contextChange_data(); |
54 | void contextChange(); |
55 | void matcher_data(); |
56 | void matcher(); |
57 | void multiple_data(); |
58 | void multiple(); |
59 | #ifdef QT_QUICKWIDGETS_LIB |
60 | void renderControlShortcuts_data(); |
61 | void renderControlShortcuts(); |
62 | #endif |
63 | }; |
64 | |
65 | Q_DECLARE_METATYPE(Qt::Key) |
66 | Q_DECLARE_METATYPE(Qt::KeyboardModifiers) |
67 | |
68 | static const bool EnabledShortcut = true; |
69 | static const bool DisabledShortcut = false; |
70 | |
71 | static QVariant shortcutMap(const QVariant &sequence, Qt::ShortcutContext context, bool enabled = true, bool autoRepeat = true) |
72 | { |
73 | QVariantMap s; |
74 | s["sequence"] = sequence; |
75 | s["enabled"] = enabled; |
76 | s["context"] = context; |
77 | s["autoRepeat"] = autoRepeat; |
78 | return s; |
79 | } |
80 | |
81 | static QVariant shortcutMap(const QVariant &key, bool enabled = true, bool autoRepeat = true) |
82 | { |
83 | return shortcutMap(sequence: key, context: Qt::WindowShortcut, enabled, autoRepeat); |
84 | } |
85 | |
86 | void tst_QQuickShortcut::standardShortcuts_data() |
87 | { |
88 | QTest::addColumn<QKeySequence::StandardKey>(name: "standardKey"); |
89 | QTest::newRow(dataTag: "Close") << QKeySequence::Close; |
90 | QTest::newRow(dataTag: "Cut") << QKeySequence::Cut; |
91 | QTest::newRow(dataTag: "NextChild") << QKeySequence::NextChild; |
92 | QTest::newRow(dataTag: "PreviousChild") << QKeySequence::PreviousChild; |
93 | QTest::newRow(dataTag: "FindNext") << QKeySequence::FindNext; |
94 | QTest::newRow(dataTag: "FindPrevious") << QKeySequence::FindPrevious; |
95 | QTest::newRow(dataTag: "FullScreen") << QKeySequence::FullScreen; |
96 | } |
97 | |
98 | void tst_QQuickShortcut::standardShortcuts() |
99 | { |
100 | QFETCH(QKeySequence::StandardKey, standardKey); |
101 | |
102 | QQmlApplicationEngine engine; |
103 | |
104 | engine.load(url: testFileUrl(fileName: "multiple.qml")); |
105 | QQuickWindow *window = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 0)); |
106 | QVERIFY(window); |
107 | window->show(); |
108 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
109 | window->requestActivate(); |
110 | QVERIFY(QTest::qWaitForWindowActive(window)); |
111 | |
112 | QObject *shortcut = window->property(name: "shortcut").value<QObject *>(); |
113 | QVERIFY(shortcut); |
114 | |
115 | // create list of shortcuts |
116 | QVariantList shortcuts; |
117 | shortcuts.push_back(t: standardKey); |
118 | shortcut->setProperty(name: "sequences", value: shortcuts); |
119 | |
120 | // test all: |
121 | QList<QKeySequence> allsequences = QKeySequence::keyBindings(key: standardKey); |
122 | for (const QKeySequence &s : allsequences) { |
123 | window->setProperty(name: "activated", value: false); |
124 | QTest::keySequence(window, keySequence: s); |
125 | QCOMPARE(window->property("activated").toBool(), true); |
126 | } |
127 | } |
128 | |
129 | void tst_QQuickShortcut::shortcuts_data() |
130 | { |
131 | QTest::addColumn<QVariantList>(name: "shortcuts"); |
132 | QTest::addColumn<Qt::Key>(name: "key"); |
133 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers"); |
134 | QTest::addColumn<QString>(name: "activatedShortcut"); |
135 | QTest::addColumn<QString>(name: "ambiguousShortcut"); |
136 | |
137 | QVariantList shortcuts; |
138 | shortcuts << shortcutMap(key: "M") |
139 | << shortcutMap(key: "Alt+M") |
140 | << shortcutMap(key: "Ctrl+M") |
141 | << shortcutMap(key: "Shift+M") |
142 | << shortcutMap(key: "Ctrl+Alt+M") |
143 | << shortcutMap(key: "+") |
144 | << shortcutMap(key: "F1") |
145 | << shortcutMap(key: "Shift+F1"); |
146 | |
147 | QTest::newRow(dataTag: "M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::NoModifier) << "M"<< ""; |
148 | QTest::newRow(dataTag: "Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::AltModifier) << "Alt+M"<< ""; |
149 | QTest::newRow(dataTag: "Ctrl+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier) << "Ctrl+M"<< ""; |
150 | QTest::newRow(dataTag: "Shift+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Shift+M"<< ""; |
151 | QTest::newRow(dataTag: "Ctrl+Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier) << "Ctrl+Alt+M"<< ""; |
152 | QTest::newRow(dataTag: "+") << shortcuts << Qt::Key_Plus << Qt::KeyboardModifiers(Qt::NoModifier) << "+"<< ""; |
153 | QTest::newRow(dataTag: "F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::NoModifier) << "F1"<< ""; |
154 | QTest::newRow(dataTag: "Shift+F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Shift+F1"<< ""; |
155 | |
156 | // ambiguous |
157 | shortcuts << shortcutMap(key: "M") |
158 | << shortcutMap(key: "Alt+M") |
159 | << shortcutMap(key: "Ctrl+M") |
160 | << shortcutMap(key: "Shift+M") |
161 | << shortcutMap(key: "Ctrl+Alt+M") |
162 | << shortcutMap(key: "+") |
163 | << shortcutMap(key: "F1") |
164 | << shortcutMap(key: "Shift+F1"); |
165 | |
166 | QTest::newRow(dataTag: "?M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< "M"; |
167 | QTest::newRow(dataTag: "?Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::AltModifier) << ""<< "Alt+M"; |
168 | QTest::newRow(dataTag: "?Ctrl+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier) << ""<< "Ctrl+M"; |
169 | QTest::newRow(dataTag: "?Shift+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ShiftModifier) << ""<< "Shift+M"; |
170 | QTest::newRow(dataTag: "?Ctrl+Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier) << ""<< "Ctrl+Alt+M"; |
171 | QTest::newRow(dataTag: "?+") << shortcuts << Qt::Key_Plus << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< "+"; |
172 | QTest::newRow(dataTag: "?F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< "F1"; |
173 | QTest::newRow(dataTag: "?Shift+F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::ShiftModifier) << ""<< "Shift+F1"; |
174 | |
175 | // disabled |
176 | shortcuts.clear(); |
177 | shortcuts << shortcutMap(key: "M", enabled: DisabledShortcut) |
178 | << shortcutMap(key: "Alt+M", enabled: DisabledShortcut) |
179 | << shortcutMap(key: "Ctrl+M", enabled: DisabledShortcut) |
180 | << shortcutMap(key: "Shift+M", enabled: DisabledShortcut) |
181 | << shortcutMap(key: "Ctrl+Alt+M", enabled: DisabledShortcut) |
182 | << shortcutMap(key: "+", enabled: DisabledShortcut) |
183 | << shortcutMap(key: "F1", enabled: DisabledShortcut) |
184 | << shortcutMap(key: "Shift+F1", enabled: DisabledShortcut); |
185 | |
186 | QTest::newRow(dataTag: "!M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< ""; |
187 | QTest::newRow(dataTag: "!Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::AltModifier) << ""<< ""; |
188 | QTest::newRow(dataTag: "!Ctrl+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier) << ""<< ""; |
189 | QTest::newRow(dataTag: "!Shift+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ShiftModifier) << ""<< ""; |
190 | QTest::newRow(dataTag: "!Ctrl+Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier) << ""<< ""; |
191 | QTest::newRow(dataTag: "!+") << shortcuts << Qt::Key_Plus << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< ""; |
192 | QTest::newRow(dataTag: "!F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< ""; |
193 | QTest::newRow(dataTag: "!Shift+F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::ShiftModifier) << ""<< ""; |
194 | |
195 | // unambigous because others disabled |
196 | shortcuts << shortcutMap(key: "M") |
197 | << shortcutMap(key: "Alt+M") |
198 | << shortcutMap(key: "Ctrl+M") |
199 | << shortcutMap(key: "Shift+M") |
200 | << shortcutMap(key: "Ctrl+Alt+M") |
201 | << shortcutMap(key: "+") |
202 | << shortcutMap(key: "F1") |
203 | << shortcutMap(key: "Shift+F1"); |
204 | |
205 | QTest::newRow(dataTag: "/M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::NoModifier) << "M"<< ""; |
206 | QTest::newRow(dataTag: "/Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::AltModifier) << "Alt+M"<< ""; |
207 | QTest::newRow(dataTag: "/Ctrl+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier) << "Ctrl+M"<< ""; |
208 | QTest::newRow(dataTag: "/Shift+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Shift+M"<< ""; |
209 | QTest::newRow(dataTag: "/Ctrl+Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier) << "Ctrl+Alt+M"<< ""; |
210 | QTest::newRow(dataTag: "/+") << shortcuts << Qt::Key_Plus << Qt::KeyboardModifiers(Qt::NoModifier) << "+"<< ""; |
211 | QTest::newRow(dataTag: "/F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::NoModifier) << "F1"<< ""; |
212 | QTest::newRow(dataTag: "/Shift+F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Shift+F1"<< ""; |
213 | } |
214 | |
215 | void tst_QQuickShortcut::shortcuts() |
216 | { |
217 | QFETCH(QVariantList, shortcuts); |
218 | QFETCH(Qt::Key, key); |
219 | QFETCH(Qt::KeyboardModifiers, modifiers); |
220 | QFETCH(QString, activatedShortcut); |
221 | QFETCH(QString, ambiguousShortcut); |
222 | |
223 | QQmlApplicationEngine engine; |
224 | engine.load(url: testFileUrl(fileName: "shortcuts.qml")); |
225 | |
226 | QQuickWindow *window = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 0)); |
227 | QVERIFY(window); |
228 | window->show(); |
229 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
230 | window->requestActivate(); |
231 | QVERIFY(QTest::qWaitForWindowActive(window)); |
232 | |
233 | window->setProperty(name: "shortcuts", value: shortcuts); |
234 | |
235 | QTest::keyPress(window, key, modifier: modifiers); |
236 | QTest::keyRelease(window, key, modifier: modifiers); |
237 | QCOMPARE(window->property("activatedShortcut").toString(), activatedShortcut); |
238 | QCOMPARE(window->property("ambiguousShortcut").toString(), ambiguousShortcut); |
239 | } |
240 | |
241 | void tst_QQuickShortcut::sequence_data() |
242 | { |
243 | QTest::addColumn<QVariantList>(name: "shortcuts"); |
244 | QTest::addColumn<Qt::Key>(name: "key1"); |
245 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers1"); |
246 | QTest::addColumn<Qt::Key>(name: "key2"); |
247 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers2"); |
248 | QTest::addColumn<Qt::Key>(name: "key3"); |
249 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers3"); |
250 | QTest::addColumn<Qt::Key>(name: "key4"); |
251 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers4"); |
252 | QTest::addColumn<QString>(name: "activatedShortcut"); |
253 | QTest::addColumn<QString>(name: "ambiguousShortcut"); |
254 | |
255 | QVariantList shortcuts; |
256 | shortcuts << shortcutMap(key: "Escape,W") |
257 | << shortcutMap(key: "Ctrl+X,Ctrl+C") |
258 | << shortcutMap(key: "Shift+Ctrl+4,Space") |
259 | << shortcutMap(key: "Alt+T,Ctrl+R,Shift+J,H"); |
260 | |
261 | QTest::newRow(dataTag: "Escape,W") << shortcuts << Qt::Key_Escape << Qt::KeyboardModifiers(Qt::NoModifier) |
262 | << Qt::Key_W << Qt::KeyboardModifiers(Qt::NoModifier) |
263 | << Qt::Key(0) << Qt::KeyboardModifiers(Qt::NoModifier) |
264 | << Qt::Key(0) << Qt::KeyboardModifiers(Qt::NoModifier) |
265 | << "Escape,W"<< ""; |
266 | |
267 | QTest::newRow(dataTag: "Ctrl+X,Ctrl+C") << shortcuts << Qt::Key_X << Qt::KeyboardModifiers(Qt::ControlModifier) |
268 | << Qt::Key_C << Qt::KeyboardModifiers(Qt::ControlModifier) |
269 | << Qt::Key(0) << Qt::KeyboardModifiers(Qt::NoModifier) |
270 | << Qt::Key(0) << Qt::KeyboardModifiers(Qt::NoModifier) |
271 | << "Ctrl+X,Ctrl+C"<< ""; |
272 | |
273 | QTest::newRow(dataTag: "Shift+Ctrl+4,Space") << shortcuts << Qt::Key_4 << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::ShiftModifier) |
274 | << Qt::Key_Space << Qt::KeyboardModifiers(Qt::NoModifier) |
275 | << Qt::Key(0) << Qt::KeyboardModifiers(Qt::NoModifier) |
276 | << Qt::Key(0) << Qt::KeyboardModifiers(Qt::NoModifier) |
277 | << "Shift+Ctrl+4,Space"<< ""; |
278 | |
279 | QTest::newRow(dataTag: "Alt+T,Ctrl+R,Shift+J,H") << shortcuts << Qt::Key_T << Qt::KeyboardModifiers(Qt::AltModifier) |
280 | << Qt::Key_R << Qt::KeyboardModifiers(Qt::ControlModifier) |
281 | << Qt::Key_J << Qt::KeyboardModifiers(Qt::ShiftModifier) |
282 | << Qt::Key_H << Qt::KeyboardModifiers(Qt::NoModifier) |
283 | << "Alt+T,Ctrl+R,Shift+J,H"<< ""; |
284 | } |
285 | |
286 | void tst_QQuickShortcut::sequence() |
287 | { |
288 | QFETCH(QVariantList, shortcuts); |
289 | QFETCH(Qt::Key, key1); |
290 | QFETCH(Qt::KeyboardModifiers, modifiers1); |
291 | QFETCH(Qt::Key, key2); |
292 | QFETCH(Qt::KeyboardModifiers, modifiers2); |
293 | QFETCH(Qt::Key, key3); |
294 | QFETCH(Qt::KeyboardModifiers, modifiers3); |
295 | QFETCH(Qt::Key, key4); |
296 | QFETCH(Qt::KeyboardModifiers, modifiers4); |
297 | QFETCH(QString, activatedShortcut); |
298 | QFETCH(QString, ambiguousShortcut); |
299 | |
300 | QQmlApplicationEngine engine; |
301 | engine.load(url: testFileUrl(fileName: "shortcuts.qml")); |
302 | |
303 | QQuickWindow *window = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 0)); |
304 | QVERIFY(window); |
305 | window->show(); |
306 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
307 | window->requestActivate(); |
308 | QVERIFY(QTest::qWaitForWindowActive(window)); |
309 | |
310 | window->setProperty(name: "shortcuts", value: shortcuts); |
311 | |
312 | if (key1 != 0) { |
313 | QTest::keyPress(window, key: key1, modifier: modifiers1); |
314 | QTest::keyRelease(window, key: key1, modifier: modifiers1); |
315 | } |
316 | if (key2 != 0) { |
317 | QTest::keyPress(window, key: key2, modifier: modifiers2); |
318 | QTest::keyRelease(window, key: key2, modifier: modifiers2); |
319 | } |
320 | if (key3 != 0) { |
321 | QTest::keyPress(window, key: key3, modifier: modifiers3); |
322 | QTest::keyRelease(window, key: key3, modifier: modifiers3); |
323 | } |
324 | if (key4 != 0) { |
325 | QTest::keyPress(window, key: key4, modifier: modifiers4); |
326 | QTest::keyRelease(window, key: key4, modifier: modifiers4); |
327 | } |
328 | |
329 | QCOMPARE(window->property("activatedShortcut").toString(), activatedShortcut); |
330 | QCOMPARE(window->property("ambiguousShortcut").toString(), ambiguousShortcut); |
331 | } |
332 | |
333 | void tst_QQuickShortcut::context_data() |
334 | { |
335 | QTest::addColumn<Qt::Key>(name: "key"); |
336 | QTest::addColumn<QVariantList>(name: "activeWindowShortcuts"); |
337 | QTest::addColumn<QVariantList>(name: "inactiveWindowShortcuts"); |
338 | QTest::addColumn<QString>(name: "activeWindowActivatedShortcut"); |
339 | QTest::addColumn<QString>(name: "inactiveWindowActivatedShortcut"); |
340 | QTest::addColumn<QString>(name: "ambiguousShortcut"); |
341 | |
342 | // APP: F1,(F2),F3,(F4) / WND: F7,(F8),F9,(F10) |
343 | QVariantList activeWindowShortcuts; |
344 | activeWindowShortcuts << shortcutMap(sequence: "F1", context: Qt::ApplicationShortcut, enabled: EnabledShortcut) |
345 | << shortcutMap(sequence: "F2", context: Qt::ApplicationShortcut, enabled: DisabledShortcut) |
346 | << shortcutMap(sequence: "F3", context: Qt::ApplicationShortcut, enabled: EnabledShortcut) |
347 | << shortcutMap(sequence: "F4", context: Qt::ApplicationShortcut, enabled: DisabledShortcut) |
348 | << shortcutMap(sequence: "F7", context: Qt::WindowShortcut, enabled: EnabledShortcut) |
349 | << shortcutMap(sequence: "F8", context: Qt::WindowShortcut, enabled: DisabledShortcut) |
350 | << shortcutMap(sequence: "F9", context: Qt::WindowShortcut, enabled: EnabledShortcut) |
351 | << shortcutMap(sequence: "F10", context: Qt::WindowShortcut, enabled: DisabledShortcut); |
352 | |
353 | // APP: F3,(F4),F5,(F6) / WND: F9,(F10),F11(F12) |
354 | QVariantList inactiveWindowShortcuts; |
355 | inactiveWindowShortcuts << shortcutMap(sequence: "F3", context: Qt::ApplicationShortcut, enabled: EnabledShortcut) |
356 | << shortcutMap(sequence: "F4", context: Qt::ApplicationShortcut, enabled: DisabledShortcut) |
357 | << shortcutMap(sequence: "F5", context: Qt::ApplicationShortcut, enabled: EnabledShortcut) |
358 | << shortcutMap(sequence: "F6", context: Qt::ApplicationShortcut, enabled: DisabledShortcut) |
359 | << shortcutMap(sequence: "F9", context: Qt::WindowShortcut, enabled: EnabledShortcut) |
360 | << shortcutMap(sequence: "F10", context: Qt::WindowShortcut, enabled: DisabledShortcut) |
361 | << shortcutMap(sequence: "F11", context: Qt::WindowShortcut, enabled: EnabledShortcut) |
362 | << shortcutMap(sequence: "F12", context: Qt::WindowShortcut, enabled: DisabledShortcut); |
363 | |
364 | // APP |
365 | QTest::newRow(dataTag: "F1") << Qt::Key_F1 << activeWindowShortcuts << inactiveWindowShortcuts << "F1"<< ""<< ""; |
366 | QTest::newRow(dataTag: "F2") << Qt::Key_F2 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< ""<< ""; |
367 | QTest::newRow(dataTag: "F3") << Qt::Key_F3 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< ""<< "F3"; |
368 | QTest::newRow(dataTag: "F4") << Qt::Key_F4 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< ""<< ""; |
369 | QTest::newRow(dataTag: "F5") << Qt::Key_F5 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< "F5"<< ""; |
370 | QTest::newRow(dataTag: "F6") << Qt::Key_F6 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< ""<< ""; |
371 | |
372 | // WND |
373 | QTest::newRow(dataTag: "F7") << Qt::Key_F7 << activeWindowShortcuts << inactiveWindowShortcuts << "F7"<< ""<< ""; |
374 | QTest::newRow(dataTag: "F8") << Qt::Key_F8 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< ""<< ""; |
375 | QTest::newRow(dataTag: "F9") << Qt::Key_F9 << activeWindowShortcuts << inactiveWindowShortcuts << "F9"<< ""<< ""; |
376 | QTest::newRow(dataTag: "F10") << Qt::Key_F10 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< ""<< ""; |
377 | QTest::newRow(dataTag: "F11") << Qt::Key_F11 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< ""<< ""; |
378 | QTest::newRow(dataTag: "F12") << Qt::Key_F12 << activeWindowShortcuts << inactiveWindowShortcuts << ""<< ""<< ""; |
379 | } |
380 | |
381 | void tst_QQuickShortcut::context() |
382 | { |
383 | QFETCH(Qt::Key, key); |
384 | QFETCH(QVariantList, activeWindowShortcuts); |
385 | QFETCH(QVariantList, inactiveWindowShortcuts); |
386 | QFETCH(QString, activeWindowActivatedShortcut); |
387 | QFETCH(QString, inactiveWindowActivatedShortcut); |
388 | QFETCH(QString, ambiguousShortcut); |
389 | |
390 | QQmlApplicationEngine engine; |
391 | |
392 | engine.load(url: testFileUrl(fileName: "shortcuts.qml")); |
393 | QQuickWindow *inactiveWindow = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 0)); |
394 | QVERIFY(inactiveWindow); |
395 | inactiveWindow->show(); |
396 | QVERIFY(QTest::qWaitForWindowExposed(inactiveWindow)); |
397 | inactiveWindow->setProperty(name: "shortcuts", value: inactiveWindowShortcuts); |
398 | |
399 | engine.load(url: testFileUrl(fileName: "shortcuts.qml")); |
400 | QQuickWindow *activeWindow = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 1)); |
401 | QVERIFY(activeWindow); |
402 | activeWindow->show(); |
403 | QVERIFY(QTest::qWaitForWindowExposed(activeWindow)); |
404 | activeWindow->requestActivate(); |
405 | QVERIFY(QTest::qWaitForWindowActive(activeWindow)); |
406 | activeWindow->setProperty(name: "shortcuts", value: activeWindowShortcuts); |
407 | |
408 | QTest::keyPress(window: activeWindow, key); |
409 | QTest::keyRelease(window: activeWindow, key); |
410 | |
411 | QCOMPARE(activeWindow->property("activatedShortcut").toString(), activeWindowActivatedShortcut); |
412 | QCOMPARE(inactiveWindow->property("activatedShortcut").toString(), inactiveWindowActivatedShortcut); |
413 | QVERIFY(activeWindow->property("ambiguousShortcut").toString() == ambiguousShortcut |
414 | || inactiveWindow->property("ambiguousShortcut").toString() == ambiguousShortcut); |
415 | } |
416 | |
417 | typedef bool (*ShortcutContextMatcher)(QObject *, Qt::ShortcutContext); |
418 | extern ShortcutContextMatcher qt_quick_shortcut_context_matcher(); |
419 | extern void qt_quick_set_shortcut_context_matcher(ShortcutContextMatcher matcher); |
420 | |
421 | static ShortcutContextMatcher lastMatcher = nullptr; |
422 | |
423 | static bool trueMatcher(QObject *, Qt::ShortcutContext) |
424 | { |
425 | lastMatcher = trueMatcher; |
426 | return true; |
427 | } |
428 | |
429 | static bool falseMatcher(QObject *, Qt::ShortcutContext) |
430 | { |
431 | lastMatcher = falseMatcher; |
432 | return false; |
433 | } |
434 | |
435 | Q_DECLARE_METATYPE(ShortcutContextMatcher) |
436 | |
437 | void tst_QQuickShortcut::matcher_data() |
438 | { |
439 | QTest::addColumn<ShortcutContextMatcher>(name: "matcher"); |
440 | QTest::addColumn<Qt::Key>(name: "key"); |
441 | QTest::addColumn<QVariant>(name: "shortcut"); |
442 | QTest::addColumn<QString>(name: "activatedShortcut"); |
443 | |
444 | ShortcutContextMatcher tm = trueMatcher; |
445 | ShortcutContextMatcher fm = falseMatcher; |
446 | |
447 | QTest::newRow(dataTag: "F1") << tm << Qt::Key_F1 << shortcutMap(sequence: "F1", context: Qt::ApplicationShortcut) << "F1"; |
448 | QTest::newRow(dataTag: "F2") << fm << Qt::Key_F2 << shortcutMap(sequence: "F2", context: Qt::ApplicationShortcut) << ""; |
449 | } |
450 | |
451 | void tst_QQuickShortcut::matcher() |
452 | { |
453 | QFETCH(ShortcutContextMatcher, matcher); |
454 | QFETCH(Qt::Key, key); |
455 | QFETCH(QVariant, shortcut); |
456 | QFETCH(QString, activatedShortcut); |
457 | |
458 | ShortcutContextMatcher defaultMatcher = qt_quick_shortcut_context_matcher(); |
459 | QVERIFY(defaultMatcher); |
460 | |
461 | qt_quick_set_shortcut_context_matcher(matcher); |
462 | QVERIFY(qt_quick_shortcut_context_matcher() == matcher); |
463 | |
464 | QQmlApplicationEngine engine(testFileUrl(fileName: "shortcuts.qml")); |
465 | QQuickWindow *window = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 0)); |
466 | QVERIFY(window); |
467 | window->show(); |
468 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
469 | window->requestActivate(); |
470 | QVERIFY(QTest::qWaitForWindowActive(window)); |
471 | |
472 | window->setProperty(name: "shortcuts", value: QVariantList() << shortcut); |
473 | QTest::keyClick(window, key); |
474 | |
475 | QVERIFY(lastMatcher == matcher); |
476 | QCOMPARE(window->property("activatedShortcut").toString(), activatedShortcut); |
477 | |
478 | qt_quick_set_shortcut_context_matcher(matcher: defaultMatcher); |
479 | } |
480 | |
481 | void tst_QQuickShortcut::multiple_data() |
482 | { |
483 | QTest::addColumn<QStringList>(name: "sequences"); |
484 | QTest::addColumn<Qt::Key>(name: "key"); |
485 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers"); |
486 | QTest::addColumn<bool>(name: "enabled"); |
487 | QTest::addColumn<bool>(name: "activated"); |
488 | |
489 | // first |
490 | QTest::newRow(dataTag: "Ctrl+X,(Shift+Del)") << (QStringList() << "Ctrl+X"<< "Shift+Del") << Qt::Key_X << Qt::KeyboardModifiers(Qt::ControlModifier) << true << true; |
491 | // second |
492 | QTest::newRow(dataTag: "(Ctrl+X),Shift+Del") << (QStringList() << "Ctrl+X"<< "Shift+Del") << Qt::Key_Delete << Qt::KeyboardModifiers(Qt::ShiftModifier) << true << true; |
493 | // disabled |
494 | QTest::newRow(dataTag: "(Ctrl+X,Shift+Del)") << (QStringList() << "Ctrl+X"<< "Shift+Del") << Qt::Key_X << Qt::KeyboardModifiers(Qt::ControlModifier) << false << false; |
495 | } |
496 | |
497 | void tst_QQuickShortcut::multiple() |
498 | { |
499 | QFETCH(QStringList, sequences); |
500 | QFETCH(Qt::Key, key); |
501 | QFETCH(Qt::KeyboardModifiers, modifiers); |
502 | QFETCH(bool, enabled); |
503 | QFETCH(bool, activated); |
504 | |
505 | QQmlApplicationEngine engine; |
506 | |
507 | engine.load(url: testFileUrl(fileName: "multiple.qml")); |
508 | QQuickWindow *window = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 0)); |
509 | QVERIFY(window); |
510 | window->show(); |
511 | QVERIFY(QTest::qWaitForWindowExposed(window)); |
512 | window->requestActivate(); |
513 | QVERIFY(QTest::qWaitForWindowActive(window)); |
514 | |
515 | QObject *shortcut = window->property(name: "shortcut").value<QObject *>(); |
516 | QVERIFY(shortcut); |
517 | |
518 | shortcut->setProperty(name: "enabled", value: enabled); |
519 | shortcut->setProperty(name: "sequences", value: sequences); |
520 | |
521 | QTest::keyPress(window, key, modifier: modifiers); |
522 | QTest::keyRelease(window, key, modifier: modifiers); |
523 | |
524 | QCOMPARE(window->property("activated").toBool(), activated); |
525 | |
526 | // check it still works after rotating the sequences |
527 | QStringList rotatedSequences; |
528 | for (int i = 1; i < sequences.size(); ++i) |
529 | rotatedSequences.push_back(t: sequences[i]); |
530 | if (sequences.size()) |
531 | rotatedSequences.push_back(t: sequences[0]); |
532 | |
533 | window->setProperty(name: "activated", value: false); |
534 | shortcut->setProperty(name: "sequences", value: rotatedSequences); |
535 | |
536 | QTest::keyPress(window, key, modifier: modifiers); |
537 | QTest::keyRelease(window, key, modifier: modifiers); |
538 | QCOMPARE(window->property("activated").toBool(), activated); |
539 | |
540 | // check setting to no shortcuts |
541 | QStringList emptySequence; |
542 | |
543 | window->setProperty(name: "activated", value: false); |
544 | shortcut->setProperty(name: "sequences", value: emptySequence); |
545 | |
546 | QTest::keyPress(window, key, modifier: modifiers); |
547 | QTest::keyRelease(window, key, modifier: modifiers); |
548 | QCOMPARE(window->property("activated").toBool(), false); |
549 | } |
550 | |
551 | void tst_QQuickShortcut::contextChange_data() |
552 | { |
553 | multiple_data(); |
554 | } |
555 | void tst_QQuickShortcut::contextChange() |
556 | { |
557 | QFETCH(QStringList, sequences); |
558 | QFETCH(Qt::Key, key); |
559 | QFETCH(Qt::KeyboardModifiers, modifiers); |
560 | QFETCH(bool, enabled); |
561 | QFETCH(bool, activated); |
562 | |
563 | QQmlApplicationEngine engine; |
564 | |
565 | engine.load(url: testFileUrl(fileName: "multiple.qml")); |
566 | QQuickWindow *inactivewindow = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 0)); |
567 | QVERIFY(inactivewindow); |
568 | inactivewindow->show(); |
569 | QVERIFY(QTest::qWaitForWindowExposed(inactivewindow)); |
570 | |
571 | QObject *shortcut = inactivewindow->property(name: "shortcut").value<QObject *>(); |
572 | QVERIFY(shortcut); |
573 | |
574 | shortcut->setProperty(name: "enabled", value: enabled); |
575 | shortcut->setProperty(name: "sequences", value: sequences); |
576 | shortcut->setProperty(name: "context", value: Qt::WindowShortcut); |
577 | |
578 | engine.load(url: testFileUrl(fileName: "multiple.qml")); |
579 | QQuickWindow *activewindow = qobject_cast<QQuickWindow *>(object: engine.rootObjects().value(i: 1)); |
580 | QVERIFY(activewindow); |
581 | activewindow->show(); |
582 | QVERIFY(QTest::qWaitForWindowExposed(activewindow)); |
583 | |
584 | QTest::keyPress(window: activewindow, key, modifier: modifiers); |
585 | QCOMPARE(inactivewindow->property("activated").toBool(), false); |
586 | |
587 | shortcut->setProperty(name: "context", value: Qt::ApplicationShortcut); |
588 | |
589 | QTest::keyPress(window: activewindow, key, modifier: modifiers); |
590 | QCOMPARE(inactivewindow->property("activated").toBool(), activated); |
591 | } |
592 | |
593 | #ifdef QT_QUICKWIDGETS_LIB |
594 | void tst_QQuickShortcut::renderControlShortcuts_data() |
595 | { |
596 | QTest::addColumn<QVariantList>(name: "shortcuts"); |
597 | QTest::addColumn<Qt::Key>(name: "key"); |
598 | QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers"); |
599 | QTest::addColumn<QString>(name: "activatedShortcut"); |
600 | QTest::addColumn<QString>(name: "ambiguousShortcut"); |
601 | |
602 | QVariantList shortcuts; |
603 | shortcuts << shortcutMap(key: "M") |
604 | << shortcutMap(key: "Alt+M") |
605 | << shortcutMap(key: "Ctrl+M") |
606 | << shortcutMap(key: "Shift+M") |
607 | << shortcutMap(key: "Ctrl+Alt+M") |
608 | << shortcutMap(key: "+") |
609 | << shortcutMap(key: "F1") |
610 | << shortcutMap(key: "Shift+F1"); |
611 | |
612 | QTest::newRow(dataTag: "M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::NoModifier) << "M"<< ""; |
613 | QTest::newRow(dataTag: "Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::AltModifier) << "Alt+M"<< ""; |
614 | QTest::newRow(dataTag: "Ctrl+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier) << "Ctrl+M"<< ""; |
615 | QTest::newRow(dataTag: "Shift+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Shift+M"<< ""; |
616 | QTest::newRow(dataTag: "Ctrl+Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier) << "Ctrl+Alt+M"<< ""; |
617 | QTest::newRow(dataTag: "+") << shortcuts << Qt::Key_Plus << Qt::KeyboardModifiers(Qt::NoModifier) << "+"<< ""; |
618 | QTest::newRow(dataTag: "F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::NoModifier) << "F1"<< ""; |
619 | QTest::newRow(dataTag: "Shift+F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Shift+F1"<< ""; |
620 | |
621 | // ambiguous |
622 | shortcuts << shortcutMap(key: "M") |
623 | << shortcutMap(key: "Alt+M") |
624 | << shortcutMap(key: "Ctrl+M") |
625 | << shortcutMap(key: "Shift+M") |
626 | << shortcutMap(key: "Ctrl+Alt+M") |
627 | << shortcutMap(key: "+") |
628 | << shortcutMap(key: "F1") |
629 | << shortcutMap(key: "Shift+F1"); |
630 | |
631 | QTest::newRow(dataTag: "?M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< "M"; |
632 | QTest::newRow(dataTag: "?Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::AltModifier) << ""<< "Alt+M"; |
633 | QTest::newRow(dataTag: "?Ctrl+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier) << ""<< "Ctrl+M"; |
634 | QTest::newRow(dataTag: "?Shift+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ShiftModifier) << ""<< "Shift+M"; |
635 | QTest::newRow(dataTag: "?Ctrl+Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier) << ""<< "Ctrl+Alt+M"; |
636 | QTest::newRow(dataTag: "?+") << shortcuts << Qt::Key_Plus << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< "+"; |
637 | QTest::newRow(dataTag: "?F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< "F1"; |
638 | QTest::newRow(dataTag: "?Shift+F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::ShiftModifier) << ""<< "Shift+F1"; |
639 | |
640 | // disabled |
641 | shortcuts.clear(); |
642 | shortcuts << shortcutMap(key: "M", enabled: DisabledShortcut) |
643 | << shortcutMap(key: "Alt+M", enabled: DisabledShortcut) |
644 | << shortcutMap(key: "Ctrl+M", enabled: DisabledShortcut) |
645 | << shortcutMap(key: "Shift+M", enabled: DisabledShortcut) |
646 | << shortcutMap(key: "Ctrl+Alt+M", enabled: DisabledShortcut) |
647 | << shortcutMap(key: "+", enabled: DisabledShortcut) |
648 | << shortcutMap(key: "F1", enabled: DisabledShortcut) |
649 | << shortcutMap(key: "Shift+F1", enabled: DisabledShortcut); |
650 | |
651 | QTest::newRow(dataTag: "!M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< ""; |
652 | QTest::newRow(dataTag: "!Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::AltModifier) << ""<< ""; |
653 | QTest::newRow(dataTag: "!Ctrl+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier) << ""<< ""; |
654 | QTest::newRow(dataTag: "!Shift+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ShiftModifier) << ""<< ""; |
655 | QTest::newRow(dataTag: "!Ctrl+Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier) << ""<< ""; |
656 | QTest::newRow(dataTag: "!+") << shortcuts << Qt::Key_Plus << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< ""; |
657 | QTest::newRow(dataTag: "!F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::NoModifier) << ""<< ""; |
658 | QTest::newRow(dataTag: "!Shift+F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::ShiftModifier) << ""<< ""; |
659 | |
660 | // unambigous because others disabled |
661 | shortcuts << shortcutMap(key: "M") |
662 | << shortcutMap(key: "Alt+M") |
663 | << shortcutMap(key: "Ctrl+M") |
664 | << shortcutMap(key: "Shift+M") |
665 | << shortcutMap(key: "Ctrl+Alt+M") |
666 | << shortcutMap(key: "+") |
667 | << shortcutMap(key: "F1") |
668 | << shortcutMap(key: "Shift+F1"); |
669 | |
670 | QTest::newRow(dataTag: "/M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::NoModifier) << "M"<< ""; |
671 | QTest::newRow(dataTag: "/Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::AltModifier) << "Alt+M"<< ""; |
672 | QTest::newRow(dataTag: "/Ctrl+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier) << "Ctrl+M"<< ""; |
673 | QTest::newRow(dataTag: "/Shift+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Shift+M"<< ""; |
674 | QTest::newRow(dataTag: "/Ctrl+Alt+M") << shortcuts << Qt::Key_M << Qt::KeyboardModifiers(Qt::ControlModifier|Qt::AltModifier) << "Ctrl+Alt+M"<< ""; |
675 | QTest::newRow(dataTag: "/+") << shortcuts << Qt::Key_Plus << Qt::KeyboardModifiers(Qt::NoModifier) << "+"<< ""; |
676 | QTest::newRow(dataTag: "/F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::NoModifier) << "F1"<< ""; |
677 | QTest::newRow(dataTag: "/Shift+F1") << shortcuts << Qt::Key_F1 << Qt::KeyboardModifiers(Qt::ShiftModifier) << "Shift+F1"<< ""; |
678 | } |
679 | |
680 | void tst_QQuickShortcut::renderControlShortcuts() |
681 | { |
682 | QFETCH(QVariantList, shortcuts); |
683 | QFETCH(Qt::Key, key); |
684 | QFETCH(Qt::KeyboardModifiers, modifiers); |
685 | QFETCH(QString, activatedShortcut); |
686 | QFETCH(QString, ambiguousShortcut); |
687 | |
688 | QScopedPointer<QQuickWidget> quickWidget(new QQuickWidget); |
689 | quickWidget->resize(w: 300,h: 300); |
690 | |
691 | QSignalSpy spy(qApp, &QGuiApplication::focusObjectChanged); |
692 | |
693 | quickWidget->setSource(testFileUrl(fileName: "shortcutsRect.qml")); |
694 | quickWidget->show(); |
695 | |
696 | spy.wait(); |
697 | |
698 | QVERIFY(qobject_cast<QQuickWidget*>(qApp->focusObject()) == quickWidget.data()); |
699 | |
700 | QQuickItem* item = quickWidget->rootObject(); |
701 | item->setProperty(name: "shortcuts", value: shortcuts); |
702 | QTest::keyPress(window: quickWidget->quickWindow(), key, modifier: modifiers, delay: 1500); |
703 | QTest::keyRelease(window: quickWidget->quickWindow(), key, modifier: modifiers, delay: 1600); |
704 | QCOMPARE(item->property("activatedShortcut").toString(), activatedShortcut); |
705 | QCOMPARE(item->property("ambiguousShortcut").toString(), ambiguousShortcut); |
706 | } |
707 | #endif // QT_QUICKWIDGETS_LIB |
708 | |
709 | QTEST_MAIN(tst_QQuickShortcut) |
710 | |
711 | #include "tst_qquickshortcut.moc" |
712 |
Definitions
- tst_QQuickShortcut
- EnabledShortcut
- DisabledShortcut
- shortcutMap
- shortcutMap
- standardShortcuts_data
- standardShortcuts
- shortcuts_data
- shortcuts
- sequence_data
- sequence
- context_data
- context
- lastMatcher
- trueMatcher
- falseMatcher
- matcher_data
- matcher
- multiple_data
- multiple
- contextChange_data
- contextChange
- renderControlShortcuts_data
Learn Advanced QML with KDAB
Find out more