From 70ce4ec6ae9321a601a5af37d11bd284b2203bfc Mon Sep 17 00:00:00 2001 From: J-P Nurmi <jpnurmi@digia.com> Date: Fri, 1 Mar 2013 15:26:54 +0100 Subject: [PATCH] Add TextEdit::selectByKeyboard The main use case is for enabling text selection by keyboard for read-only editors. Change-Id: Ieaa9af366fd0eaf863a104a2fdf33c9ddad38b10 Reviewed-by: Alan Alpert (Personal) <416365416c@gmail.com> Reviewed-by: Frederik Gladhorn <frederik.gladhorn@digia.com> --- src/quick/items/qquickitemsmodule.cpp | 1 + src/quick/items/qquicktextedit.cpp | 46 ++++++- src/quick/items/qquicktextedit_p.h | 5 + src/quick/items/qquicktextedit_p_p.h | 4 +- .../qquicktextedit/tst_qquicktextedit.cpp | 124 ++++++++++++++++++ tests/testapplications/text/textedit.qml | 7 +- 6 files changed, 184 insertions(+), 3 deletions(-) diff --git a/src/quick/items/qquickitemsmodule.cpp b/src/quick/items/qquickitemsmodule.cpp index 20c35f42f2..cd49377822 100644 --- a/src/quick/items/qquickitemsmodule.cpp +++ b/src/quick/items/qquickitemsmodule.cpp @@ -158,6 +158,7 @@ static void qt_quickitems_defineModule(const char *uri, int major, int minor) qmlRegisterType<QQuickScale>(uri,major,minor,"Scale"); qmlRegisterType<QQuickText>(uri,major,minor,"Text"); qmlRegisterType<QQuickTextEdit>(uri,major,minor,"TextEdit"); + qmlRegisterType<QQuickTextEdit,1>(uri,2,1,"TextEdit"); qmlRegisterType<QQuickTextInput>(uri,major,minor,"TextInput"); qmlRegisterType<QQuickViewSection>(uri,major,minor,"ViewSection"); diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index dabbc96614..e30b9cb3fd 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -1238,6 +1238,44 @@ void QQuickTextEdit::componentComplete() if (d->cursorComponent && isCursorVisible()) QQuickTextUtil::createCursor(d); } + +/*! + \qmlproperty bool QtQuick2::TextEdit::selectByKeyboard + \since QtQuick 2.1 + + Defaults to true when the editor is editable, and false + when read-only. + + If true, the user can use the keyboard to select text + even if the editor is read-only. If false, the user + cannot use the keyboard to select text even if the + editor is editable. + + \sa readOnly +*/ +bool QQuickTextEdit::selectByKeyboard() const +{ + Q_D(const QQuickTextEdit); + if (d->selectByKeyboardSet) + return d->selectByKeyboard; + return !isReadOnly(); +} + +void QQuickTextEdit::setSelectByKeyboard(bool on) +{ + Q_D(QQuickTextEdit); + bool was = selectByKeyboard(); + d->selectByKeyboardSet = true; + if (was != on) { + d->selectByKeyboard = on; + if (on) + d->control->setTextInteractionFlags(d->control->textInteractionFlags() | Qt::TextSelectableByKeyboard); + else + d->control->setTextInteractionFlags(d->control->textInteractionFlags() & ~Qt::TextSelectableByKeyboard); + emit selectByKeyboardChanged(on); + } +} + /*! \qmlproperty bool QtQuick2::TextEdit::selectByMouse @@ -1316,8 +1354,12 @@ void QQuickTextEdit::setReadOnly(bool r) Qt::TextInteractionFlags flags = Qt::LinksAccessibleByMouse; if (d->selectByMouse) flags = flags | Qt::TextSelectableByMouse; + if (d->selectByKeyboardSet && d->selectByKeyboard) + flags = flags | Qt::TextSelectableByKeyboard; + else if (!d->selectByKeyboardSet && !r) + flags = flags | Qt::TextSelectableByKeyboard; if (!r) - flags = flags | Qt::TextSelectableByKeyboard | Qt::TextEditable; + flags = flags | Qt::TextEditable; d->control->setTextInteractionFlags(flags); if (!r) d->control->moveCursor(QTextCursor::End); @@ -1327,6 +1369,8 @@ void QQuickTextEdit::setReadOnly(bool r) #endif q_canPasteChanged(); emit readOnlyChanged(r); + if (!d->selectByKeyboardSet) + emit selectByKeyboardChanged(!r); } bool QQuickTextEdit::isReadOnly() const diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h index 255c8ac670..8a2d9b1e92 100644 --- a/src/quick/items/qquicktextedit_p.h +++ b/src/quick/items/qquicktextedit_p.h @@ -90,6 +90,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem #ifndef QT_NO_IM Q_PROPERTY(Qt::InputMethodHints inputMethodHints READ inputMethodHints WRITE setInputMethodHints NOTIFY inputMethodHintsChanged) #endif + Q_PROPERTY(bool selectByKeyboard READ selectByKeyboard WRITE setSelectByKeyboard NOTIFY selectByKeyboardChanged REVISION 1) Q_PROPERTY(bool selectByMouse READ selectByMouse WRITE setSelectByMouse NOTIFY selectByMouseChanged) Q_PROPERTY(SelectionMode mouseSelectionMode READ mouseSelectionMode WRITE setMouseSelectionMode NOTIFY mouseSelectionModeChanged) Q_PROPERTY(bool canPaste READ canPaste NOTIFY canPasteChanged) @@ -201,6 +202,9 @@ public: void setInputMethodHints(Qt::InputMethodHints hints); #endif + bool selectByKeyboard() const; + void setSelectByKeyboard(bool); + bool selectByMouse() const; void setSelectByMouse(bool); @@ -274,6 +278,7 @@ Q_SIGNALS: void activeFocusOnPressChanged(bool activeFocusOnPressed); void persistentSelectionChanged(bool isPersistentSelection); void textMarginChanged(qreal textMargin); + Q_REVISION(1) void selectByKeyboardChanged(bool selectByKeyboard); void selectByMouseChanged(bool selectByMouse); void mouseSelectionModeChanged(SelectionMode mode); void linkActivated(const QString &link); diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index f65af3d2d3..dd0f76f8d9 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -87,7 +87,7 @@ public: , documentDirty(true), dirty(false), richText(false), cursorVisible(false), cursorPending(false) , focusOnPress(true), persistentSelection(false), requireImplicitWidth(false) , selectByMouse(false), canPaste(false), canPasteValid(false), hAlignImplicit(true) - , textCached(true), inLayout(false) + , textCached(true), inLayout(false), selectByKeyboard(false), selectByKeyboardSet(false) { } @@ -168,6 +168,8 @@ public: bool hAlignImplicit:1; bool textCached:1; bool inLayout:1; + bool selectByKeyboard:1; + bool selectByKeyboardSet:1; }; QT_END_NAMESPACE diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index 4c4a04b293..b9041fb719 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -134,6 +134,9 @@ private slots: void dragMouseSelection(); void mouseSelectionMode_accessors(); void selectByMouse(); + void selectByKeyboard(); + void keyboardSelection_data(); + void keyboardSelection(); void renderType(); void inputMethodHints(); @@ -2063,6 +2066,127 @@ void tst_qquicktextedit::selectByMouse() QCOMPARE(spy.at(1).at(0).toBool(), false); } +void tst_qquicktextedit::selectByKeyboard() +{ + QQmlComponent oldComponent(&engine); + oldComponent.setData("import QtQuick 2.0\n TextEdit { selectByKeyboard: true }", QUrl()); + QVERIFY(!oldComponent.create()); + + QQmlComponent component(&engine); + component.setData("import QtQuick 2.1\n TextEdit { }", QUrl()); + QScopedPointer<QObject> object(component.create()); + QQuickTextEdit *edit = qobject_cast<QQuickTextEdit *>(object.data()); + QVERIFY(edit); + + QSignalSpy spy(edit, SIGNAL(selectByKeyboardChanged(bool))); + + QCOMPARE(edit->isReadOnly(), false); + QCOMPARE(edit->selectByKeyboard(), true); + + edit->setReadOnly(true); + QCOMPARE(edit->selectByKeyboard(), false); + QCOMPARE(spy.count(), 1); + QCOMPARE(spy.at(0).at(0).toBool(), false); + + edit->setSelectByKeyboard(true); + QCOMPARE(edit->selectByKeyboard(), true); + QCOMPARE(spy.count(), 2); + QCOMPARE(spy.at(1).at(0).toBool(), true); + + edit->setReadOnly(false); + QCOMPARE(edit->selectByKeyboard(), true); + QCOMPARE(spy.count(), 2); + + edit->setSelectByKeyboard(false); + QCOMPARE(edit->selectByKeyboard(), false); + QCOMPARE(spy.count(), 3); + QCOMPARE(spy.at(2).at(0).toBool(), false); +} + +Q_DECLARE_METATYPE(QKeySequence::StandardKey) + +void tst_qquicktextedit::keyboardSelection_data() +{ + QTest::addColumn<QString>("text"); + QTest::addColumn<bool>("readOnly"); + QTest::addColumn<bool>("selectByKeyboard"); + QTest::addColumn<int>("cursorPosition"); + QTest::addColumn<QKeySequence::StandardKey>("standardKey"); + QTest::addColumn<QString>("selectedText"); + + QTest::newRow("editable - select first char") + << QStringLiteral("editable - select first char") << false << true << 0 << QKeySequence::SelectNextChar << QStringLiteral("e"); + QTest::newRow("editable - select first word") + << QStringLiteral("editable - select first char") << false << true << 0 << QKeySequence::SelectNextWord << QStringLiteral("editable "); + + QTest::newRow("editable - cannot select first char") + << QStringLiteral("editable - cannot select first char") << false << false << 0 << QKeySequence::SelectNextChar << QStringLiteral(""); + QTest::newRow("editable - cannot select first word") + << QStringLiteral("editable - cannot select first word") << false << false << 0 << QKeySequence::SelectNextWord << QStringLiteral(""); + + QTest::newRow("editable - select last char") + << QStringLiteral("editable - select last char") << false << true << 27 << QKeySequence::SelectPreviousChar << QStringLiteral("r"); + QTest::newRow("editable - select last word") + << QStringLiteral("editable - select last word") << false << true << 27 << QKeySequence::SelectPreviousWord << QStringLiteral("word"); + + QTest::newRow("editable - cannot select last char") + << QStringLiteral("editable - cannot select last char") << false << false << 35 << QKeySequence::SelectPreviousChar << QStringLiteral(""); + QTest::newRow("editable - cannot select last word") + << QStringLiteral("editable - cannot select last word") << false << false << 35 << QKeySequence::SelectPreviousWord << QStringLiteral(""); + + QTest::newRow("read-only - cannot select first char") + << QStringLiteral("read-only - cannot select first char") << true << false << 0 << QKeySequence::SelectNextChar << QStringLiteral(""); + QTest::newRow("read-only - cannot select first word") + << QStringLiteral("read-only - cannot select first word") << true << false << 0 << QKeySequence::SelectNextWord << QStringLiteral(""); + + QTest::newRow("read-only - cannot select last char") + << QStringLiteral("read-only - cannot select last char") << true << false << 35 << QKeySequence::SelectPreviousChar << QStringLiteral(""); + QTest::newRow("read-only - cannot select last word") + << QStringLiteral("read-only - cannot select last word") << true << false << 35 << QKeySequence::SelectPreviousWord << QStringLiteral(""); + + QTest::newRow("read-only - select first char") + << QStringLiteral("read-only - select first char") << true << true << 0 << QKeySequence::SelectNextChar << QStringLiteral("r"); + QTest::newRow("read-only - select first word") + << QStringLiteral("read-only - select first word") << true << true << 0 << QKeySequence::SelectNextWord << QStringLiteral("read"); + + QTest::newRow("read-only - select last char") + << QStringLiteral("read-only - select last char") << true << true << 28 << QKeySequence::SelectPreviousChar << QStringLiteral("r"); + QTest::newRow("read-only - select last word") + << QStringLiteral("read-only - select last word") << true << true << 28 << QKeySequence::SelectPreviousWord << QStringLiteral("word"); +} + +void tst_qquicktextedit::keyboardSelection() +{ + QFETCH(QString, text); + QFETCH(bool, readOnly); + QFETCH(bool, selectByKeyboard); + QFETCH(int, cursorPosition); + QFETCH(QKeySequence::StandardKey, standardKey); + QFETCH(QString, selectedText); + + QQmlComponent component(&engine); + component.setData("import QtQuick 2.1\n TextEdit { focus: true }", QUrl()); + QScopedPointer<QObject> object(component.create()); + QQuickTextEdit *edit = qobject_cast<QQuickTextEdit *>(object.data()); + QVERIFY(edit); + + edit->setText(text); + edit->setReadOnly(readOnly); + edit->setSelectByKeyboard(selectByKeyboard); + edit->setCursorPosition(cursorPosition); + + QQuickWindow window; + edit->setParentItem(window.contentItem()); + window.show(); + window.requestActivate(); + QTest::qWaitForWindowActive(&window); + QVERIFY(edit->hasActiveFocus()); + + simulateKeys(&window, standardKey); + + QCOMPARE(edit->selectedText(), selectedText); +} + void tst_qquicktextedit::renderType() { QQmlComponent component(&engine); diff --git a/tests/testapplications/text/textedit.qml b/tests/testapplications/text/textedit.qml index 789a52894e..e0d7dbdde4 100644 --- a/tests/testapplications/text/textedit.qml +++ b/tests/testapplications/text/textedit.qml @@ -39,7 +39,7 @@ ** ****************************************************************************/ -import QtQuick 2.0 +import QtQuick 2.1 Rectangle { height: 360; width: 640 @@ -75,6 +75,7 @@ Rectangle { wrapMode: { wrapvalue.model.get(wrapvalue.currentIndex).value } smooth: { smoothvalue.model.get(smoothvalue.currentIndex).value } selectByMouse: { mousevalue.model.get(mousevalue.currentIndex).value } + selectByKeyboard: { keyboardvalue.model.get(keyboardvalue.currentIndex).value } onLinkActivated: { bordercolor.border.color = "red" } Rectangle { id: bordercolor; color: "transparent"; border.color: "green"; anchors.fill: parent } } @@ -227,6 +228,10 @@ Rectangle { id: mousevalue controlname: "Mouse" model: ListModel { ListElement { name: "Off"; value: false } ListElement { name: "On"; value: true } } } + ControlView { + id: keyboardvalue + controlname: "Keyboard" + model: ListModel { ListElement { name: "Off"; value: false } ListElement { name: "On"; value: true } } } ControlView { id: halignvalue controlname: "HAlign" -- GitLab