1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2017 The Qt Company Ltd. |
4 | ** Contact: http://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the test suite of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL3$ |
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 http://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at http://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.LGPLv3 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.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 later as published by the Free |
28 | ** Software Foundation and appearing in the file LICENSE.GPL included in |
29 | ** the packaging of this file. Please review the following information to |
30 | ** ensure the GNU General Public License version 2.0 requirements will be |
31 | ** met: http://www.gnu.org/licenses/gpl-2.0.html. |
32 | ** |
33 | ** $QT_END_LICENSE$ |
34 | ** |
35 | ****************************************************************************/ |
36 | |
37 | #include <qtest.h> |
38 | #include <QtTest/QSignalSpy> |
39 | #include <QtQml/qqmlengine.h> |
40 | #include <QtQml/qqmlcomponent.h> |
41 | #include <QtQml/qqmlcontext.h> |
42 | #include <QtQuick/qquickview.h> |
43 | #include <QtQuick/private/qquickitem_p.h> |
44 | #include <QtQuickTemplates2/private/qquickcontrol_p.h> |
45 | #include <QtGui/private/qguiapplication_p.h> |
46 | #include <QtGui/qstylehints.h> |
47 | #include <QtGui/qtouchdevice.h> |
48 | #include "../shared/util.h" |
49 | #include "../shared/visualtestutil.h" |
50 | |
51 | using namespace QQuickVisualTestUtil; |
52 | |
53 | class tst_focus : public QQmlDataTest |
54 | { |
55 | Q_OBJECT |
56 | |
57 | private slots: |
58 | void initTestCase(); |
59 | |
60 | void navigation_data(); |
61 | void navigation(); |
62 | |
63 | void policy_data(); |
64 | void policy(); |
65 | |
66 | void reason_data(); |
67 | void reason(); |
68 | |
69 | void visualFocus(); |
70 | |
71 | void scope_data(); |
72 | void scope(); |
73 | }; |
74 | |
75 | void tst_focus::initTestCase() |
76 | { |
77 | QQmlDataTest::initTestCase(); |
78 | } |
79 | |
80 | void tst_focus::navigation_data() |
81 | { |
82 | QTest::addColumn<Qt::Key>(name: "key" ); |
83 | QTest::addColumn<QString>(name: "testFile" ); |
84 | QTest::addColumn<Qt::TabFocusBehavior>(name: "behavior" ); |
85 | QTest::addColumn<QStringList>(name: "order" ); |
86 | |
87 | QTest::newRow(dataTag: "tab-all-controls" ) << Qt::Key_Tab << QString("activeFocusOnTab.qml" ) << Qt::TabFocusAllControls << (QStringList() << "button2" << "checkbox" << "checkbox1" << "checkbox2" << "radiobutton" << "radiobutton1" << "radiobutton2" << "rangeslider.first" << "rangeslider.second" << "slider" << "spinbox" << "switch" << "tabbutton1" << "tabbutton2" << "textfield" << "toolbutton" << "textarea" << "button1" ); |
88 | QTest::newRow(dataTag: "backtab-all-controls" ) << Qt::Key_Backtab << QString("activeFocusOnTab.qml" ) << Qt::TabFocusAllControls << (QStringList() << "textarea" << "toolbutton" << "textfield" << "tabbutton2" << "tabbutton1" << "switch" << "spinbox" << "slider" << "rangeslider.second" << "rangeslider.first" << "radiobutton2" << "radiobutton1" << "radiobutton" << "checkbox2" << "checkbox1" << "checkbox" << "button2" << "button1" ); |
89 | |
90 | QTest::newRow(dataTag: "tab-text-controls" ) << Qt::Key_Tab << QString("activeFocusOnTab.qml" ) << Qt::TabFocusTextControls << (QStringList() << "spinbox" << "textfield" << "textarea" ); |
91 | QTest::newRow(dataTag: "backtab-text-controls" ) << Qt::Key_Backtab << QString("activeFocusOnTab.qml" ) << Qt::TabFocusTextControls << (QStringList() << "textarea" << "textfield" << "spinbox" ); |
92 | |
93 | QTest::newRow(dataTag: "key-up" ) << Qt::Key_Up << QString("keyNavigation.qml" ) << Qt::TabFocusAllControls << (QStringList() << "textarea" << "toolbutton" << "textfield" << "tabbutton2" << "tabbutton1" << "switch" << "slider" << "rangeslider.first" << "radiobutton2" << "radiobutton1" << "radiobutton" << "checkbox2" << "checkbox1" << "checkbox" << "button2" << "button1" ); |
94 | QTest::newRow(dataTag: "key-down" ) << Qt::Key_Down << QString("keyNavigation.qml" ) << Qt::TabFocusAllControls << (QStringList() << "button2" << "checkbox" << "checkbox1" << "checkbox2" << "radiobutton" << "radiobutton1" << "radiobutton2" << "rangeslider.first" << "slider" << "switch" << "tabbutton1" << "tabbutton2" << "textfield" << "toolbutton" << "textarea" << "button1" ); |
95 | QTest::newRow(dataTag: "key-left" ) << Qt::Key_Left << QString("keyNavigation.qml" ) << Qt::TabFocusAllControls << (QStringList() << "toolbutton" << "tabbutton2" << "tabbutton1" << "switch" << "spinbox" << "radiobutton2" << "radiobutton1" << "radiobutton" << "checkbox2" << "checkbox1" << "checkbox" << "button2" << "button1" ); |
96 | QTest::newRow(dataTag: "key-right" ) << Qt::Key_Right << QString("keyNavigation.qml" ) << Qt::TabFocusAllControls << (QStringList() << "button2" << "checkbox" << "checkbox1" << "checkbox2" << "radiobutton" << "radiobutton1" << "radiobutton2" << "spinbox" << "switch" << "tabbutton1" << "tabbutton2" << "toolbutton" << "button1" ); |
97 | } |
98 | |
99 | void tst_focus::navigation() |
100 | { |
101 | QFETCH(Qt::Key, key); |
102 | QFETCH(QString, testFile); |
103 | QFETCH(Qt::TabFocusBehavior, behavior); |
104 | QFETCH(QStringList, order); |
105 | |
106 | QGuiApplication::styleHints()->setTabFocusBehavior(behavior); |
107 | |
108 | QQuickView view; |
109 | view.contentItem()->setObjectName("contentItem" ); |
110 | |
111 | view.setSource(testFileUrl(fileName: testFile)); |
112 | view.show(); |
113 | view.requestActivate(); |
114 | QVERIFY(QTest::qWaitForWindowActive(&view)); |
115 | QVERIFY(QGuiApplication::focusWindow() == &view); |
116 | |
117 | for (const QString &name : qAsConst(t&: order)) { |
118 | QKeyEvent event(QEvent::KeyPress, key, Qt::NoModifier); |
119 | QGuiApplication::sendEvent(receiver: &view, event: &event); |
120 | QVERIFY(event.isAccepted()); |
121 | |
122 | QQuickItem *item = findItem<QQuickItem>(parent: view.rootObject(), objectName: name); |
123 | QVERIFY2(item, qPrintable(name)); |
124 | QVERIFY2(item->hasActiveFocus(), qPrintable(QString("expected: '%1', actual: '%2'" ).arg(name).arg(view.activeFocusItem() ? view.activeFocusItem()->objectName() : "null" ))); |
125 | } |
126 | |
127 | QGuiApplication::styleHints()->setTabFocusBehavior(Qt::TabFocusBehavior(-1)); |
128 | } |
129 | |
130 | void tst_focus::policy_data() |
131 | { |
132 | QTest::addColumn<QString>(name: "name" ); |
133 | |
134 | QTest::newRow(dataTag: "Control" ) << "Control" ; |
135 | QTest::newRow(dataTag: "ComboBox" ) << "ComboBox" ; |
136 | QTest::newRow(dataTag: "Button" ) << "Button" ; |
137 | QTest::newRow(dataTag: "Slider" ) << "Slider" ; |
138 | QTest::newRow(dataTag: "ScrollBar" ) << "ScrollBar" ; |
139 | } |
140 | |
141 | void tst_focus::policy() |
142 | { |
143 | QFETCH(QString, name); |
144 | |
145 | QQmlEngine engine; |
146 | QQmlComponent component(&engine); |
147 | component.setData(QString("import QtQuick.Controls 2.1; ApplicationWindow { width: 100; height: 100; %1 { anchors.fill: parent } }" ).arg(a: name).toUtf8(), baseUrl: QUrl()); |
148 | |
149 | QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(object: component.create())); |
150 | QVERIFY(window); |
151 | |
152 | QQuickControl *control = qobject_cast<QQuickControl *>(object: window->contentItem()->childItems().first()); |
153 | QVERIFY(control); |
154 | |
155 | QVERIFY(!control->hasActiveFocus()); |
156 | QVERIFY(!control->hasVisualFocus()); |
157 | |
158 | window->show(); |
159 | window->requestActivate(); |
160 | QVERIFY(QTest::qWaitForWindowActive(window.data())); |
161 | |
162 | struct TouchDeviceDeleter |
163 | { |
164 | static inline void cleanup(QTouchDevice *device) |
165 | { |
166 | QWindowSystemInterface::unregisterTouchDevice(device); |
167 | delete device; |
168 | } |
169 | }; |
170 | |
171 | QScopedPointer<QTouchDevice, TouchDeviceDeleter> device(new QTouchDevice); |
172 | device->setType(QTouchDevice::TouchScreen); |
173 | QWindowSystemInterface::registerTouchDevice(device: device.data()); |
174 | |
175 | control->setFocusPolicy(Qt::NoFocus); |
176 | QCOMPARE(control->focusPolicy(), Qt::NoFocus); |
177 | |
178 | // Qt::TabFocus vs. QQuickItem::activeFocusOnTab |
179 | control->setActiveFocusOnTab(true); |
180 | QCOMPARE(control->focusPolicy(), Qt::TabFocus); |
181 | control->setActiveFocusOnTab(false); |
182 | QCOMPARE(control->focusPolicy(), Qt::NoFocus); |
183 | |
184 | control->setFocusPolicy(Qt::TabFocus); |
185 | QCOMPARE(control->focusPolicy(), Qt::TabFocus); |
186 | QCOMPARE(control->activeFocusOnTab(), true); |
187 | |
188 | // Qt::TabFocus |
189 | QGuiApplication::styleHints()->setTabFocusBehavior(Qt::TabFocusAllControls); |
190 | QTest::keyClick(window: window.data(), key: Qt::Key_Tab); |
191 | QVERIFY(control->hasActiveFocus()); |
192 | QVERIFY(control->hasVisualFocus()); |
193 | QGuiApplication::styleHints()->setTabFocusBehavior(Qt::TabFocusBehavior(-1)); |
194 | |
195 | // reset |
196 | control->setFocus(false); |
197 | QVERIFY(!control->hasActiveFocus()); |
198 | |
199 | // Qt::ClickFocus (mouse) |
200 | control->setFocusPolicy(Qt::NoFocus); |
201 | control->setAcceptedMouseButtons(Qt::LeftButton); |
202 | QTest::mouseClick(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(control->width() / 2, control->height() / 2)); |
203 | QVERIFY(!control->hasActiveFocus()); |
204 | QVERIFY(!control->hasVisualFocus()); |
205 | |
206 | control->setFocusPolicy(Qt::ClickFocus); |
207 | QCOMPARE(control->focusPolicy(), Qt::ClickFocus); |
208 | QTest::mouseClick(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(control->width() / 2, control->height() / 2)); |
209 | QVERIFY(control->hasActiveFocus()); |
210 | QVERIFY(!control->hasVisualFocus()); |
211 | |
212 | // reset |
213 | control->setFocus(false); |
214 | QVERIFY(!control->hasActiveFocus()); |
215 | |
216 | // Qt::ClickFocus (touch) |
217 | control->setFocusPolicy(Qt::NoFocus); |
218 | QTest::touchEvent(window: window.data(), device: device.data()).press(touchId: 0, pt: QPoint(control->width() / 2, control->height() / 2)); |
219 | QTest::touchEvent(window: window.data(), device: device.data()).release(touchId: 0, pt: QPoint(control->width() / 2, control->height() / 2)); |
220 | QVERIFY(!control->hasActiveFocus()); |
221 | QVERIFY(!control->hasVisualFocus()); |
222 | |
223 | control->setFocusPolicy(Qt::ClickFocus); |
224 | QCOMPARE(control->focusPolicy(), Qt::ClickFocus); |
225 | QTest::touchEvent(window: window.data(), device: device.data()).press(touchId: 0, pt: QPoint(control->width() / 2, control->height() / 2)); |
226 | QTest::touchEvent(window: window.data(), device: device.data()).release(touchId: 0, pt: QPoint(control->width() / 2, control->height() / 2)); |
227 | QVERIFY(control->hasActiveFocus()); |
228 | QVERIFY(!control->hasVisualFocus()); |
229 | |
230 | // reset |
231 | control->setFocus(false); |
232 | QVERIFY(!control->hasActiveFocus()); |
233 | |
234 | // Qt::WheelFocus |
235 | QWheelEvent wheelEvent(QPoint(control->width() / 2, control->height() / 2), 10, Qt::NoButton, Qt::NoModifier); |
236 | QGuiApplication::sendEvent(receiver: control, event: &wheelEvent); |
237 | QVERIFY(!control->hasActiveFocus()); |
238 | QVERIFY(!control->hasVisualFocus()); |
239 | |
240 | control->setFocusPolicy(Qt::WheelFocus); |
241 | QCOMPARE(control->focusPolicy(), Qt::WheelFocus); |
242 | |
243 | QGuiApplication::sendEvent(receiver: control, event: &wheelEvent); |
244 | QVERIFY(control->hasActiveFocus()); |
245 | QVERIFY(!control->hasVisualFocus()); |
246 | } |
247 | |
248 | void tst_focus::reason_data() |
249 | { |
250 | QTest::addColumn<QString>(name: "name" ); |
251 | |
252 | QTest::newRow(dataTag: "Control" ) << "Control" ; |
253 | QTest::newRow(dataTag: "TextField" ) << "TextField" ; |
254 | QTest::newRow(dataTag: "TextArea" ) << "TextArea" ; |
255 | QTest::newRow(dataTag: "SpinBox" ) << "SpinBox" ; |
256 | QTest::newRow(dataTag: "ComboBox" ) << "ComboBox" ; |
257 | } |
258 | |
259 | void tst_focus::reason() |
260 | { |
261 | QFETCH(QString, name); |
262 | |
263 | QQmlEngine engine; |
264 | QQmlComponent component(&engine); |
265 | component.setData(QString("import QtQuick.Controls 2.1; ApplicationWindow { width: 100; height: 100; %1 { anchors.fill: parent } }" ).arg(a: name).toUtf8(), baseUrl: QUrl()); |
266 | |
267 | QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(object: component.create())); |
268 | QVERIFY(window.data()); |
269 | |
270 | QQuickItem *control = window->contentItem()->childItems().first(); |
271 | QVERIFY(control); |
272 | |
273 | window->show(); |
274 | window->requestActivate(); |
275 | QVERIFY(QTest::qWaitForWindowActive(window.data())); |
276 | |
277 | QCOMPARE(control->property("focusReason" ).toInt(), int(Qt::OtherFocusReason)); |
278 | control->forceActiveFocus(reason: Qt::MouseFocusReason); |
279 | QVERIFY(control->hasActiveFocus()); |
280 | QCOMPARE(control->property("focusReason" ).toInt(), int(Qt::MouseFocusReason)); |
281 | |
282 | QEXPECT_FAIL("TextArea" , "TODO: TextArea::visualFocus?" , Continue); |
283 | QEXPECT_FAIL("TextField" , "TODO: TextField::visualFocus?" , Continue); |
284 | QCOMPARE(control->property("visualFocus" ), QVariant(false)); |
285 | |
286 | window->contentItem()->setFocus(focus: false, reason: Qt::TabFocusReason); |
287 | QVERIFY(!control->hasActiveFocus()); |
288 | QCOMPARE(control->property("focusReason" ).toInt(), int(Qt::TabFocusReason)); |
289 | |
290 | QEXPECT_FAIL("TextArea" , "" , Continue); |
291 | QEXPECT_FAIL("TextField" , "" , Continue); |
292 | QCOMPARE(control->property("visualFocus" ), QVariant(false)); |
293 | |
294 | control->forceActiveFocus(reason: Qt::TabFocusReason); |
295 | QVERIFY(control->hasActiveFocus()); |
296 | QCOMPARE(control->property("focusReason" ).toInt(), int(Qt::TabFocusReason)); |
297 | |
298 | QEXPECT_FAIL("TextArea" , "" , Continue); |
299 | QEXPECT_FAIL("TextField" , "" , Continue); |
300 | QCOMPARE(control->property("visualFocus" ), QVariant(true)); |
301 | } |
302 | |
303 | void tst_focus::visualFocus() |
304 | { |
305 | QQuickView view; |
306 | view.setSource(testFileUrl(fileName: "visualFocus.qml" )); |
307 | view.show(); |
308 | view.requestActivate(); |
309 | QVERIFY(QTest::qWaitForWindowActive(&view)); |
310 | |
311 | QQuickItem *column = view.rootObject(); |
312 | QVERIFY(column); |
313 | QCOMPARE(column->childItems().count(), 2); |
314 | |
315 | QQuickControl *button = qobject_cast<QQuickControl *>(object: column->childItems().first()); |
316 | QVERIFY(button); |
317 | |
318 | QQuickItem *textfield = column->childItems().last(); |
319 | QVERIFY(textfield); |
320 | |
321 | button->forceActiveFocus(reason: Qt::TabFocusReason); |
322 | QVERIFY(button->hasActiveFocus()); |
323 | QVERIFY(button->hasVisualFocus()); |
324 | QVERIFY(button->property("showFocus" ).toBool()); |
325 | |
326 | QTest::mouseClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(textfield->x() + textfield->width() / 2, textfield->y() + textfield->height() / 2)); |
327 | QVERIFY(!button->hasActiveFocus()); |
328 | QVERIFY(!button->hasVisualFocus()); |
329 | QVERIFY(!button->property("showFocus" ).toBool()); |
330 | } |
331 | |
332 | void tst_focus::scope_data() |
333 | { |
334 | QTest::addColumn<QString>(name: "name" ); |
335 | |
336 | QTest::newRow(dataTag: "Frame" ) << "Frame" ; |
337 | QTest::newRow(dataTag: "GroupBox" ) << "Frame" ; |
338 | QTest::newRow(dataTag: "Page" ) << "Page" ; |
339 | QTest::newRow(dataTag: "Pane" ) << "Pane" ; |
340 | QTest::newRow(dataTag: "StackView" ) << "StackView" ; |
341 | } |
342 | |
343 | void tst_focus::scope() |
344 | { |
345 | QFETCH(QString, name); |
346 | |
347 | QQmlEngine engine; |
348 | QQmlComponent component(&engine); |
349 | component.setData(QString("import QtQuick 2.9; import QtQuick.Controls 2.2; ApplicationWindow { property alias child: child; width: 100; height: 100; %1 { anchors.fill: parent; Item { id: child; width: 10; height: 10 } } }" ).arg(a: name).toUtf8(), baseUrl: QUrl()); |
350 | |
351 | QScopedPointer<QQuickApplicationWindow> window(qobject_cast<QQuickApplicationWindow *>(object: component.create())); |
352 | QVERIFY2(window, qPrintable(component.errorString())); |
353 | |
354 | QQuickControl *control = qobject_cast<QQuickControl *>(object: window->contentItem()->childItems().first()); |
355 | QVERIFY(control); |
356 | |
357 | control->setFocusPolicy(Qt::WheelFocus); |
358 | control->setAcceptedMouseButtons(Qt::LeftButton); |
359 | |
360 | QQuickItem *child = window->property(name: "child" ).value<QQuickItem *>(); |
361 | QVERIFY(child); |
362 | |
363 | window->show(); |
364 | window->requestActivate(); |
365 | QVERIFY(QTest::qWaitForWindowActive(window.data())); |
366 | |
367 | struct TouchDeviceDeleter |
368 | { |
369 | static inline void cleanup(QTouchDevice *device) |
370 | { |
371 | QWindowSystemInterface::unregisterTouchDevice(device); |
372 | delete device; |
373 | } |
374 | }; |
375 | |
376 | QScopedPointer<QTouchDevice, TouchDeviceDeleter> device(new QTouchDevice); |
377 | device->setType(QTouchDevice::TouchScreen); |
378 | QWindowSystemInterface::registerTouchDevice(device: device.data()); |
379 | |
380 | child->forceActiveFocus(); |
381 | QVERIFY(child->hasActiveFocus()); |
382 | QVERIFY(control->hasActiveFocus()); |
383 | |
384 | // Qt::ClickFocus (mouse) |
385 | QTest::mouseClick(window: window.data(), button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(control->width() / 2, control->height() / 2)); |
386 | QVERIFY(!child->hasActiveFocus()); |
387 | QVERIFY(control->hasActiveFocus()); |
388 | |
389 | // reset |
390 | child->forceActiveFocus(); |
391 | QVERIFY(child->hasActiveFocus()); |
392 | QVERIFY(control->hasActiveFocus()); |
393 | |
394 | // Qt::ClickFocus (touch) |
395 | QTest::touchEvent(window: window.data(), device: device.data()).press(touchId: 0, pt: QPoint(control->width() / 2, control->height() / 2)); |
396 | QTest::touchEvent(window: window.data(), device: device.data()).release(touchId: 0, pt: QPoint(control->width() / 2, control->height() / 2)); |
397 | QVERIFY(!child->hasActiveFocus()); |
398 | QVERIFY(control->hasActiveFocus()); |
399 | |
400 | // reset |
401 | child->forceActiveFocus(); |
402 | QVERIFY(child->hasActiveFocus()); |
403 | QVERIFY(control->hasActiveFocus()); |
404 | |
405 | // Qt::WheelFocus |
406 | QWheelEvent wheelEvent(QPoint(control->width() / 2, control->height() / 2), 10, Qt::NoButton, Qt::NoModifier); |
407 | QGuiApplication::sendEvent(receiver: control, event: &wheelEvent); |
408 | QVERIFY(!child->hasActiveFocus()); |
409 | QVERIFY(control->hasActiveFocus()); |
410 | } |
411 | |
412 | QTEST_MAIN(tst_focus) |
413 | |
414 | #include "tst_focus.moc" |
415 | |