#ifndef QT_NO_TEXTCONTROL
+#include <qcoreapplication.h>
#include <qfont.h>
#include <qpainter.h>
#include <qevent.h>
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)
{
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();
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;
}
}
+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);
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)
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;
bool isPreediting() const;
void commitPreedit();
+ void cancelPreedit();
QPointF trippleClickPoint;
QPointF mousePressPos;
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 &);
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);
}
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)),
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()
#include <private/qqmlglobal_p.h>
+#include <QtCore/qcoreapplication.h>
#include <QtQml/qqmlinfo.h>
#include <QtGui/qevent.h>
#include <QTextBoundaryFinder>
Q_D(QQuickTextInput);
if (s == text())
return;
- if (d->composeMode())
- qApp->inputMethod()->reset();
+
+ d->cancelPreedit();
d->internalSetText(s, -1, false);
}
{
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);
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 {
if (!ev->isAccepted())
QQuickImplicitSizeItem::inputMethodEvent(ev);
- if (wasComposing != (d->m_textLayout.preeditAreaText().length() > 0))
+ if (wasComposing != d->hasImState)
emit inputMethodComposingChanged();
}
bool QQuickTextInput::isInputMethodComposing() const
{
Q_D(const QQuickTextInput);
- return d->preeditAreaText().length() > 0;
+ return d->hasImState;
}
void QQuickTextInputPrivate::init()
*/
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);
}
/*!
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;
if (isGettingInput)
finishChange(priorState);
+ if (cursorVisible != oldCursorVisible)
+ emit q->cursorVisibleChanged(cursorVisible);
+
if (selectionChange) {
emit q->selectionChanged();
q->updateInputMethod(Qt::ImCursorRectangle | Qt::ImAnchorPosition
, selectPressed(false)
, textLayoutDirty(true)
, persistentSelection(false)
- , m_hideCursor(false)
+ , hasImState(false)
, m_separator(0)
, m_readOnly(0)
, m_textDirty(0)
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;
#endif
void commitPreedit();
+ void cancelPreedit();
Qt::CursorMoveStyle cursorMoveStyle() const { return m_textLayout.cursorMoveStyle(); }
void setCursorMoveStyle(Qt::CursorMoveStyle style) { m_textLayout.setCursorMoveStyle(style); }
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);
QCOMPARE(edit.isCursorVisible(), true);
QCOMPARE(spy.count(), 5);
- QQuickView alternateView;
+ QWindow alternateView;
alternateView.show();
alternateView.requestActivateWindow();
QTest::qWaitForWindowShown(&alternateView);
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()
}
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()
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);
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()
}
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()