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
51using namespace QQuickVisualTestUtil;
52
53class tst_focus : public QQmlDataTest
54{
55 Q_OBJECT
56
57private 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
75void tst_focus::initTestCase()
76{
77 QQmlDataTest::initTestCase();
78}
79
80void 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
99void 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
130void 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
141void 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
248void 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
259void 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
303void 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
332void 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
343void 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
412QTEST_MAIN(tst_focus)
413
414#include "tst_focus.moc"
415

source code of qtquickcontrols2/tests/auto/focus/tst_focus.cpp