Set cursorVisible to false when im cursor length is 0.
authorAndrew den Exter <andrew.den-exter@nokia.com>
Thu, 29 Mar 2012 06:34:09 +0000 (16:34 +1000)
committerQt by Nokia <qt-info@nokia.com>
Mon, 2 Apr 2012 08:45:55 +0000 (10:45 +0200)
If the length of the QInputMethodEvent::Cursor attribute is 0 the
cursor is supposed to be hidden.  To ensure this and any other IM
state is reverted when the input method is reset send a empty
event to the editor when preedit is cancelled or removed and
count formatting or cursor changes when determining if the
input method is composing (i.e has state that needs to be reset).

Change-Id: Ifca69aa0c18776b1aef355ed6ae9aecc40b9d475
Reviewed-by: Yann Bodson <yann.bodson@nokia.com>
src/quick/items/qquicktextcontrol.cpp
src/quick/items/qquicktextcontrol_p.h
src/quick/items/qquicktextcontrol_p_p.h
src/quick/items/qquicktextedit.cpp
src/quick/items/qquicktextinput.cpp
src/quick/items/qquicktextinput_p_p.h
tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp
tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp

index eefe938..739b5f8 100644 (file)
@@ -44,6 +44,7 @@
 
 #ifndef QT_NO_TEXTCONTROL
 
+#include <qcoreapplication.h>
 #include <qfont.h>
 #include <qpainter.h>
 #include <qevent.h>
@@ -107,11 +108,12 @@ QQuickTextControlPrivate::QQuickTextControlPrivate()
       ignoreAutomaticScrollbarAdjustement(false),
       overwriteMode(false),
       acceptRichText(true),
-      hideCursor(false),
+      cursorVisible(false),
       hasFocus(false),
       isEnabled(true),
       hadSelectionOnMousePress(false),
-      wordSelectionEnabled(false)
+      wordSelectionEnabled(false),
+      hasImState(false)
 {}
 
 bool QQuickTextControlPrivate::cursorMoveKeyEvent(QKeyEvent *e)
@@ -292,6 +294,8 @@ void QQuickTextControlPrivate::setContent(Qt::TextFormat format, const QString &
 {
     Q_Q(QQuickTextControl);
 
+    cancelPreedit();
+
     // for use when called from setPlainText. we may want to re-use the currently
     // set char format then.
     const QTextCharFormat charFormatForInsertion = cursor.charFormat();
@@ -1441,13 +1445,16 @@ void QQuickTextControlPrivate::inputMethodEvent(QInputMethodEvent *e)
     QList<QTextLayout::FormatRange> overrides;
     const int oldPreeditCursor = preeditCursor;
     preeditCursor = e->preeditString().length();
-    hideCursor = false;
+    hasImState = !e->preeditString().isEmpty();
+    cursorVisible = true;
     for (int i = 0; i < e->attributes().size(); ++i) {
         const QInputMethodEvent::Attribute &a = e->attributes().at(i);
         if (a.type == QInputMethodEvent::Cursor) {
+            hasImState = true;
             preeditCursor = a.start;
-            hideCursor = !a.length;
+            cursorVisible = a.length != 0;
         } else if (a.type == QInputMethodEvent::TextFormat) {
+            hasImState = true;
             QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
             if (f.isValid()) {
                 QTextLayout::FormatRange o;
@@ -1514,6 +1521,37 @@ void QQuickTextControlPrivate::focusEvent(QFocusEvent *e)
     }
 }
 
+bool QQuickTextControl::hasImState() const
+{
+    Q_D(const QQuickTextControl);
+    return d->hasImState;
+}
+
+bool QQuickTextControl::cursorVisible() const
+{
+    Q_D(const QQuickTextControl);
+    return d->cursorVisible;
+}
+
+void QQuickTextControl::setCursorVisible(bool visible)
+{
+    Q_D(QQuickTextControl);
+    d->cursorVisible = visible;
+    d->setBlinkingCursorEnabled(d->cursorVisible
+            && (d->interactionFlags & (Qt::TextEditable | Qt::TextSelectableByKeyboard)));
+}
+
+QTextCursor QQuickTextControl::cursorForPosition(const QPointF &pos) const
+{
+    Q_D(const QQuickTextControl);
+    int cursorPos = hitTest(pos, Qt::FuzzyHit);
+    if (cursorPos == -1)
+        cursorPos = 0;
+    QTextCursor c(d->doc);
+    c.setPosition(cursorPos);
+    return c;
+}
+
 QRectF QQuickTextControl::cursorRect(const QTextCursor &cursor) const
 {
     Q_D(const QQuickTextControl);
@@ -1727,21 +1765,31 @@ bool QQuickTextControlPrivate::isPreediting() const
 
 void QQuickTextControlPrivate::commitPreedit()
 {
-    if (!isPreediting())
+    Q_Q(QQuickTextControl);
+
+    if (!hasImState)
         return;
 
     qApp->inputMethod()->commit();
 
-    if (!isPreediting())
+    if (!hasImState)
         return;
 
-    cursor.beginEditBlock();
-    preeditCursor = 0;
-    QTextBlock block = cursor.block();
-    QTextLayout *layout = block.layout();
-    layout->setPreeditArea(-1, QString());
-    layout->clearAdditionalFormats();
-    cursor.endEditBlock();
+    QInputMethodEvent event;
+    QCoreApplication::sendEvent(q->parent(), &event);
+}
+
+void QQuickTextControlPrivate::cancelPreedit()
+{
+    Q_Q(QQuickTextControl);
+
+    if (!hasImState)
+        return;
+
+    qApp->inputMethod()->reset();
+
+    QInputMethodEvent event;
+    QCoreApplication::sendEvent(q->parent(), &event);
 }
 
 void QQuickTextControl::setTextInteractionFlags(Qt::TextInteractionFlags flags)
index 9e3fc90..be3f7f7 100644 (file)
@@ -98,6 +98,10 @@ public:
     QString toHtml() const;
 #endif
 
+    bool hasImState() const;
+    bool cursorVisible() const;
+    void setCursorVisible(bool visible);
+    QTextCursor cursorForPosition(const QPointF &pos) const;
     QRectF cursorRect(const QTextCursor &cursor) const;
     QRectF cursorRect() const;
     QRectF selectionRect(const QTextCursor &cursor) const;
index c5a39cc..3a10f00 100644 (file)
@@ -127,6 +127,7 @@ public:
 
     bool isPreediting() const;
     void commitPreedit();
+    void cancelPreedit();
 
     QPointF trippleClickPoint;
     QPointF mousePressPos;
@@ -155,11 +156,12 @@ public:
     bool ignoreAutomaticScrollbarAdjustement : 1;
     bool overwriteMode : 1;
     bool acceptRichText : 1;
-    bool hideCursor : 1; // used to hide the cursor in the preedit area
+    bool cursorVisible : 1; // used to hide the cursor in the preedit area
     bool hasFocus : 1;
     bool isEnabled : 1;
     bool hadSelectionOnMousePress : 1;
     bool wordSelectionEnabled : 1;
+    bool hasImState : 1;
 
     void _q_copyLink();
     void _q_updateBlock(const QTextBlock &);
index 4fa5233..f727c54 100644 (file)
@@ -871,10 +871,9 @@ void QQuickTextEdit::setCursorVisible(bool on)
     if (d->cursorVisible == on)
         return;
     d->cursorVisible = on;
-    QFocusEvent focusEvent(on ? QEvent::FocusIn : QEvent::FocusOut);
     if (!on && !d->persistentSelection)
         d->control->setCursorIsFocusIndicator(true);
-    d->control->processEvent(&focusEvent, QPointF(0, -d->yoff));
+    d->control->setCursorVisible(on);
     emit cursorVisibleChanged(d->cursorVisible);
 }
 
@@ -1536,15 +1535,18 @@ void QQuickTextEdit::inputMethodEvent(QInputMethodEvent *event)
     Q_D(QQuickTextEdit);
     const bool wasComposing = isInputMethodComposing();
     d->control->processEvent(event, QPointF(0, -d->yoff));
+    setCursorVisible(d->control->cursorVisible());
     if (wasComposing != isInputMethodComposing())
         emit inputMethodComposingChanged();
 }
 
 void QQuickTextEdit::itemChange(ItemChange change, const ItemChangeData &value)
 {
+    Q_D(QQuickTextEdit);
     if (change == ItemActiveFocusHasChanged) {
         setCursorVisible(value.boolValue); // ### refactor: focus handling && d->canvas && d->canvas->hasFocus());
-
+        QFocusEvent focusEvent(value.boolValue ? QEvent::FocusIn : QEvent::FocusOut);
+        d->control->processEvent(&focusEvent, QPointF(0, -d->yoff));
         if (value.boolValue) {
             q_updateAlignment();
             connect(qApp->inputMethod(), SIGNAL(inputDirectionChanged(Qt::LayoutDirection)),
@@ -1729,9 +1731,7 @@ bool QQuickTextEdit::canRedo() const
 bool QQuickTextEdit::isInputMethodComposing() const
 {
     Q_D(const QQuickTextEdit);
-    if (QTextLayout *layout = d->control->textCursor().block().layout())
-        return layout->preeditAreaText().length() > 0;
-    return false;
+    return d->control->hasImState();
 }
 
 void QQuickTextEditPrivate::init()
index ccd7ff1..5aa01d2 100644 (file)
@@ -45,6 +45,7 @@
 
 #include <private/qqmlglobal_p.h>
 
+#include <QtCore/qcoreapplication.h>
 #include <QtQml/qqmlinfo.h>
 #include <QtGui/qevent.h>
 #include <QTextBoundaryFinder>
@@ -124,8 +125,8 @@ void QQuickTextInput::setText(const QString &s)
     Q_D(QQuickTextInput);
     if (s == text())
         return;
-    if (d->composeMode())
-        qApp->inputMethod()->reset();
+
+    d->cancelPreedit();
     d->internalSetText(s, -1, false);
 }
 
@@ -674,9 +675,7 @@ QRectF QQuickTextInput::cursorRectangle() const
 {
     Q_D(const QQuickTextInput);
 
-    int c = d->m_cursor;
-    if (d->m_preeditCursor != -1)
-        c += d->m_preeditCursor;
+    int c = d->m_cursor + d->m_preeditCursor;
     if (d->m_echoMode == NoEcho)
         c = 0;
     QTextLine l = d->m_textLayout.lineForTextPosition(c);
@@ -1398,7 +1397,7 @@ void QQuickTextInput::keyPressEvent(QKeyEvent* ev)
 void QQuickTextInput::inputMethodEvent(QInputMethodEvent *ev)
 {
     Q_D(QQuickTextInput);
-    const bool wasComposing = d->preeditAreaText().length() > 0;
+    const bool wasComposing = d->hasImState;
     if (d->m_readOnly) {
         ev->ignore();
     } else {
@@ -1407,7 +1406,7 @@ void QQuickTextInput::inputMethodEvent(QInputMethodEvent *ev)
     if (!ev->isAccepted())
         QQuickImplicitSizeItem::inputMethodEvent(ev);
 
-    if (wasComposing != (d->m_textLayout.preeditAreaText().length() > 0))
+    if (wasComposing != d->hasImState)
         emit inputMethodComposingChanged();
 }
 
@@ -2513,7 +2512,7 @@ void QQuickTextInput::itemChange(ItemChange change, const ItemChangeData &value)
 bool QQuickTextInput::isInputMethodComposing() const
 {
     Q_D(const QQuickTextInput);
-    return d->preeditAreaText().length() > 0;
+    return d->hasImState;
 }
 
 void QQuickTextInputPrivate::init()
@@ -2820,18 +2819,31 @@ void QQuickTextInputPrivate::paste(QClipboard::Mode clipboardMode)
 */
 void QQuickTextInputPrivate::commitPreedit()
 {
-    if (!composeMode())
+    Q_Q(QQuickTextInput);
+
+    if (!hasImState)
         return;
 
     qApp->inputMethod()->commit();
 
-    if (!composeMode())
+    if (!hasImState)
+        return;
+
+    QInputMethodEvent ev;
+    QCoreApplication::sendEvent(q, &ev);
+}
+
+void QQuickTextInputPrivate::cancelPreedit()
+{
+    Q_Q(QQuickTextInput);
+
+    if (!hasImState)
         return;
 
-    m_preeditCursor = 0;
-    m_textLayout.setPreeditArea(-1, QString());
-    m_textLayout.clearAdditionalFormats();
-    updateLayout();
+    qApp->inputMethod()->reset();
+
+    QInputMethodEvent ev;
+    QCoreApplication::sendEvent(q, &ev);
 }
 
 /*!
@@ -3113,15 +3125,19 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event)
     m_textLayout.setPreeditArea(m_cursor, event->preeditString());
 #endif //QT_NO_IM
     const int oldPreeditCursor = m_preeditCursor;
+    const bool oldCursorVisible = cursorVisible;
     m_preeditCursor = event->preeditString().length();
-    m_hideCursor = false;
+    hasImState = !event->preeditString().isEmpty();
+    cursorVisible = true;
     QList<QTextLayout::FormatRange> formats;
     for (int i = 0; i < event->attributes().size(); ++i) {
         const QInputMethodEvent::Attribute &a = event->attributes().at(i);
         if (a.type == QInputMethodEvent::Cursor) {
+            hasImState = true;
             m_preeditCursor = a.start;
-            m_hideCursor = !a.length;
+            cursorVisible = a.length != 0;
         } else if (a.type == QInputMethodEvent::TextFormat) {
+            hasImState = true;
             QTextCharFormat f = qvariant_cast<QTextFormat>(a.value).toCharFormat();
             if (f.isValid()) {
                 QTextLayout::FormatRange o;
@@ -3144,6 +3160,9 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event)
     if (isGettingInput)
         finishChange(priorState);
 
+    if (cursorVisible != oldCursorVisible)
+        emit q->cursorVisibleChanged(cursorVisible);
+
     if (selectionChange) {
         emit q->selectionChanged();
         q->updateInputMethod(Qt::ImCursorRectangle | Qt::ImAnchorPosition
index 74efdcf..1bc2cf5 100644 (file)
@@ -115,7 +115,7 @@ public:
         , selectPressed(false)
         , textLayoutDirty(true)
         , persistentSelection(false)
-        , m_hideCursor(false)
+        , hasImState(false)
         , m_separator(0)
         , m_readOnly(0)
         , m_textDirty(0)
@@ -245,7 +245,7 @@ public:
     bool selectPressed:1;
     bool textLayoutDirty:1;
     bool persistentSelection:1;
-    bool m_hideCursor : 1; // used to hide the m_cursor inside preedit areas
+    bool hasImState : 1;
     bool m_separator : 1;
     bool m_readOnly : 1;
     bool m_textDirty : 1;
@@ -319,6 +319,7 @@ public:
 #endif
 
     void commitPreedit();
+    void cancelPreedit();
 
     Qt::CursorMoveStyle cursorMoveStyle() const { return m_textLayout.cursorMoveStyle(); }
     void setCursorMoveStyle(Qt::CursorMoveStyle style) { m_textLayout.setCursorMoveStyle(style); }
index 9173a2b..dd9aa0a 100644 (file)
@@ -1999,15 +1999,16 @@ void tst_qquicktextedit::cursorDelegate()
 
 void tst_qquicktextedit::cursorVisible()
 {
+    QQuickTextEdit edit;
+    edit.componentComplete();
+    QSignalSpy spy(&edit, SIGNAL(cursorVisibleChanged(bool)));
+
     QQuickView view(testFileUrl("cursorVisible.qml"));
     view.show();
     view.requestActivateWindow();
     QTest::qWaitForWindowShown(&view);
     QTRY_COMPARE(&view, qGuiApp->focusWindow());
 
-    QQuickTextEdit edit;
-    QSignalSpy spy(&edit, SIGNAL(cursorVisibleChanged(bool)));
-
     QCOMPARE(edit.isCursorVisible(), false);
 
     edit.setCursorVisible(true);
@@ -2034,7 +2035,7 @@ void tst_qquicktextedit::cursorVisible()
     QCOMPARE(edit.isCursorVisible(), true);
     QCOMPARE(spy.count(), 5);
 
-    QQuickView alternateView;
+    QWindow alternateView;
     alternateView.show();
     alternateView.requestActivateWindow();
     QTest::qWaitForWindowShown(&alternateView);
@@ -2046,6 +2047,47 @@ void tst_qquicktextedit::cursorVisible()
     QTest::qWaitForWindowShown(&view);
     QCOMPARE(edit.isCursorVisible(), true);
     QCOMPARE(spy.count(), 7);
+
+    {   // Cursor attribute with 0 length hides cursor.
+        QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
+        QCoreApplication::sendEvent(&edit, &ev);
+    }
+    QCOMPARE(edit.isCursorVisible(), false);
+    QCOMPARE(spy.count(), 8);
+
+    {   // Cursor attribute with non zero length shows cursor.
+        QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant()));
+        QCoreApplication::sendEvent(&edit, &ev);
+    }
+    QCOMPARE(edit.isCursorVisible(), true);
+    QCOMPARE(spy.count(), 9);
+
+
+    {   // If the cursor is hidden by the input method and the text is changed it should be visible again.
+        QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
+        QCoreApplication::sendEvent(&edit, &ev);
+    }
+    QCOMPARE(edit.isCursorVisible(), false);
+    QCOMPARE(spy.count(), 10);
+
+    edit.setText("something");
+    QCOMPARE(edit.isCursorVisible(), true);
+    QCOMPARE(spy.count(), 11);
+
+    {   // If the cursor is hidden by the input method and the cursor position is changed it should be visible again.
+        QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
+        QCoreApplication::sendEvent(&edit, &ev);
+    }
+    QCOMPARE(edit.isCursorVisible(), false);
+    QCOMPARE(spy.count(), 12);
+
+    edit.setCursorPosition(5);
+    QCOMPARE(edit.isCursorVisible(), true);
+    QCOMPARE(spy.count(), 13);
 }
 
 void tst_qquicktextedit::delegateLoading_data()
@@ -2682,6 +2724,74 @@ void tst_qquicktextedit::inputMethodComposing()
     }
     QCOMPARE(edit->isInputMethodComposing(), false);
     QCOMPARE(spy.count(), 2);
+
+    // Changing the text while not composing doesn't alter the composing state.
+    edit->setText(text.mid(0, 16));
+    QCOMPARE(edit->isInputMethodComposing(), false);
+    QCOMPARE(spy.count(), 2);
+
+    {
+        QInputMethodEvent event(text.mid(16), QList<QInputMethodEvent::Attribute>());
+        QGuiApplication::sendEvent(edit, &event);
+    }
+    QCOMPARE(edit->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 3);
+
+    // Changing the text while composing cancels composition.
+    edit->setText(text.mid(0, 12));
+    QCOMPARE(edit->isInputMethodComposing(), false);
+    QCOMPARE(spy.count(), 4);
+
+    {   // Preedit cursor positioned outside (empty) preedit; composing.
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, -2, 1, QVariant()));
+        QGuiApplication::sendEvent(edit, &event);
+    }
+    QCOMPARE(edit->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 5);
+
+    {   // Cursor hidden; composing
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
+        QGuiApplication::sendEvent(edit, &event);
+    }
+    QCOMPARE(edit->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 5);
+
+    {   // Default cursor attributes; composing.
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant()));
+        QGuiApplication::sendEvent(edit, &event);
+    }
+    QCOMPARE(edit->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 5);
+
+    {   // Selections are persisted: not composing
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 2, 4, QVariant()));
+        QGuiApplication::sendEvent(edit, &event);
+    }
+    QCOMPARE(edit->isInputMethodComposing(), false);
+    QCOMPARE(spy.count(), 6);
+
+    edit->setCursorPosition(0);
+
+    {   // Formatting applied; composing.
+        QTextCharFormat format;
+        format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 2, 4, format));
+        QGuiApplication::sendEvent(edit, &event);
+    }
+    QCOMPARE(edit->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 7);
+
+    {
+        QInputMethodEvent event;
+        QGuiApplication::sendEvent(edit, &event);
+    }
+    QCOMPARE(edit->isInputMethodComposing(), false);
+    QCOMPARE(spy.count(), 8);
 }
 
 void tst_qquicktextedit::cursorRectangleSize()
index 1c44380..1102951 100644 (file)
@@ -2366,16 +2366,16 @@ void tst_qquicktextinput::cursorDelegate()
 
 void tst_qquicktextinput::cursorVisible()
 {
+    QQuickTextInput input;
+    input.componentComplete();
+    QSignalSpy spy(&input, SIGNAL(cursorVisibleChanged(bool)));
+
     QQuickView view(testFileUrl("cursorVisible.qml"));
     view.show();
     view.requestActivateWindow();
     QTest::qWaitForWindowShown(&view);
     QTRY_COMPARE(&view, qGuiApp->focusWindow());
 
-    QQuickTextInput input;
-    input.componentComplete();
-    QSignalSpy spy(&input, SIGNAL(cursorVisibleChanged(bool)));
-
     QCOMPARE(input.isCursorVisible(), false);
 
     input.setCursorVisible(true);
@@ -2414,6 +2414,46 @@ void tst_qquicktextinput::cursorVisible()
     QTest::qWaitForWindowShown(&view);
     QCOMPARE(input.isCursorVisible(), true);
     QCOMPARE(spy.count(), 7);
+
+    {   // Cursor attribute with 0 length hides cursor.
+        QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
+        QCoreApplication::sendEvent(&input, &ev);
+    }
+    QCOMPARE(input.isCursorVisible(), false);
+    QCOMPARE(spy.count(), 8);
+
+    {   // Cursor attribute with non zero length shows cursor.
+        QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant()));
+        QCoreApplication::sendEvent(&input, &ev);
+    }
+    QCOMPARE(input.isCursorVisible(), true);
+    QCOMPARE(spy.count(), 9);
+
+    {   // If the cursor is hidden by the input method and the text is changed it should be visible again.
+        QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
+        QCoreApplication::sendEvent(&input, &ev);
+    }
+    QCOMPARE(input.isCursorVisible(), false);
+    QCOMPARE(spy.count(), 10);
+
+    input.setText("something");
+    QCOMPARE(input.isCursorVisible(), true);
+    QCOMPARE(spy.count(), 11);
+
+    {   // If the cursor is hidden by the input method and the cursor position is changed it should be visible again.
+        QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
+        QCoreApplication::sendEvent(&input, &ev);
+    }
+    QCOMPARE(input.isCursorVisible(), false);
+    QCOMPARE(spy.count(), 12);
+
+    input.setCursorPosition(5);
+    QCOMPARE(input.isCursorVisible(), true);
+    QCOMPARE(spy.count(), 13);
 }
 
 void tst_qquicktextinput::cursorRectangle_data()
@@ -3280,6 +3320,75 @@ void tst_qquicktextinput::inputMethodComposing()
     }
     QCOMPARE(input->isInputMethodComposing(), false);
     QCOMPARE(spy.count(), 2);
+
+    // Changing the text while not composing doesn't alter the composing state.
+    input->setText(text.mid(0, 16));
+    QCOMPARE(input->isInputMethodComposing(), false);
+    QCOMPARE(spy.count(), 2);
+
+    {
+        QInputMethodEvent event(text.mid(16), QList<QInputMethodEvent::Attribute>());
+        QGuiApplication::sendEvent(input, &event);
+    }
+    QCOMPARE(input->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 3);
+
+    // Changing the text while composing cancels composition.
+    input->setText(text.mid(0, 12));
+    QCOMPARE(input->isInputMethodComposing(), false);
+    QCOMPARE(spy.count(), 4);
+
+    {   // Preedit cursor positioned outside (empty) preedit; composing.
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, -2, 1, QVariant()));
+        QGuiApplication::sendEvent(input, &event);
+    }
+    QCOMPARE(input->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 5);
+
+
+    {   // Cursor hidden; composing
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
+        QGuiApplication::sendEvent(input, &event);
+    }
+    QCOMPARE(input->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 5);
+
+    {   // Default cursor attributes; composing.
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant()));
+        QGuiApplication::sendEvent(input, &event);
+    }
+    QCOMPARE(input->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 5);
+
+    {   // Selections are persisted: not composing
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, -5, 4, QVariant()));
+        QGuiApplication::sendEvent(input, &event);
+    }
+    QCOMPARE(input->isInputMethodComposing(), false);
+    QCOMPARE(spy.count(), 6);
+
+    input->setCursorPosition(12);
+
+    {   // Formatting applied; composing.
+        QTextCharFormat format;
+        format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
+        QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
+                << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, -5, 4, format));
+        QGuiApplication::sendEvent(input, &event);
+    }
+    QCOMPARE(input->isInputMethodComposing(), true);
+    QCOMPARE(spy.count(), 7);
+
+    {
+        QInputMethodEvent event;
+        QGuiApplication::sendEvent(input, &event);
+    }
+    QCOMPARE(input->isInputMethodComposing(), false);
+    QCOMPARE(spy.count(), 8);
 }
 
 void tst_qquicktextinput::inputMethodUpdate()