Add undo and redo functions to TextInput and TextEdit.
authorAndrew den Exter <andrew.den-exter@nokia.com>
Tue, 10 Jan 2012 01:52:43 +0000 (11:52 +1000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 12 Jan 2012 08:33:32 +0000 (09:33 +0100)
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 <martin.jones@nokia.com>

src/quick/items/qquicktextcontrol.cpp
src/quick/items/qquicktextcontrol_p.h
src/quick/items/qquicktextedit.cpp
src/quick/items/qquicktextedit_p.h
src/quick/items/qquicktextinput.cpp
src/quick/items/qquicktextinput_p.h
src/quick/items/qquicktextinput_p_p.h
tests/auto/qtquick2/qquicktextedit/tst_qquicktextedit.cpp
tests/auto/qtquick2/qquicktextinput/tst_qquicktextinput.cpp

index 1b3e35b..4f9475f 100644 (file)
@@ -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();
index 6b4c1a8..0ea2116 100644 (file)
@@ -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
index a11acd7..eba8582 100644 (file)
@@ -56,6 +56,7 @@
 #include <QtCore/qmath.h>
 
 #include <private/qdeclarativeglobal_p.h>
+#include <private/qdeclarativeproperty_p.h>
 #include <private/qtextengine_p.h>
 #include <QtQuick/private/qsgtexture_p.h>
 #include <private/qsgadaptationlayer_p.h>
@@ -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);
index ef16cfe..f37b7cd 100644 (file)
@@ -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);
 
index 7858345..df536f4 100644 (file)
@@ -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();
index eadd0cc..535b1af 100644 (file)
@@ -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);
 
index b20b8c4..03d825d 100644 (file)
@@ -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();
 
index 78bab0a..7d40bc4 100644 (file)
@@ -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<QQuickTextEdit*>(textInputComponent.create());
-    QVERIFY(textInput != 0);
+    QDeclarativeComponent textEditComponent(&engine);
+    textEditComponent.setData(componentStr.toLatin1(), QUrl());
+    QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit*>(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<QQuickTextEdit*>(textInputComponent.create());
-    QVERIFY(textInput != 0);
+    QDeclarativeComponent textEditComponent(&engine);
+    textEditComponent.setData(componentStr.toLatin1(), QUrl());
+    QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit*>(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<QQuickTextEdit*>(textInputComponent.create());
-    QVERIFY(textInput != 0);
+    QDeclarativeComponent textEditComponent(&engine);
+    textEditComponent.setData(componentStr.toLatin1(), QUrl());
+    QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit*>(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()
index e37e81d..017ac53 100644 (file)
@@ -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());
 }