aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2022-08-24 13:09:43 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-08-29 18:48:14 +0000
commitdfd6170ebb9ce1b1106d8de953029a04279c616c (patch)
tree71b200861d918048228c543dce70cf1a7309062c
parentcbef5bae849d1d8c079e0b5fdd3d63fff5616ab8 (diff)
TextArea: selectByMouse default=true, but not on touchscreens
When you drag a finger across a TextArea, it should not select text. - your finger probably covers several characters, so you can't see where you're selecting until afterwards - if the item is in a Flickable, flicking by touch should be prioritized - if flicking happens, the text cursor should not move; but to avoid losing too much functionality, we allow it to move on release, if the TextArea gets the release (i.e. if it still has the exclusive grab) - TextArea's pressed, pressAndHold and released signals continue to behave the same with touch as with mouse As with the TextEdit change in 90d3fac73a10b9363fd34e6757cc730d7a0c086c, we now distinguish mouse events that are synthesized from non-mouse devices and avoid mouse-like behaviors as described above, but there is no behavior change if the event comes from an actual mouse or touchpad. In QQuickPressHandler::mousePressEvent() we give the original event's device to the delayed press event, now that we check the device to distinguish "real" mouse events from those that are synthesized from touch. [ChangeLog][Controls][TextArea] The selectByMouse property is now enabled by default, but no longer enables selecting by dragging your finger across text on a touchscreen. Platforms that are optimized for touchscreens normally use special text-selection handles, which interact with Qt via QInputMethod. You can opt out of the behavior change by setting the environment variable QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR=old. Task-number: QTBUG-10684 Task-number: QTBUG-38934 Task-number: QTBUG-90494 Task-number: QTBUG-101205 Change-Id: Icbe81e547b1cf8d5e3cc3ed922f12c7b411ca658 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Richard Moe Gustavsen <richard.gustavsen@qt.io> (cherry picked from commit 3ebb3540df9729d336d90225d3b4c1d2ca182e8e) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r--src/quick/items/qquicktextedit.cpp2
-rw-r--r--src/quicktemplates2/qquickpresshandler.cpp2
-rw-r--r--src/quicktemplates2/qquicktextarea.cpp6
-rw-r--r--tests/auto/quickcontrols2/CMakeLists.txt1
-rw-r--r--tests/auto/quickcontrols2/qquicktextarea/CMakeLists.txt46
-rw-r--r--tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_default.qml6
-rw-r--r--tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_old_default.qml6
-rw-r--r--tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_old_overridden.qml7
-rw-r--r--tests/auto/quickcontrols2/qquicktextarea/data/twoInAColumn.qml35
-rw-r--r--tests/auto/quickcontrols2/qquicktextarea/tst_qquicktextarea.cpp162
10 files changed, 271 insertions, 2 deletions
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp
index ba8d8a1fc4..3bc1db006a 100644
--- a/src/quick/items/qquicktextedit.cpp
+++ b/src/quick/items/qquicktextedit.cpp
@@ -3377,7 +3377,7 @@ void QQuickTextEdit::setOldSelectionDefault()
setKeepMouseGrab(false);
d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByMouse);
d->control->setTouchDragSelectionEnabled(true);
- qCDebug(lcTextEdit, "pre-6.4 behavior chosen by import version: selectByMouse defaults false; if enabled, touchscreen acts like a mouse");
+ qCDebug(lcTextEdit, "pre-6.4 behavior chosen: selectByMouse defaults false; if enabled, touchscreen acts like a mouse");
}
// TODO in 6.7.0: remove the note about versions prior to 6.4 in selectByMouse() documentation
diff --git a/src/quicktemplates2/qquickpresshandler.cpp b/src/quicktemplates2/qquickpresshandler.cpp
index b1c2208e99..d752bb36f1 100644
--- a/src/quicktemplates2/qquickpresshandler.cpp
+++ b/src/quicktemplates2/qquickpresshandler.cpp
@@ -20,7 +20,7 @@ void QQuickPressHandler::mousePressEvent(QMouseEvent *event)
if (Qt::LeftButton == (event->buttons() & Qt::LeftButton)) {
timer.start(QGuiApplication::styleHints()->mousePressAndHoldInterval(), control);
delayedMousePressEvent = new QMouseEvent(event->type(), event->position().toPoint(), event->globalPosition().toPoint(),
- event->button(), event->buttons(), event->modifiers());
+ event->button(), event->buttons(), event->modifiers(), event->pointingDevice());
} else {
timer.stop();
}
diff --git a/src/quicktemplates2/qquicktextarea.cpp b/src/quicktemplates2/qquicktextarea.cpp
index 48bdcddb64..0572746d14 100644
--- a/src/quicktemplates2/qquicktextarea.cpp
+++ b/src/quicktemplates2/qquicktextarea.cpp
@@ -19,6 +19,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
/*!
\qmltype TextArea
\inherits TextEdit
@@ -509,6 +511,10 @@ QQuickTextArea::QQuickTextArea(QQuickItem *parent)
QObjectPrivate::connect(this, &QQuickTextEdit::readOnlyChanged,
d, &QQuickTextAreaPrivate::readOnlyChanged);
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+ if (qEnvironmentVariable("QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR") == u"old"_s)
+ QQuickTextEdit::setOldSelectionDefault();
+#endif
}
QQuickTextArea::~QQuickTextArea()
diff --git a/tests/auto/quickcontrols2/CMakeLists.txt b/tests/auto/quickcontrols2/CMakeLists.txt
index 5bd05b95ee..ce391638f4 100644
--- a/tests/auto/quickcontrols2/CMakeLists.txt
+++ b/tests/auto/quickcontrols2/CMakeLists.txt
@@ -30,6 +30,7 @@ add_subdirectory(qquickmenubar)
add_subdirectory(qquickninepatchimage)
add_subdirectory(qquickpopup)
add_subdirectory(qquickstyle)
+add_subdirectory(qquicktextarea)
add_subdirectory(qquickuniversalstyle)
add_subdirectory(qquickuniversalstyleconf)
add_subdirectory(revisions)
diff --git a/tests/auto/quickcontrols2/qquicktextarea/CMakeLists.txt b/tests/auto/quickcontrols2/qquicktextarea/CMakeLists.txt
new file mode 100644
index 0000000000..553a1588ae
--- /dev/null
+++ b/tests/auto/quickcontrols2/qquicktextarea/CMakeLists.txt
@@ -0,0 +1,46 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#####################################################################
+## tst_qquicktextarea Test:
+#####################################################################
+
+# Collect test data
+file(GLOB_RECURSE test_data_glob
+ RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
+ data/*)
+list(APPEND test_data ${test_data_glob})
+
+qt_internal_add_test(tst_qquicktextarea
+ SOURCES
+ tst_qquicktextarea.cpp
+ DEFINES
+ QQC2_IMPORT_PATH=\\\"${CMAKE_CURRENT_SOURCE_DIR}/../../../../src/quickcontrols2\\\"
+ PUBLIC_LIBRARIES
+ Qt::CorePrivate
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::QmlPrivate
+ Qt::QuickControls2
+ Qt::QuickControls2Private
+ Qt::QuickControlsTestUtilsPrivate
+ Qt::QuickPrivate
+ Qt::QuickTemplates2Private
+ Qt::QuickTest
+ Qt::QuickTestUtilsPrivate
+ Qt::TestPrivate
+ TESTDATA ${test_data}
+)
+
+## Scopes:
+#####################################################################
+
+qt_internal_extend_target(tst_qquicktextarea CONDITION ANDROID OR IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\":/data\\\"
+)
+
+qt_internal_extend_target(tst_qquicktextarea CONDITION NOT ANDROID AND NOT IOS
+ DEFINES
+ QT_QMLTEST_DATADIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/data\\\"
+)
diff --git a/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_default.qml b/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_default.qml
new file mode 100644
index 0000000000..cb51b80545
--- /dev/null
+++ b/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_default.qml
@@ -0,0 +1,6 @@
+import QtQuick.Controls
+
+TextArea {
+ text: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+}
+
diff --git a/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_old_default.qml b/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_old_default.qml
new file mode 100644
index 0000000000..1401be36c1
--- /dev/null
+++ b/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_old_default.qml
@@ -0,0 +1,6 @@
+import QtQuick
+import QtQuick.Controls 6.3
+
+TextArea {
+ text: "0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+}
diff --git a/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_old_overridden.qml b/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_old_overridden.qml
new file mode 100644
index 0000000000..c0e6fb60a8
--- /dev/null
+++ b/tests/auto/quickcontrols2/qquicktextarea/data/mouseselection_old_overridden.qml
@@ -0,0 +1,7 @@
+import QtQuick
+import QtQuick.Controls 6.3
+
+TextArea {
+ selectByMouse: true
+ text: "0123456789 ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+}
diff --git a/tests/auto/quickcontrols2/qquicktextarea/data/twoInAColumn.qml b/tests/auto/quickcontrols2/qquicktextarea/data/twoInAColumn.qml
new file mode 100644
index 0000000000..944f5598ac
--- /dev/null
+++ b/tests/auto/quickcontrols2/qquicktextarea/data/twoInAColumn.qml
@@ -0,0 +1,35 @@
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+ColumnLayout {
+ height: 200
+ width: 400
+ spacing: -6
+ Rectangle {
+ border.color: top.activeFocus ? "steelblue" : "lightgrey"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.margins: 6
+ TextArea {
+ id: top
+ objectName: "top"
+ text: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ anchors.fill: parent
+ verticalAlignment: TextArea.AlignTop
+ }
+ }
+ Rectangle {
+ border.color: bottom.activeFocus ? "steelblue" : "lightgrey"
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ Layout.margins: 6
+ TextArea {
+ id: bottom
+ objectName: "bottom"
+ text: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ anchors.fill: parent
+ verticalAlignment: TextArea.AlignTop
+ }
+ }
+}
diff --git a/tests/auto/quickcontrols2/qquicktextarea/tst_qquicktextarea.cpp b/tests/auto/quickcontrols2/qquicktextarea/tst_qquicktextarea.cpp
new file mode 100644
index 0000000000..f9f31d9c3a
--- /dev/null
+++ b/tests/auto/quickcontrols2/qquicktextarea/tst_qquicktextarea.cpp
@@ -0,0 +1,162 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QtTest/qtest.h>
+#include <QtTest/qsignalspy.h>
+#include <QtTest/qtesttouch.h>
+
+#include <QtGui/qfontmetrics.h>
+#include <QtGui/private/qguiapplication_p.h>
+#include <QtGui/qpa/qplatformintegration.h>
+#include <QtGui/qtestsupport_gui.h>
+#include <QtQuick/qquickview.h>
+#include <QtQuickTestUtils/private/qmlutils_p.h>
+#include <QtQuickTestUtils/private/viewtestutils_p.h>
+#include <QtQuickTemplates2/private/qquicktextarea_p.h>
+#include <QtQuickControlsTestUtils/private/qtest_quickcontrols_p.h>
+
+class tst_QQuickTextArea : public QQmlDataTest
+{
+ Q_OBJECT
+
+public:
+ tst_QQuickTextArea();
+
+private slots:
+ void initTestCase() override;
+ void touchscreenDoesNotSelect_data();
+ void touchscreenDoesNotSelect();
+ void touchscreenSetsFocusAndMovesCursor();
+
+private:
+ static bool hasWindowActivation();
+ QScopedPointer<QPointingDevice> touchDevice = QScopedPointer<QPointingDevice>(QTest::createTouchDevice());
+};
+
+tst_QQuickTextArea::tst_QQuickTextArea()
+ : QQmlDataTest(QT_QMLTEST_DATADIR)
+{
+}
+
+void tst_QQuickTextArea::initTestCase()
+{
+ QQmlDataTest::initTestCase();
+ qputenv("QML_NO_TOUCH_COMPRESSION", "1");
+}
+
+void tst_QQuickTextArea::touchscreenDoesNotSelect_data()
+{
+ QTest::addColumn<QUrl>("src");
+ QTest::addColumn<bool>("setEnv");
+ QTest::addColumn<bool>("selectByMouse");
+ QTest::addColumn<bool>("selectByTouch");
+ QTest::newRow("new default") << testFileUrl("mouseselection_default.qml") << false << true << false;
+#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
+ QTest::newRow("putenv") << testFileUrl("mouseselection_default.qml") << true << false << false;
+ QTest::newRow("old_import") << testFileUrl("mouseselection_old_default.qml") << false << true << false;
+ QTest::newRow("old+putenv") << testFileUrl("mouseselection_old_default.qml") << true << false << false;
+ QTest::newRow("old+putenv+selectByMouse") << testFileUrl("mouseselection_old_overridden.qml") << true << true << true;
+#endif
+}
+
+void tst_QQuickTextArea::touchscreenDoesNotSelect()
+{
+ QFETCH(QUrl, src);
+ QFETCH(bool, setEnv);
+ QFETCH(bool, selectByMouse);
+ QFETCH(bool, selectByTouch);
+
+ if (setEnv)
+ qputenv("QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR", "old");
+ else
+ qunsetenv("QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR");
+
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, src));
+
+ QQuickTextEdit *textEditObject = qobject_cast<QQuickTextEdit *>(window.rootObject());
+ QVERIFY(textEditObject != nullptr);
+ QCOMPARE(textEditObject->selectByMouse(), selectByMouse);
+ textEditObject->setSelectByMouse(true); // enable selection with pre-6.4 import version
+ QVERIFY(textEditObject->selectedText().isEmpty());
+
+ if (selectByMouse) {
+ // press-drag-and-release from x1 to x2
+ int x1 = 10;
+ int x2 = 70;
+ int y = QFontMetrics(textEditObject->font()).height() / 2;
+ QTest::touchEvent(&window, touchDevice.data()).press(0, QPoint(x1,y), &window);
+ QTest::touchEvent(&window, touchDevice.data()).move(0, QPoint(x2,y), &window);
+ QTest::touchEvent(&window, touchDevice.data()).release(0, QPoint(x2,y), &window);
+ QQuickTouchUtils::flush(&window);
+ // if the import version is old enough, fall back to old behavior: touch swipe _does_ select text if selectByMouse is true
+ QCOMPARE(textEditObject->selectedText().isEmpty(), !selectByTouch);
+ }
+}
+
+void tst_QQuickTextArea::touchscreenSetsFocusAndMovesCursor()
+{
+ if (!hasWindowActivation())
+ QSKIP("Window activation is not supported");
+ qunsetenv("QT_QUICK_CONTROLS_TEXT_SELECTION_BEHAVIOR");
+
+ QQuickView window;
+ QVERIFY(QQuickTest::showView(window, testFileUrl("twoInAColumn.qml")));
+ window.requestActivate();
+ QVERIFY(QTest::qWaitForWindowActive(&window));
+
+ QQuickTextEdit *top = window.rootObject()->findChild<QQuickTextEdit*>("top");
+ QVERIFY(top);
+ QQuickTextEdit *bottom = window.rootObject()->findChild<QQuickTextEdit*>("bottom");
+ QVERIFY(bottom);
+ const auto len = bottom->text().length();
+
+ // tap the bottom field
+ const qreal yOffset = bottom->topPadding() + 6; // where to tap or drag to hit the text
+ QPoint p1 = bottom->mapToScene({60, yOffset}).toPoint();
+ QTest::touchEvent(&window, touchDevice.data()).press(0, p1, &window);
+ QQuickTouchUtils::flush(&window);
+ // text cursor is at 0 by default, on press
+ QCOMPARE(bottom->cursorPosition(), 0);
+ // the focus changes and the cursor moves after release (not after press, as in TextEdit)
+ QTest::touchEvent(&window, touchDevice.data()).release(0, p1, &window);
+ QQuickTouchUtils::flush(&window);
+ QCOMPARE(qApp->focusObject(), bottom);
+ QTRY_COMPARE_GT(bottom->cursorPosition(), 0);
+
+ // typing a character inserts it at the cursor position
+ QVERIFY(!bottom->text().contains('q'));
+ QTest::keyClick(&window, Qt::Key_Q);
+ QCOMPARE(bottom->text().length(), len + 1);
+ QCOMPARE_GT(bottom->text().indexOf('q'), 0);
+
+ // press-drag-and-release from p1 to p2 on the top field
+ p1 = top->mapToScene({0, yOffset}).toPoint();
+ QPoint p2 = top->mapToScene({76, yOffset}).toPoint();
+ QTest::touchEvent(&window, touchDevice.data()).press(0, p1, &window);
+ QQuickTouchUtils::flush(&window);
+ QTest::touchEvent(&window, touchDevice.data()).move(0, p2, &window);
+ QQuickTouchUtils::flush(&window);
+ QTest::touchEvent(&window, touchDevice.data()).release(0, p2, &window);
+ QQuickTouchUtils::flush(&window);
+ QCOMPARE(qApp->focusObject(), top);
+ QVERIFY(top->selectedText().isEmpty());
+ QCOMPARE_GT(top->cursorPosition(), 0);
+
+ // touch-drag did not select text, but mouse-drag from p2 back to p1
+ // does select the first part of the text, and leave the cursor at the beginning
+ QTest::mousePress(&window, Qt::LeftButton, {}, p2);
+ QTest::mouseMove(&window, p1);
+ QTest::mouseRelease(&window, Qt::LeftButton, {}, p1);
+ QCOMPARE(top->cursorPosition(), 0);
+ QCOMPARE_GT(top->selectedText().size(), 0);
+}
+
+bool tst_QQuickTextArea::hasWindowActivation()
+{
+ return (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::WindowActivation));
+}
+
+QTEST_QUICKCONTROLS_MAIN(tst_QQuickTextArea)
+
+#include "tst_qquicktextarea.moc"