aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/quick/items/qquicktextinput.cpp133
-rw-r--r--src/quick/items/qquicktextinput_p_p.h5
-rw-r--r--tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp146
3 files changed, 245 insertions, 39 deletions
diff --git a/src/quick/items/qquicktextinput.cpp b/src/quick/items/qquicktextinput.cpp
index bb38e516e0..4dbd39e826 100644
--- a/src/quick/items/qquicktextinput.cpp
+++ b/src/quick/items/qquicktextinput.cpp
@@ -2815,7 +2815,7 @@ void QQuickTextInputPrivate::cancelPreedit()
void QQuickTextInputPrivate::backspace()
{
int priorState = m_undoState;
- if (hasSelectedText()) {
+ if (separateSelection()) {
removeSelectedText();
} else if (m_cursor) {
--m_cursor;
@@ -2848,7 +2848,7 @@ void QQuickTextInputPrivate::backspace()
void QQuickTextInputPrivate::del()
{
int priorState = m_undoState;
- if (hasSelectedText()) {
+ if (separateSelection()) {
removeSelectedText();
} else {
int n = m_textLayout.nextCursorPosition(m_cursor) - m_cursor;
@@ -2868,7 +2868,8 @@ void QQuickTextInputPrivate::del()
void QQuickTextInputPrivate::insert(const QString &newText)
{
int priorState = m_undoState;
- removeSelectedText();
+ if (separateSelection())
+ removeSelectedText();
internalInsert(newText);
finishChange(priorState);
}
@@ -2881,6 +2882,7 @@ void QQuickTextInputPrivate::insert(const QString &newText)
void QQuickTextInputPrivate::clear()
{
int priorState = m_undoState;
+ separateSelection();
m_selstart = 0;
m_selend = m_text.length();
removeSelectedText();
@@ -3031,6 +3033,7 @@ void QQuickTextInputPrivate::processInputMethodEvent(QInputMethodEvent *event)
if (isGettingInput) {
// If any text is being input, remove selected text.
priorState = m_undoState;
+ separateSelection();
if (m_echoMode == QQuickTextInput::PasswordEchoOnEdit && !m_passwordEchoEditing) {
updatePasswordEchoEditing(true);
m_selstart = 0;
@@ -3303,8 +3306,7 @@ void QQuickTextInputPrivate::internalInsert(const QString &s)
if (delay > 0)
m_passwordEchoTimer.start(delay, q);
}
- if (hasSelectedText())
- addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend));
+ Q_ASSERT(!hasSelectedText()); // insert(), processInputMethodEvent() call removeSelectedText() first.
if (m_maskData) {
QString ms = maskString(m_cursor, s);
for (int i = 0; i < (int) ms.length(); ++i) {
@@ -3341,8 +3343,7 @@ void QQuickTextInputPrivate::internalDelete(bool wasBackspace)
{
if (m_cursor < (int) m_text.length()) {
cancelPasswordEchoTimer();
- if (hasSelectedText())
- addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend));
+ Q_ASSERT(!hasSelectedText()); // del(), backspace() call removeSelectedText() first.
addCommand(Command((CommandType)((m_maskData ? 2 : 0) + (wasBackspace ? Remove : Delete)),
m_cursor, m_text.at(m_cursor), -1, -1));
if (m_maskData) {
@@ -3368,9 +3369,7 @@ void QQuickTextInputPrivate::removeSelectedText()
{
if (m_selstart < m_selend && m_selend <= (int) m_text.length()) {
cancelPasswordEchoTimer();
- separate();
int i ;
- addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend));
if (m_selstart <= m_cursor && m_cursor < m_selend) {
// cursor is within the selection. Split up the commands
// to be able to restore the correct cursor position
@@ -3399,6 +3398,25 @@ void QQuickTextInputPrivate::removeSelectedText()
/*!
\internal
+ Adds the current selection to the undo history.
+
+ Returns true if there is a current selection and false otherwise.
+*/
+
+bool QQuickTextInputPrivate::separateSelection()
+{
+ if (hasSelectedText()) {
+ separate();
+ addCommand(Command(SetSelection, m_cursor, 0, m_selstart, m_selend));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/*!
+ \internal
+
Parses the input mask specified by \a maskFields to generate
the mask data used to handle input masks.
*/
@@ -3797,11 +3815,14 @@ void QQuickTextInputPrivate::internalUndo(int until)
}
if (until < 0 && m_undoState) {
Command& next = m_history[m_undoState-1];
- if (next.type != cmd.type && next.type < RemoveSelection
- && (cmd.type < RemoveSelection || next.type == Separator))
+ if (next.type != cmd.type
+ && next.type < RemoveSelection
+ && (cmd.type < RemoveSelection || next.type == Separator)) {
break;
+ }
}
}
+ separate();
m_textDirty = true;
}
@@ -3839,9 +3860,12 @@ void QQuickTextInputPrivate::internalRedo()
}
if (m_undoState < (int)m_history.size()) {
Command& next = m_history[m_undoState];
- if (next.type != cmd.type && cmd.type < RemoveSelection && next.type != Separator
- && (next.type < RemoveSelection || cmd.type == Separator))
+ if (next.type != cmd.type
+ && cmd.type < RemoveSelection
+ && next.type != Separator
+ && (next.type < RemoveSelection || cmd.type == Separator)) {
break;
+ }
}
}
m_textDirty = true;
@@ -3937,16 +3961,12 @@ void QQuickTextInput::timerEvent(QTimerEvent *event)
void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event)
{
Q_Q(QQuickTextInput);
- bool inlineCompletionAccepted = false;
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
if (hasAcceptableInput(m_text) || fixup()) {
emit q->accepted();
}
- if (inlineCompletionAccepted)
- event->accept();
- else
- event->ignore();
+ event->ignore();
return;
}
@@ -3996,11 +4016,8 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event)
}
}
else if (event == QKeySequence::DeleteEndOfLine) {
- if (!m_readOnly) {
- setSelection(m_cursor, end());
- copy();
- del();
- }
+ if (!m_readOnly)
+ deleteEndOfLine();
}
#endif //QT_NO_CLIPBOARD
else if (event == QKeySequence::MoveToStartOfLine || event == QKeySequence::MoveToStartOfBlock) {
@@ -4065,16 +4082,12 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event)
del();
}
else if (event == QKeySequence::DeleteEndOfWord) {
- if (!m_readOnly) {
- cursorWordForward(true);
- del();
- }
+ if (!m_readOnly)
+ deleteEndOfWord();
}
else if (event == QKeySequence::DeleteStartOfWord) {
- if (!m_readOnly) {
- cursorWordBackward(true);
- del();
- }
+ if (!m_readOnly)
+ deleteStartOfWord();
}
#endif // QT_NO_SHORTCUT
else {
@@ -4082,10 +4095,8 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event)
if (event->modifiers() & Qt::ControlModifier) {
switch (event->key()) {
case Qt::Key_Backspace:
- if (!m_readOnly) {
- cursorWordBackward(true);
- del();
- }
+ if (!m_readOnly)
+ deleteStartOfWord();
break;
default:
if (!handled)
@@ -4125,6 +4136,58 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event)
event->accept();
}
+/*!
+ \internal
+
+ Deletes the portion of the word before the current cursor position.
+*/
+
+void QQuickTextInputPrivate::deleteStartOfWord()
+{
+ int priorState = m_undoState;
+ Command cmd(SetSelection, m_cursor, 0, m_selstart, m_selend);
+ separate();
+ cursorWordBackward(true);
+ addCommand(cmd);
+ removeSelectedText();
+ finishChange(priorState);
+}
+
+/*!
+ \internal
+
+ Deletes the portion of the word after the current cursor position.
+*/
+
+void QQuickTextInputPrivate::deleteEndOfWord()
+{
+ int priorState = m_undoState;
+ Command cmd(SetSelection, m_cursor, 0, m_selstart, m_selend);
+ separate();
+ cursorWordForward(true);
+ // moveCursor (sometimes) calls separate() so we need to add the command after that so the
+ // cursor position and selection are restored in the same undo operation as the remove.
+ addCommand(cmd);
+ removeSelectedText();
+ finishChange(priorState);
+}
+
+/*!
+ \internal
+
+ Deletes all text from the cursor position to the end of the line.
+*/
+
+void QQuickTextInputPrivate::deleteEndOfLine()
+{
+ int priorState = m_undoState;
+ Command cmd(SetSelection, m_cursor, 0, m_selstart, m_selend);
+ separate();
+ setSelection(m_cursor, end());
+ addCommand(cmd);
+ removeSelectedText();
+ finishChange(priorState);
+}
QT_END_NAMESPACE
diff --git a/src/quick/items/qquicktextinput_p_p.h b/src/quick/items/qquicktextinput_p_p.h
index 0cc0846a5e..e147c17d96 100644
--- a/src/quick/items/qquicktextinput_p_p.h
+++ b/src/quick/items/qquicktextinput_p_p.h
@@ -435,6 +435,11 @@ private:
inline void separate() { m_separator = true; }
+ bool separateSelection();
+ void deleteStartOfWord();
+ void deleteEndOfWord();
+ void deleteEndOfLine();
+
enum ValidatorState {
#ifndef QT_NO_VALIDATOR
InvalidInput = QValidator::Invalid,
diff --git a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp
index a4d1920350..7cc9ddb8f2 100644
--- a/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp
+++ b/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp
@@ -192,6 +192,8 @@ private slots:
void undo_keypressevents_data();
void undo_keypressevents();
+ void backspaceSurrogatePairs();
+
void QTBUG_19956();
void QTBUG_19956_data();
void QTBUG_19956_regexp();
@@ -236,8 +238,8 @@ Q_DECLARE_METATYPE(KeyList)
void tst_qquicktextinput::simulateKeys(QWindow *window, const QList<Key> &keys)
{
for (int i = 0; i < keys.count(); ++i) {
- const int key = keys.at(i).first;
- const int modifiers = key & Qt::KeyboardModifierMask;
+ const int key = keys.at(i).first & ~Qt::KeyboardModifierMask;
+ const int modifiers = keys.at(i).first & Qt::KeyboardModifierMask;
const QString text = !keys.at(i).second.isNull() ? QString(keys.at(i).second) : QString();
QKeyEvent press(QEvent::KeyPress, Qt::Key(key), Qt::KeyboardModifiers(modifiers), text);
@@ -5138,7 +5140,7 @@ void tst_qquicktextinput::undo_keypressevents_data()
<< QKeySequence::MoveToStartOfLine
// selecting 'AB'
<< (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
- << Qt::Key_Delete
+ << Qt::Key_Backspace
<< QKeySequence::Undo
<< Qt::Key_Right
<< (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
@@ -5213,7 +5215,6 @@ void tst_qquicktextinput::undo_keypressevents_data()
<< "ABC";
expectedString << "ABC";
- // for versions previous to 3.2 we overwrite needed two undo operations
expectedString << "123";
QTest::newRow("Inserts,moving,selection and overwriting") << keys << expectedString;
@@ -5230,6 +5231,106 @@ void tst_qquicktextinput::undo_keypressevents_data()
expectedString << QString();
QTest::newRow("Insert,undo,redo") << keys << expectedString;
+ } {
+ KeyList keys;
+ QStringList expectedString;
+
+ keys << "hello world"
+ << (Qt::Key_Backspace | Qt::ControlModifier)
+ << QKeySequence::Undo
+ << QKeySequence::Redo
+ << "hello";
+
+ expectedString
+ << "hello hello"
+ << "hello "
+ << "hello world"
+ << QString();
+
+ QTest::newRow("Insert,delete previous word,undo,redo,insert") << keys << expectedString;
+ } {
+ KeyList keys;
+ QStringList expectedString;
+
+ keys << "hello world"
+ << QKeySequence::SelectPreviousWord
+ << (Qt::Key_Backspace)
+ << QKeySequence::Undo
+ << "hello";
+
+ expectedString
+ << "hello hello"
+ << "hello world"
+ << QString();
+
+ QTest::newRow("Insert,select previous word,remove,undo,insert") << keys << expectedString;
+ } {
+ KeyList keys;
+ QStringList expectedString;
+
+ keys << "hello world"
+ << QKeySequence::DeleteStartOfWord
+ << QKeySequence::Undo
+ << "hello";
+
+ expectedString
+ << "hello worldhello"
+ << "hello world"
+ << QString();
+
+ QTest::newRow("Insert,delete previous word,undo,insert") << keys << expectedString;
+ } {
+ KeyList keys;
+ QStringList expectedString;
+
+ keys << "hello world"
+ << QKeySequence::MoveToPreviousWord
+ << QKeySequence::DeleteEndOfWord
+ << QKeySequence::Undo
+ << "hello";
+
+ expectedString
+ << "hello helloworld"
+ << "hello world"
+ << QString();
+
+ QTest::newRow("Insert,move,delete next word,undo,insert") << keys << expectedString;
+ }
+ if (!QKeySequence(QKeySequence::DeleteEndOfLine).isEmpty()) { // X11 only.
+ KeyList keys;
+ QStringList expectedString;
+
+ keys << "hello world"
+ << QKeySequence::MoveToStartOfLine
+ << Qt::Key_Right
+ << QKeySequence::DeleteEndOfLine
+ << QKeySequence::Undo
+ << "hello";
+
+ expectedString
+ << "hhelloello world"
+ << "hello world"
+ << QString();
+
+ QTest::newRow("Insert,move,delete end of line,undo,insert") << keys << expectedString;
+ } {
+ KeyList keys;
+ QStringList expectedString;
+
+ keys << "hello world"
+ << QKeySequence::MoveToPreviousWord
+ << (Qt::Key_Left | Qt::ShiftModifier)
+ << (Qt::Key_Left | Qt::ShiftModifier)
+ << QKeySequence::DeleteEndOfWord
+ << QKeySequence::Undo
+ << "hello";
+
+ expectedString
+ << "hellhelloworld"
+ << "hello world"
+ << QString();
+
+ QTest::newRow("Insert,move,select,delete next word,undo,insert") << keys << expectedString;
}
}
@@ -5260,6 +5361,43 @@ void tst_qquicktextinput::undo_keypressevents()
QVERIFY(textInput->text().isEmpty());
}
+void tst_qquicktextinput::backspaceSurrogatePairs()
+{
+ // Test backspace, and delete remove both characters in a surrogate pair.
+ static const quint16 textData[] = { 0xd800, 0xdf00, 0xd800, 0xdf01, 0xd800, 0xdf02, 0xd800, 0xdf03, 0xd800, 0xdf04 };
+ const QString text = QString::fromUtf16(textData, lengthOf(textData));
+
+ QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
+ QQmlComponent textInputComponent(&engine);
+ textInputComponent.setData(componentStr.toLatin1(), QUrl());
+ QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
+ QVERIFY(textInput != 0);
+ textInput->setText(text);
+ textInput->setCursorPosition(text.length());
+
+ QQuickWindow window;
+ textInput->setParentItem(window.contentItem());
+ window.show();
+ window.requestActivateWindow();
+ QTest::qWaitForWindowShown(&window);
+ QTRY_COMPARE(QGuiApplication::focusWindow(), &window);
+
+ for (int i = text.length(); i >= 0; i -= 2) {
+ QCOMPARE(textInput->text(), text.mid(0, i));
+ QTest::keyClick(&window, Qt::Key_Backspace, Qt::NoModifier);
+ }
+ QCOMPARE(textInput->text(), QString());
+
+ textInput->setText(text);
+ textInput->setCursorPosition(0);
+
+ for (int i = 0; i < text.length(); i += 2) {
+ QCOMPARE(textInput->text(), text.mid(i));
+ QTest::keyClick(&window, Qt::Key_Delete, Qt::NoModifier);
+ }
+ QCOMPARE(textInput->text(), QString());
+}
+
void tst_qquicktextinput::QTBUG_19956()
{
QFETCH(QString, url);