From 7eb5f8a54bdcf0520a7aa0a2874a059060866097 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Thu, 1 Feb 2018 11:48:45 +0100 Subject: QQuickControl: respect click focus policy for focus scopes If a focus scope explicitly requests click focus, make it gain active focus by clearing the sub-focus child. Pane { focusPolicy: Qt.ClickFocus TextField { } } [ChangeLog][Controls][Control] Fixed focus scope controls, such as Frame, GroupBox, Page, and Pane, to respect click focus policy by clearing a potential sub-focus child. This makes it possible to close the virtual keyboard by clicking the background of a Pane that has Qt.ClickFocus set as its focusPolicy, for example. Task-number: QTBUG-66133 Change-Id: I582f3c66aa6f11e229d89c4f610fae3c6259104d Reviewed-by: Mitch Curtis --- tests/auto/focus/tst_focus.cpp | 72 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'tests/auto') diff --git a/tests/auto/focus/tst_focus.cpp b/tests/auto/focus/tst_focus.cpp index 3d4a8875..ad7578d0 100644 --- a/tests/auto/focus/tst_focus.cpp +++ b/tests/auto/focus/tst_focus.cpp @@ -67,6 +67,9 @@ private slots: void reason(); void visualFocus(); + + void scope_data(); + void scope(); }; void tst_focus::initTestCase() @@ -326,6 +329,75 @@ void tst_focus::visualFocus() QVERIFY(!button->property("showFocus").toBool()); } +void tst_focus::scope_data() +{ + QTest::addColumn("name"); + + QTest::newRow("Frame") << "Frame"; + QTest::newRow("GroupBox") << "Frame"; + QTest::newRow("Page") << "Page"; + QTest::newRow("Pane") << "Pane"; + QTest::newRow("StackView") << "StackView"; +} + +void tst_focus::scope() +{ + QFETCH(QString, name); + + QQmlEngine engine; + QQmlComponent component(&engine); + 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(name).toUtf8(), QUrl()); + + QScopedPointer window(qobject_cast(component.create())); + QVERIFY2(window, qPrintable(component.errorString())); + + QQuickControl *control = qobject_cast(window->contentItem()->childItems().first()); + QVERIFY(control); + + control->setFocusPolicy(Qt::WheelFocus); + control->setAcceptedMouseButtons(Qt::LeftButton); + + QQuickItem *child = window->property("child").value(); + QVERIFY(child); + + window->show(); + window->requestActivate(); + QVERIFY(QTest::qWaitForWindowActive(window.data())); + + struct TouchDeviceDeleter + { + static inline void cleanup(QTouchDevice *device) + { + QWindowSystemInterface::unregisterTouchDevice(device); + delete device; + } + }; + + QScopedPointer device(new QTouchDevice); + device->setType(QTouchDevice::TouchScreen); + QWindowSystemInterface::registerTouchDevice(device.data()); + + child->forceActiveFocus(); + QVERIFY(child->hasActiveFocus()); + QVERIFY(control->hasActiveFocus()); + + // Qt::ClickFocus (mouse) + QTest::mouseClick(window.data(), Qt::LeftButton, Qt::NoModifier, QPoint(control->width() / 2, control->height() / 2)); + QVERIFY(!child->hasActiveFocus()); + QVERIFY(control->hasActiveFocus()); + + // reset + child->forceActiveFocus(); + QVERIFY(child->hasActiveFocus()); + QVERIFY(control->hasActiveFocus()); + + // Qt::ClickFocus (touch) + QTest::touchEvent(window.data(), device.data()).press(0, QPoint(control->width() / 2, control->height() / 2)); + QTest::touchEvent(window.data(), device.data()).release(0, QPoint(control->width() / 2, control->height() / 2)); + QVERIFY(!child->hasActiveFocus()); + QVERIFY(control->hasActiveFocus()); +} + QTEST_MAIN(tst_focus) #include "tst_focus.moc" -- cgit v1.2.3 From a5aa09dfd7e42f38545fe4640b0fa251bdd32be9 Mon Sep 17 00:00:00 2001 From: Mitch Curtis Date: Wed, 31 Jan 2018 16:28:07 +0100 Subject: Make AbstractButton's icon properties win over Action's when both are set Task-number: QTBUG-65193 Change-Id: Idff23dcc35f3c3fe41406678613b022098149318 Reviewed-by: J-P Nurmi --- tests/auto/controls/data/tst_abstractbutton.qml | 221 ++++++++++++++++++++++-- 1 file changed, 206 insertions(+), 15 deletions(-) (limited to 'tests/auto') diff --git a/tests/auto/controls/data/tst_abstractbutton.qml b/tests/auto/controls/data/tst_abstractbutton.qml index 7acf9882..a46a08df 100644 --- a/tests/auto/controls/data/tst_abstractbutton.qml +++ b/tests/auto/controls/data/tst_abstractbutton.qml @@ -285,6 +285,208 @@ TestCase { compare(spy.count, data.resetChanged ? 1 : 0) } + function test_actionIcon_data() { + var data = [] + + // Save duplicating the rows by reusing them with different properties of the same type. + // This means that the first loop will test icon.name and the second one will test icon.source. + var stringPropertyValueSuffixes = [ + { propertyName: "name", valueSuffix: "IconName" }, + { propertyName: "source", valueSuffix: "IconSource" } + ] + + for (var i = 0; i < stringPropertyValueSuffixes.length; ++i) { + var propertyName = stringPropertyValueSuffixes[i].propertyName + var valueSuffix = stringPropertyValueSuffixes[i].valueSuffix + + var buttonPropertyValue = "Button" + valueSuffix + var buttonPropertyValue2 = "Button" + valueSuffix + "2" + var actionPropertyValue = "Action" + valueSuffix + var actionPropertyValue2 = "Action" + valueSuffix + "2" + + data.push({ tag: "implicit " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + resetExpected: "", resetChanged: true }) + data.push({ tag: "explicit " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "empty button " + propertyName, property: propertyName, + initButton: "", initAction: actionPropertyValue, + assignExpected: "", assignChanged: false, + resetExpected: "", resetChanged: false }) + data.push({ tag: "empty action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: "", + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "empty both " + propertyName, property: propertyName, + initButton: undefined, initAction: "", + assignExpected: "", assignChanged: false, + resetExpected: "", resetChanged: false }) + data.push({ tag: "modify button " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyButton: buttonPropertyValue2, + modifyButtonExpected: buttonPropertyValue2, modifyButtonChanged: true, + resetExpected: buttonPropertyValue2, resetChanged: false }) + data.push({ tag: "modify implicit action " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyAction: actionPropertyValue2, + modifyActionExpected: actionPropertyValue2, modifyActionChanged: true, + resetExpected: "", resetChanged: true }) + data.push({ tag: "modify explicit action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + modifyAction: actionPropertyValue2, + modifyActionExpected: buttonPropertyValue, modifyActionChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + } + + var intPropertyNames = [ + "width", + "height", + ] + + for (i = 0; i < intPropertyNames.length; ++i) { + propertyName = intPropertyNames[i] + + buttonPropertyValue = 20 + buttonPropertyValue2 = 21 + actionPropertyValue = 40 + actionPropertyValue2 = 41 + var defaultValue = 0 + + data.push({ tag: "implicit " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + resetExpected: defaultValue, resetChanged: true }) + data.push({ tag: "explicit " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "default button " + propertyName, property: propertyName, + initButton: defaultValue, initAction: actionPropertyValue, + assignExpected: defaultValue, assignChanged: false, + resetExpected: defaultValue, resetChanged: false }) + data.push({ tag: "default action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: defaultValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "default both " + propertyName, property: propertyName, + initButton: undefined, initAction: defaultValue, + assignExpected: defaultValue, assignChanged: false, + resetExpected: defaultValue, resetChanged: false }) + data.push({ tag: "modify button " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyButton: buttonPropertyValue2, + modifyButtonExpected: buttonPropertyValue2, modifyButtonChanged: true, + resetExpected: buttonPropertyValue2, resetChanged: false }) + data.push({ tag: "modify implicit action " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyAction: actionPropertyValue2, + modifyActionExpected: actionPropertyValue2, modifyActionChanged: true, + resetExpected: defaultValue, resetChanged: true }) + data.push({ tag: "modify explicit action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + modifyAction: actionPropertyValue2, + modifyActionExpected: buttonPropertyValue, modifyActionChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + } + + propertyName = "color" + buttonPropertyValue = "#aa0000" + buttonPropertyValue2 = "#ff0000" + actionPropertyValue = "#0000aa" + actionPropertyValue2 = "#0000ff" + defaultValue = "#00000000" + + data.push({ tag: "implicit " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + resetExpected: defaultValue, resetChanged: true }) + data.push({ tag: "explicit " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "default button " + propertyName, property: propertyName, + initButton: defaultValue, initAction: actionPropertyValue, + assignExpected: defaultValue, assignChanged: false, + resetExpected: defaultValue, resetChanged: false }) + data.push({ tag: "default action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: defaultValue, + assignExpected: buttonPropertyValue, assignChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + data.push({ tag: "default both " + propertyName, property: propertyName, + initButton: undefined, initAction: defaultValue, + assignExpected: defaultValue, assignChanged: false, + resetExpected: defaultValue, resetChanged: false }) + data.push({ tag: "modify button " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyButton: buttonPropertyValue2, + modifyButtonExpected: buttonPropertyValue2, modifyButtonChanged: true, + resetExpected: buttonPropertyValue2, resetChanged: false }) + data.push({ tag: "modify implicit action " + propertyName, property: propertyName, + initButton: undefined, initAction: actionPropertyValue, + assignExpected: actionPropertyValue, assignChanged: true, + modifyAction: actionPropertyValue2, + modifyActionExpected: actionPropertyValue2, modifyActionChanged: true, + resetExpected: defaultValue, resetChanged: true }) + data.push({ tag: "modify explicit action " + propertyName, property: propertyName, + initButton: buttonPropertyValue, initAction: actionPropertyValue, + assignExpected: buttonPropertyValue, assignChanged: false, + modifyAction: actionPropertyValue2, + modifyActionExpected: buttonPropertyValue, modifyActionChanged: false, + resetExpected: buttonPropertyValue, resetChanged: false }) + + return data; + } + + function test_actionIcon(data) { + var control = createTemporaryObject(button, testCase) + verify(control) + control.icon[data.property] = data.initButton + + var act = action.createObject(control) + act.icon[data.property] = data.initAction + + var spy = signalSpy.createObject(control, {target: control, signalName: "iconChanged"}) + verify(spy.valid) + + // assign action + spy.clear() + control.action = act + compare(control.icon[data.property], data.assignExpected) + compare(spy.count, data.assignChanged ? 1 : 0) + + // modify button + if (data.hasOwnProperty("modifyButton")) { + spy.clear() + control.icon[data.property] = data.modifyButton + compare(control.icon[data.property], data.modifyButtonExpected) + compare(spy.count, data.modifyButtonChanged ? 1 : 0) + } + + // modify action + if (data.hasOwnProperty("modifyAction")) { + spy.clear() + act.icon[data.property] = data.modifyAction + compare(control.icon[data.property], data.modifyActionExpected) + compare(spy.count, data.modifyActionChanged ? 1 : 0) + } + + // reset action + spy.clear() + control.action = null + compare(control.icon[data.property], data.resetExpected) + compare(spy.count, data.resetChanged ? 1 : 0) + } + Component { id: actionButton AbstractButton { @@ -305,8 +507,6 @@ TestCase { // initial values compare(control.text, "Default") - compare(control.icon.name, "default") - compare(control.icon.source, "qrc:/icons/default.png") compare(control.checkable, true) compare(control.checked, true) compare(control.enabled, false) @@ -316,14 +516,10 @@ TestCase { // changes via action control.action.text = "Action" - control.action.icon.name = "action" - control.action.icon.source = "qrc:/icons/action.png" control.action.checkable = false control.action.checked = false control.action.enabled = true compare(control.text, "Action") // propagates - compare(control.icon.name, "action") // propagates - compare(control.icon.source, "qrc:/icons/action.png") // propagates compare(control.checkable, false) // propagates compare(control.checked, false) // propagates compare(control.enabled, true) // propagates @@ -331,31 +527,26 @@ TestCase { // changes via button control.text = "Button" - control.icon.name = "button" - control.icon.source = "qrc:/icons/button.png" control.checkable = true control.checked = true control.enabled = false compare(control.text, "Button") - compare(control.icon.name, "button") - compare(control.icon.source, "qrc:/icons/button.png") compare(control.checkable, true) compare(control.checked, true) compare(control.enabled, false) compare(control.action.text, "Action") // does NOT propagate - compare(control.action.icon.name, "action") // does NOT propagate - compare(control.action.icon.source, "qrc:/icons/action.png") // does NOT propagate compare(control.action.checkable, true) // propagates compare(control.action.checked, true) // propagates compare(control.action.enabled, true) // does NOT propagate compare(textSpy.count, 2) - // remove the action so that only the button's text is left + // remove the action so that only the button's properties are left control.action = null compare(control.text, "Button") compare(textSpy.count, 2) - // setting an action while button has text shouldn't cause a change in the button's effective text + // setting an action while button has a particular property set + // shouldn't cause a change in the button's effective property value var secondAction = createTemporaryObject(action, testCase) verify(secondAction) secondAction.text = "SecondAction" @@ -363,7 +554,7 @@ TestCase { compare(control.text, "Button") compare(textSpy.count, 2) - // test setting an action with empty text + // test setting an action whose properties aren't set var thirdAction = createTemporaryObject(action, testCase) verify(thirdAction) control.action = thirdAction -- cgit v1.2.3 From f301decbe39d253cc979774e2f32cde9c1212e4c Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Mon, 5 Feb 2018 16:17:31 +0100 Subject: Popup: fix restoring focus for popups that did not request focus When a popup closes, if it has active focus, it should restore focus back to where it was regardless of whether the popup originally requested focus. Even if a popup does not request focus on open, it may gain focus programmatically or when a control with click-focus is clicked, for example. Task-number: QTBUG-66113 Change-Id: I9a7c467abe781bbef390d74898d13b9a30b2695b Reviewed-by: Mitch Curtis --- tests/auto/qquickpopup/tst_qquickpopup.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'tests/auto') diff --git a/tests/auto/qquickpopup/tst_qquickpopup.cpp b/tests/auto/qquickpopup/tst_qquickpopup.cpp index 3f4b2d13..b113e5ec 100644 --- a/tests/auto/qquickpopup/tst_qquickpopup.cpp +++ b/tests/auto/qquickpopup/tst_qquickpopup.cpp @@ -521,6 +521,16 @@ void tst_QQuickPopup::activeFocusOnClose1() nonFocusedPopup->close(); QVERIFY(!nonFocusedPopup->isVisible()); QVERIFY(focusedPopup->hasActiveFocus()); + + // QTBUG-66113: force active focus on a popup that did not request focus + nonFocusedPopup->open(); + nonFocusedPopup->forceActiveFocus(); + QVERIFY(nonFocusedPopup->isVisible()); + QVERIFY(nonFocusedPopup->hasActiveFocus()); + + nonFocusedPopup->close(); + QVERIFY(!nonFocusedPopup->isVisible()); + QVERIFY(focusedPopup->hasActiveFocus()); } void tst_QQuickPopup::activeFocusOnClose2() -- cgit v1.2.3 From cf719053689c32967084fdac54b118e44d0b7b67 Mon Sep 17 00:00:00 2001 From: J-P Nurmi Date: Fri, 2 Feb 2018 12:03:45 +0100 Subject: QQuickControl: respect wheel focus policy for focus scopes [ChangeLog][Controls][Control] Fixed focus scope controls to respect wheel focus policy. Task-number: QTBUG-66133 Change-Id: If963feba4b6e59b87ca54af5f6606805093eb0cc Reviewed-by: Mitch Curtis --- tests/auto/focus/tst_focus.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'tests/auto') diff --git a/tests/auto/focus/tst_focus.cpp b/tests/auto/focus/tst_focus.cpp index ad7578d0..958b996b 100644 --- a/tests/auto/focus/tst_focus.cpp +++ b/tests/auto/focus/tst_focus.cpp @@ -396,6 +396,17 @@ void tst_focus::scope() QTest::touchEvent(window.data(), device.data()).release(0, QPoint(control->width() / 2, control->height() / 2)); QVERIFY(!child->hasActiveFocus()); QVERIFY(control->hasActiveFocus()); + + // reset + child->forceActiveFocus(); + QVERIFY(child->hasActiveFocus()); + QVERIFY(control->hasActiveFocus()); + + // Qt::WheelFocus + QWheelEvent wheelEvent(QPoint(control->width() / 2, control->height() / 2), 10, Qt::NoButton, Qt::NoModifier); + QGuiApplication::sendEvent(control, &wheelEvent); + QVERIFY(!child->hasActiveFocus()); + QVERIFY(control->hasActiveFocus()); } QTEST_MAIN(tst_focus) -- cgit v1.2.3