From 9d73bf289e2719d781670baad1733989a56d7f40 Mon Sep 17 00:00:00 2001 From: Andrew den Exter Date: Tue, 10 Jan 2012 11:52:43 +1000 Subject: [PATCH] Add undo and redo functions to TextInput and TextEdit. The functionality already existed and was usable through keyboard short cuts but was not accessible through API. Task-number: QTBUG-16191 Change-Id: I080fa2ddb76668a7a632aa7477004f99037ea68b Reviewed-by: Martin Jones --- src/quick/items/qquicktextcontrol.cpp | 6 -- src/quick/items/qquicktextcontrol_p.h | 2 - src/quick/items/qquicktextedit.cpp | 52 ++++++++++++++ src/quick/items/qquicktextedit_p.h | 9 +++ src/quick/items/qquicktextinput.cpp | 77 ++++++++++++++++++-- src/quick/items/qquicktextinput_p.h | 9 +++ src/quick/items/qquicktextinput_p_p.h | 7 +- .../qtquick2/qquicktextedit/tst_qquicktextedit.cpp | 83 ++++++++++++++-------- .../qquicktextinput/tst_qquicktextinput.cpp | 37 ++++++++-- 9 files changed, 235 insertions(+), 47 deletions(-) diff --git a/src/quick/items/qquicktextcontrol.cpp b/src/quick/items/qquicktextcontrol.cpp index 1b3e35b..4f9475f 100644 --- a/src/quick/items/qquicktextcontrol.cpp +++ b/src/quick/items/qquicktextcontrol.cpp @@ -309,12 +309,6 @@ void QQuickTextControlPrivate::setContent(Qt::TextFormat format, const QString & QObject::connect(doc, SIGNAL(contentsChanged()), q, SLOT(_q_updateCurrentCharFormatAndSelection())); QObject::connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)), q, SLOT(_q_emitCursorPosChanged(QTextCursor))); QObject::connect(doc, SIGNAL(documentLayoutChanged()), q, SLOT(_q_documentLayoutChanged())); - - // convenience signal forwards - QObject::connect(doc, SIGNAL(undoAvailable(bool)), q, SIGNAL(undoAvailable(bool))); - QObject::connect(doc, SIGNAL(redoAvailable(bool)), q, SIGNAL(redoAvailable(bool))); - QObject::connect(doc, SIGNAL(modificationChanged(bool)), q, SIGNAL(modificationChanged(bool))); - QObject::connect(doc, SIGNAL(blockCountChanged(int)), q, SIGNAL(blockCountChanged(int))); } bool previousUndoRedoState = doc->isUndoRedoEnabled(); diff --git a/src/quick/items/qquicktextcontrol_p.h b/src/quick/items/qquicktextcontrol_p.h index 6b4c1a8..0ea2116 100644 --- a/src/quick/items/qquicktextcontrol_p.h +++ b/src/quick/items/qquicktextcontrol_p.h @@ -174,11 +174,9 @@ Q_SIGNALS: void updateCursorRequest(const QRectF &rect = QRectF()); void updateRequest(const QRectF &rect = QRectF()); void documentSizeChanged(const QSizeF &); - void blockCountChanged(int newBlockCount); void cursorRectangleChanged(); void linkActivated(const QString &link); void linkHovered(const QString &); - void modificationChanged(bool m); public: // control properties diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index a11acd7..eba8582 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -56,6 +56,7 @@ #include #include +#include #include #include #include @@ -1402,6 +1403,29 @@ void QQuickTextEdit::paste() } #endif // QT_NO_CLIPBOARD + +/*! + Undoes the last operation if undo is \l {canUndo}{available}. Deselects any + current selection, and updates the selection start to the current cursor + position. +*/ + +void QQuickTextEdit::undo() +{ + Q_D(QQuickTextEdit); + d->control->undo(); +} + +/*! + Redoes the last operation if redo is \l {canRedo}{available}. +*/ + +void QQuickTextEdit::redo() +{ + Q_D(QQuickTextEdit); + d->control->redo(); +} + /*! \overload Handles the given mouse \a event. @@ -1653,6 +1677,32 @@ bool QQuickTextEdit::canPaste() const } /*! + \qmlproperty bool QtQuick2::TextEdit::canUndo + + Returns true if the TextEdit is writable and there are previous operations + that can be undone. +*/ + +bool QQuickTextEdit::canUndo() const +{ + Q_D(const QQuickTextEdit); + return d->document->isUndoAvailable(); +} + +/*! + \qmlproperty bool QtQuick2::TextEdit::canRedo + + Returns true if the TextEdit is writable and there are \l {undo}{undone} + operations that can be redone. +*/ + +bool QQuickTextEdit::canRedo() const +{ + Q_D(const QQuickTextEdit); + return d->document->isRedoAvailable(); +} + +/*! \qmlproperty bool QtQuick2::TextEdit::inputMethodComposing @@ -1709,6 +1759,8 @@ void QQuickTextEditPrivate::init() #ifndef QT_NO_CLIPBOARD QObject::connect(QGuiApplication::clipboard(), SIGNAL(dataChanged()), q, SLOT(q_canPasteChanged())); #endif + FAST_CONNECT(document, SIGNAL(undoAvailable(bool)), q, SIGNAL(canUndoChanged())); + FAST_CONNECT(document, SIGNAL(redoAvailable(bool)), q, SIGNAL(canRedoChanged())); document->setDefaultFont(font); document->setDocumentMargin(textMargin); diff --git a/src/quick/items/qquicktextedit_p.h b/src/quick/items/qquicktextedit_p.h index ef16cfe..f37b7cd 100644 --- a/src/quick/items/qquicktextedit_p.h +++ b/src/quick/items/qquicktextedit_p.h @@ -90,6 +90,8 @@ class Q_AUTOTEST_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem 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) + Q_PROPERTY(bool canUndo READ canUndo NOTIFY canUndoChanged) + Q_PROPERTY(bool canRedo READ canRedo NOTIFY canRedoChanged) Q_PROPERTY(bool inputMethodComposing READ isInputMethodComposing NOTIFY inputMethodComposingChanged) public: @@ -193,6 +195,9 @@ public: bool canPaste() const; + bool canUndo() const; + bool canRedo() const; + virtual void componentComplete(); /* FROM EDIT */ @@ -248,6 +253,8 @@ Q_SIGNALS: void mouseSelectionModeChanged(SelectionMode mode); void linkActivated(const QString &link); void canPasteChanged(); + void canUndoChanged(); + void canRedoChanged(); void inputMethodComposingChanged(); void effectiveHorizontalAlignmentChanged(); @@ -262,6 +269,8 @@ public Q_SLOTS: void copy(); void paste(); #endif + void undo(); + void redo(); void insert(int position, const QString &text); void remove(int start, int end); diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp index 7858345..df536f4 100644 --- a/src/quick/items/qquicktextinput.cpp +++ b/src/quick/items/qquicktextinput.cpp @@ -571,6 +571,7 @@ void QQuickTextInput::setReadOnly(bool ro) if (!ro) d->setCursorPosition(d->end()); q_canPasteChanged(); + d->emitUndoRedoChanged(); emit readOnlyChanged(ro); } @@ -1754,6 +1755,34 @@ void QQuickTextInput::paste() #endif // QT_NO_CLIPBOARD /*! + Undoes the last operation if undo is \l {canUndo}{available}. Deselects any + current selection, and updates the selection start to the current cursor + position. +*/ + +void QQuickTextInput::undo() +{ + Q_D(QQuickTextInput); + if (!d->m_readOnly) { + d->internalUndo(); + d->finishChange(-1, true); + } +} + +/*! + Redoes the last operation if redo is \l {canRedo}{available}. +*/ + +void QQuickTextInput::redo() +{ + Q_D(QQuickTextInput); + if (!d->m_readOnly) { + d->internalRedo(); + d->finishChange(); + } +} + +/*! \qmlmethod void QtQuick2::TextInput::insert(int position, string text) Inserts \a text into the TextInput at position. @@ -2043,6 +2072,32 @@ bool QQuickTextInput::canPaste() const return d->canPaste; } +/*! + \qmlproperty bool QtQuick2::TextInput::canUndo + + Returns true if the TextInput is writable and there are previous operations + that can be undone. +*/ + +bool QQuickTextInput::canUndo() const +{ + Q_D(const QQuickTextInput); + return d->canUndo; +} + +/*! + \qmlproperty bool QtQuick2::TextInput::canRedo + + Returns true if the TextInput is writable and there are \l {undo}{undone} + operations that can be redone. +*/ + +bool QQuickTextInput::canRedo() const +{ + Q_D(const QQuickTextInput); + return d->canRedo; +} + void QQuickTextInput::moveCursorSelection(int position) { Q_D(QQuickTextInput); @@ -2988,6 +3043,7 @@ bool QQuickTextInputPrivate::finishChange(int validateFromState, bool update, bo notifyInputPanel |= (m_cursor == m_lastCursorPos); if (notifyInputPanel) q->updateMicroFocus(); + emitUndoRedoChanged(); emitCursorPositionChanged(); return true; @@ -3557,7 +3613,6 @@ void QQuickTextInputPrivate::internalUndo(int until) } } m_textDirty = true; - emitCursorPositionChanged(); } void QQuickTextInputPrivate::internalRedo() @@ -3600,7 +3655,21 @@ void QQuickTextInputPrivate::internalRedo() } } m_textDirty = true; - emitCursorPositionChanged(); +} + +void QQuickTextInputPrivate::emitUndoRedoChanged() +{ + Q_Q(QQuickTextInput); + const bool previousUndo = canUndo; + const bool previousRedo = canRedo; + + canUndo = isUndoAvailable(); + canRedo = isRedoAvailable(); + + if (previousUndo != canUndo) + emit q->canUndoChanged(); + if (previousRedo != canRedo) + emit q->canRedoChanged(); } /*! @@ -3725,11 +3794,11 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event) #ifndef QT_NO_SHORTCUT else if (event == QKeySequence::Undo) { if (!m_readOnly) - undo(); + q->undo(); } else if (event == QKeySequence::Redo) { if (!m_readOnly) - redo(); + q->redo(); } else if (event == QKeySequence::SelectAll) { selectAll(); diff --git a/src/quick/items/qquicktextinput_p.h b/src/quick/items/qquicktextinput_p.h index eadd0cc..535b1af 100644 --- a/src/quick/items/qquicktextinput_p.h +++ b/src/quick/items/qquicktextinput_p.h @@ -99,6 +99,8 @@ class Q_AUTOTEST_EXPORT QQuickTextInput : public QQuickImplicitSizeItem 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) + Q_PROPERTY(bool canUndo READ canUndo NOTIFY canUndoChanged) + Q_PROPERTY(bool canRedo READ canRedo NOTIFY canRedoChanged) Q_PROPERTY(bool inputMethodComposing READ isInputMethodComposing NOTIFY inputMethodComposingChanged) public: @@ -238,6 +240,9 @@ public: QRectF boundingRect() const; bool canPaste() const; + bool canUndo() const; + bool canRedo() const; + bool isInputMethodComposing() const; Qt::InputMethodHints imHints() const; @@ -275,6 +280,8 @@ Q_SIGNALS: void selectByMouseChanged(bool selectByMouse); void mouseSelectionModeChanged(SelectionMode mode); void canPasteChanged(); + void canUndoChanged(); + void canRedoChanged(); void inputMethodComposingChanged(); void effectiveHorizontalAlignmentChanged(); @@ -306,6 +313,8 @@ public Q_SLOTS: void copy(); void paste(); #endif + void undo(); + void redo(); void insert(int position, const QString &text); void remove(int start, int end); diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h index b20b8c4..03d825d 100644 --- a/src/quick/items/qquicktextinput_p_p.h +++ b/src/quick/items/qquicktextinput_p_p.h @@ -113,6 +113,8 @@ public: , selectByMouse(false) , canPaste(false) , canPasteValid(false) + , canUndo(false) + , canRedo(false) , hAlignImplicit(true) , selectPressed(false) , textLayoutDirty(true) @@ -237,6 +239,8 @@ public: bool selectByMouse:1; bool canPaste:1; bool canPasteValid:1; + bool canUndo:1; + bool canRedo:1; bool hAlignImplicit:1; bool selectPressed:1; bool textLayoutDirty:1; @@ -349,8 +353,6 @@ public: void insert(const QString &); void clear(); - void undo() { internalUndo(); finishChange(-1, true); } - void redo() { internalRedo(); finishChange(); } void selectWordAtPos(int); void setCursorPosition(int pos) { if (pos <= m_text.length()) moveCursor(qMax(0, pos)); } @@ -422,6 +424,7 @@ private: void internalUndo(int until = -1); void internalRedo(); + void emitUndoRedoChanged(); void emitCursorPositionChanged(); diff --git a/tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp index 78bab0a..7d40bc4 100644 --- a/tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp @@ -3326,28 +3326,32 @@ void tst_qquicktextedit::undo() QFETCH(QStringList, expectedString); QString componentStr = "import QtQuick 2.0\nTextEdit { focus: true }"; - QDeclarativeComponent textInputComponent(&engine); - textInputComponent.setData(componentStr.toLatin1(), QUrl()); - QQuickTextEdit *textInput = qobject_cast(textInputComponent.create()); - QVERIFY(textInput != 0); + QDeclarativeComponent textEditComponent(&engine); + textEditComponent.setData(componentStr.toLatin1(), QUrl()); + QQuickTextEdit *textEdit = qobject_cast(textEditComponent.create()); + QVERIFY(textEdit != 0); QQuickCanvas canvas; - textInput->setParentItem(canvas.rootItem()); + textEdit->setParentItem(canvas.rootItem()); canvas.show(); canvas.requestActivateWindow(); QTest::qWaitForWindowShown(&canvas); QTRY_COMPARE(QGuiApplication::activeWindow(), &canvas); + QVERIFY(!textEdit->canUndo()); + + QSignalSpy spy(textEdit, SIGNAL(canUndoChanged())); + int i; // STEP 1: First build up an undo history by inserting or typing some strings... for (i = 0; i < insertString.size(); ++i) { if (insertIndex[i] > -1) - textInput->setCursorPosition(insertIndex[i]); + textEdit->setCursorPosition(insertIndex[i]); // experimental stuff if (insertMode[i] == REPLACE_UNTIL_END) { - textInput->select(insertIndex[i], insertIndex[i] + 8); + textEdit->select(insertIndex[i], insertIndex[i] + 8); // This is what I actually want... // QTest::keyClick(testWidget, Qt::Key_End, Qt::ShiftModifier); @@ -3357,14 +3361,19 @@ void tst_qquicktextedit::undo() QTest::keyClick(&canvas, insertString.at(i).at(j).toLatin1()); } + QCOMPARE(spy.count(), 1); + // STEP 2: Next call undo several times and see if we can restore to the previous state for (i = 0; i < expectedString.size() - 1; ++i) { - QCOMPARE(textInput->text(), expectedString[i]); - simulateKeys(&canvas, QKeySequence::Undo); + QCOMPARE(textEdit->text(), expectedString[i]); + QVERIFY(textEdit->canUndo()); + textEdit->undo(); } // STEP 3: Verify that we have undone everything - QVERIFY(textInput->text().isEmpty()); + QVERIFY(textEdit->text().isEmpty()); + QVERIFY(!textEdit->canUndo()); + QCOMPARE(spy.count(), 2); } void tst_qquicktextedit::redo_data() @@ -3403,35 +3412,53 @@ void tst_qquicktextedit::redo() QFETCH(QStringList, expectedString); QString componentStr = "import QtQuick 2.0\nTextEdit { focus: true }"; - QDeclarativeComponent textInputComponent(&engine); - textInputComponent.setData(componentStr.toLatin1(), QUrl()); - QQuickTextEdit *textInput = qobject_cast(textInputComponent.create()); - QVERIFY(textInput != 0); + QDeclarativeComponent textEditComponent(&engine); + textEditComponent.setData(componentStr.toLatin1(), QUrl()); + QQuickTextEdit *textEdit = qobject_cast(textEditComponent.create()); + QVERIFY(textEdit != 0); QQuickCanvas canvas; - textInput->setParentItem(canvas.rootItem()); + textEdit->setParentItem(canvas.rootItem()); canvas.show(); canvas.requestActivateWindow(); QTest::qWaitForWindowShown(&canvas); QTRY_COMPARE(QGuiApplication::activeWindow(), &canvas); + QVERIFY(!textEdit->canUndo()); + QVERIFY(!textEdit->canRedo()); + + QSignalSpy spy(textEdit, SIGNAL(canRedoChanged())); + int i; // inserts the diff strings at diff positions for (i = 0; i < insertString.size(); ++i) { if (insertIndex[i] > -1) - textInput->setCursorPosition(insertIndex[i]); + textEdit->setCursorPosition(insertIndex[i]); for (int j = 0; j < insertString.at(i).length(); j++) QTest::keyClick(&canvas, insertString.at(i).at(j).toLatin1()); + QVERIFY(textEdit->canUndo()); + QVERIFY(!textEdit->canRedo()); } + QCOMPARE(spy.count(), 0); + // undo everything - while (!textInput->text().isEmpty()) - simulateKeys(&canvas, QKeySequence::Undo); + while (!textEdit->text().isEmpty()) { + QVERIFY(textEdit->canUndo()); + textEdit->undo(); + QVERIFY(textEdit->canRedo()); + } + + QCOMPARE(spy.count(), 1); for (i = 0; i < expectedString.size(); ++i) { - simulateKeys(&canvas, QKeySequence::Redo); - QCOMPARE(textInput->text() , expectedString[i]); + QVERIFY(textEdit->canRedo()); + textEdit->redo(); + QCOMPARE(textEdit->text() , expectedString[i]); + QVERIFY(textEdit->canUndo()); } + QVERIFY(!textEdit->canRedo()); + QCOMPARE(spy.count(), 2); } void tst_qquicktextedit::undo_keypressevents_data() @@ -3578,13 +3605,13 @@ void tst_qquicktextedit::undo_keypressevents() QFETCH(QStringList, expectedString); QString componentStr = "import QtQuick 2.0\nTextEdit { focus: true }"; - QDeclarativeComponent textInputComponent(&engine); - textInputComponent.setData(componentStr.toLatin1(), QUrl()); - QQuickTextEdit *textInput = qobject_cast(textInputComponent.create()); - QVERIFY(textInput != 0); + QDeclarativeComponent textEditComponent(&engine); + textEditComponent.setData(componentStr.toLatin1(), QUrl()); + QQuickTextEdit *textEdit = qobject_cast(textEditComponent.create()); + QVERIFY(textEdit != 0); QQuickCanvas canvas; - textInput->setParentItem(canvas.rootItem()); + textEdit->setParentItem(canvas.rootItem()); canvas.show(); canvas.requestActivateWindow(); QTest::qWaitForWindowShown(&canvas); @@ -3593,10 +3620,10 @@ void tst_qquicktextedit::undo_keypressevents() simulateKeys(&canvas, keys); for (int i = 0; i < expectedString.size(); ++i) { - QCOMPARE(textInput->text() , expectedString[i]); - simulateKeys(&canvas, QKeySequence::Undo); + QCOMPARE(textEdit->text() , expectedString[i]); + textEdit->undo(); } - QVERIFY(textInput->text().isEmpty()); + QVERIFY(textEdit->text().isEmpty()); } void tst_qquicktextedit::emptytags_QTBUG_22058() diff --git a/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp index e37e81d..017ac53 100644 --- a/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp +++ b/tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp @@ -3975,6 +3975,10 @@ void tst_qquicktextinput::undo() QTest::qWaitForWindowShown(&canvas); QTRY_COMPARE(QGuiApplication::activeWindow(), &canvas); + QVERIFY(!textInput->canUndo()); + + QSignalSpy spy(textInput, SIGNAL(canUndoChanged())); + int i; // STEP 1: First build up an undo history by inserting or typing some strings... @@ -3994,14 +3998,19 @@ void tst_qquicktextinput::undo() QTest::keyClick(&canvas, insertString.at(i).at(j).toLatin1()); } + QCOMPARE(spy.count(), 1); + // STEP 2: Next call undo several times and see if we can restore to the previous state for (i = 0; i < expectedString.size() - 1; ++i) { QCOMPARE(textInput->text(), expectedString[i]); - simulateKeys(&canvas, QKeySequence::Undo); + QVERIFY(textInput->canUndo()); + textInput->undo(); } // STEP 3: Verify that we have undone everything QVERIFY(textInput->text().isEmpty()); + QVERIFY(!textInput->canUndo()); + QCOMPARE(spy.count(), 2); } void tst_qquicktextinput::redo_data() @@ -4052,6 +4061,11 @@ void tst_qquicktextinput::redo() QTest::qWaitForWindowShown(&canvas); QTRY_COMPARE(QGuiApplication::activeWindow(), &canvas); + QVERIFY(!textInput->canUndo()); + QVERIFY(!textInput->canRedo()); + + QSignalSpy spy(textInput, SIGNAL(canRedoChanged())); + int i; // inserts the diff strings at diff positions for (i = 0; i < insertString.size(); ++i) { @@ -4059,16 +4073,29 @@ void tst_qquicktextinput::redo() textInput->setCursorPosition(insertIndex[i]); for (int j = 0; j < insertString.at(i).length(); j++) QTest::keyClick(&canvas, insertString.at(i).at(j).toLatin1()); + QVERIFY(textInput->canUndo()); + QVERIFY(!textInput->canRedo()); } + QCOMPARE(spy.count(), 0); + // undo everything - while (!textInput->text().isEmpty()) - simulateKeys(&canvas, QKeySequence::Undo); + while (!textInput->text().isEmpty()) { + QVERIFY(textInput->canUndo()); + textInput->undo(); + QVERIFY(textInput->canRedo()); + } + + QCOMPARE(spy.count(), 1); for (i = 0; i < expectedString.size(); ++i) { - simulateKeys(&canvas, QKeySequence::Redo); + QVERIFY(textInput->canRedo()); + textInput->redo(); QCOMPARE(textInput->text() , expectedString[i]); + QVERIFY(textInput->canUndo()); } + QVERIFY(!textInput->canRedo()); + QCOMPARE(spy.count(), 2); } void tst_qquicktextinput::undo_keypressevents_data() @@ -4230,7 +4257,7 @@ void tst_qquicktextinput::undo_keypressevents() for (int i = 0; i < expectedString.size(); ++i) { QCOMPARE(textInput->text() , expectedString[i]); - simulateKeys(&canvas, QKeySequence::Undo); + textInput->undo(); } QVERIFY(textInput->text().isEmpty()); } -- 2.7.4