Add overwriteMode to QML TextEdit and QML TextInput

Overwrite mode was added to QML TextEdit and QML TextInput to match the
functionality provided by QTextEdit. Tests were updated as well to
ensure the mode functions as expected.

Task-number: QTBUG-26513
Change-Id: I1769159b298220107b09f9f13dc3af5f274715cc
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@theqtcompany.com>
This commit is contained in:
Dan Cape 2015-08-28 11:11:57 -04:00
parent bf3de80424
commit 9456832163
9 changed files with 238 additions and 2 deletions

View File

@ -44,6 +44,7 @@
#include <qcoreapplication.h>
#include <qfont.h>
#include <qfontmetrics.h>
#include <qevent.h>
#include <qdebug.h>
#include <qdrag.h>
@ -965,6 +966,14 @@ process:
{
QString text = e->text();
if (!text.isEmpty() && (text.at(0).isPrint() || text.at(0) == QLatin1Char('\t'))) {
if (overwriteMode
// no need to call deleteChar() if we have a selection, insertText
// does it already
&& !cursor.hasSelection()
&& !cursor.atBlockEnd()) {
cursor.deleteChar();
}
cursor.insertText(text);
selectionChanged();
} else {
@ -1007,6 +1016,12 @@ QRectF QQuickTextControlPrivate::rectForPosition(int position) const
if (line.isValid()) {
qreal x = line.cursorToX(relativePos);
qreal w = 0;
if (overwriteMode) {
if (relativePos < line.textLength() - line.textStart())
w = line.cursorToX(relativePos + 1) - x;
else
w = QFontMetrics(block.layout()->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw()
}
r = QRectF(layoutPos.x() + x, layoutPos.y() + line.y(), textCursorWidth + w, line.height());
} else {
r = QRectF(layoutPos.x(), layoutPos.y(), textCursorWidth, 10); // #### correct height
@ -1483,6 +1498,21 @@ bool QQuickTextControl::hasImState() const
return d->hasImState;
}
bool QQuickTextControl::overwriteMode() const
{
Q_D(const QQuickTextControl);
return d->overwriteMode;
}
void QQuickTextControl::setOverwriteMode(bool overwrite)
{
Q_D(QQuickTextControl);
if (d->overwriteMode == overwrite)
return;
d->overwriteMode = overwrite;
emit overwriteModeChanged(overwrite);
}
bool QQuickTextControl::cursorVisible() const
{
Q_D(const QQuickTextControl);

View File

@ -94,6 +94,8 @@ public:
#endif
bool hasImState() const;
bool overwriteMode() const;
void setOverwriteMode(bool overwrite);
bool cursorVisible() const;
void setCursorVisible(bool visible);
QRectF cursorRect(const QTextCursor &cursor) const;
@ -148,6 +150,7 @@ Q_SIGNALS:
void copyAvailable(bool b);
void selectionChanged();
void cursorPositionChanged();
void overwriteModeChanged(bool overwriteMode);
// control signals
void updateCursorRequest();

View File

@ -1583,6 +1583,32 @@ bool QQuickTextEdit::event(QEvent *event)
return QQuickImplicitSizeItem::event(event);
}
/*!
\qmlproperty bool QtQuick::TextEdit::overwriteMode
\since 5.8
Whether text entered by the user will overwrite existing text.
As with many text editors, the text editor widget can be configured
to insert or overwrite existing text with new text entered by the user.
If this property is \c true, existing text is overwritten, character-for-character
by new text; otherwise, text is inserted at the cursor position, displacing
existing text.
By default, this property is \c false (new text does not overwrite existing text).
*/
bool QQuickTextEdit::overwriteMode() const
{
Q_D(const QQuickTextEdit);
return d->control->overwriteMode();
}
void QQuickTextEdit::setOverwriteMode(bool overwrite)
{
Q_D(QQuickTextEdit);
d->control->setOverwriteMode(overwrite);
}
/*!
\overload
Handles the given key \a event.
@ -2186,6 +2212,7 @@ void QQuickTextEditPrivate::init()
qmlobject_connect(control, QQuickTextControl, SIGNAL(cursorRectangleChanged()), q, QQuickTextEdit, SLOT(moveCursorDelegate()));
qmlobject_connect(control, QQuickTextControl, SIGNAL(linkActivated(QString)), q, QQuickTextEdit, SIGNAL(linkActivated(QString)));
qmlobject_connect(control, QQuickTextControl, SIGNAL(linkHovered(QString)), q, QQuickTextEdit, SIGNAL(linkHovered(QString)));
qmlobject_connect(control, QQuickTextControl, SIGNAL(overwriteModeChanged(bool)), q, QQuickTextEdit, SIGNAL(overwriteModeChanged(bool)));
qmlobject_connect(control, QQuickTextControl, SIGNAL(textChanged()), q, QQuickTextEdit, SLOT(q_textChanged()));
qmlobject_connect(control, QQuickTextControl, SIGNAL(preeditTextChanged()), q, QQuickTextEdit, SIGNAL(preeditTextChanged()));
#ifndef QT_NO_CLIPBOARD

View File

@ -86,6 +86,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextEdit : public QQuickImplicitSizeItem
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
Q_PROPERTY(QRectF cursorRectangle READ cursorRectangle NOTIFY cursorRectangleChanged)
Q_PROPERTY(QQmlComponent* cursorDelegate READ cursorDelegate WRITE setCursorDelegate NOTIFY cursorDelegateChanged)
Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode NOTIFY overwriteModeChanged)
Q_PROPERTY(int selectionStart READ selectionStart NOTIFY selectionStartChanged)
Q_PROPERTY(int selectionEnd READ selectionEnd NOTIFY selectionEndChanged)
Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectedTextChanged)
@ -199,6 +200,9 @@ public:
QQmlComponent* cursorDelegate() const;
void setCursorDelegate(QQmlComponent*);
bool overwriteMode() const;
void setOverwriteMode(bool overwrite);
int selectionStart() const;
int selectionEnd() const;
@ -313,6 +317,7 @@ Q_SIGNALS:
void readOnlyChanged(bool isReadOnly);
void cursorVisibleChanged(bool isCursorVisible);
void cursorDelegateChanged();
void overwriteModeChanged(bool overwriteMode);
void activeFocusOnPressChanged(bool activeFocusOnPressed);
void persistentSelectionChanged(bool isPersistentSelection);
void textMarginChanged(qreal textMargin);

View File

@ -813,7 +813,14 @@ QRectF QQuickTextInput::cursorRectangle() const
return QRectF();
qreal x = l.cursorToX(c) - d->hscroll + leftPadding();
qreal y = l.y() - d->vscroll + topPadding();
return QRectF(x, y, 1, l.height());
qreal w = 1;
if (d->overwriteMode) {
if (c < text().length())
w = l.cursorToX(c + 1) - x;
else
w = QFontMetrics(font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw()
}
return QRectF(x, y, w, l.height());
}
/*!
@ -1263,7 +1270,14 @@ QRectF QQuickTextInput::positionToRectangle(int pos) const
return QRectF();
qreal x = l.cursorToX(pos) - d->hscroll;
qreal y = l.y() - d->vscroll;
return QRectF(x, y, 1, l.height());
qreal w = 1;
if (d->overwriteMode) {
if (pos < text().length())
w = l.cursorToX(pos + 1) - x;
else
w = QFontMetrics(font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw()
}
return QRectF(x, y, w, l.height());
}
/*!
@ -1345,6 +1359,36 @@ int QQuickTextInputPrivate::positionAt(qreal x, qreal y, QTextLine::CursorPositi
return line.isValid() ? line.xToCursor(x, position) : 0;
}
/*!
\qmlproperty bool QtQuick::TextInput::overwriteMode
\since 5.8
Whether text entered by the user will overwrite existing text.
As with many text editors, the text editor widget can be configured
to insert or overwrite existing text with new text entered by the user.
If this property is \c true, existing text is overwritten, character-for-character
by new text; otherwise, text is inserted at the cursor position, displacing
existing text.
By default, this property is \c false (new text does not overwrite existing text).
*/
bool QQuickTextInput::overwriteMode() const
{
Q_D(const QQuickTextInput);
return d->overwriteMode;
}
void QQuickTextInput::setOverwriteMode(bool overwrite)
{
Q_D(QQuickTextInput);
if (d->overwriteMode == overwrite)
return;
d->overwriteMode = overwrite;
emit overwriteModeChanged(overwrite);
}
void QQuickTextInput::keyPressEvent(QKeyEvent* ev)
{
Q_D(QQuickTextInput);
@ -4409,6 +4453,14 @@ void QQuickTextInputPrivate::processKeyEvent(QKeyEvent* event)
if (unknown && !m_readOnly) {
QString t = event->text();
if (!t.isEmpty() && t.at(0).isPrint()) {
if (overwriteMode
// no need to call del() if we have a selection, insert
// does it already
&& !hasSelectedText()
&& !(m_cursor == q_func()->text().length())) {
del();
}
insert(t);
event->accept();
return;

View File

@ -79,6 +79,7 @@ class Q_QUICK_PRIVATE_EXPORT QQuickTextInput : public QQuickImplicitSizeItem
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
Q_PROPERTY(QRectF cursorRectangle READ cursorRectangle NOTIFY cursorRectangleChanged)
Q_PROPERTY(QQmlComponent *cursorDelegate READ cursorDelegate WRITE setCursorDelegate NOTIFY cursorDelegateChanged)
Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode NOTIFY overwriteModeChanged)
Q_PROPERTY(int selectionStart READ selectionStart NOTIFY selectionStartChanged)
Q_PROPERTY(int selectionEnd READ selectionEnd NOTIFY selectionEndChanged)
Q_PROPERTY(QString selectedText READ selectedText NOTIFY selectedTextChanged)
@ -245,6 +246,9 @@ public:
QQmlComponent* cursorDelegate() const;
void setCursorDelegate(QQmlComponent*);
bool overwriteMode() const;
void setOverwriteMode(bool overwrite);
bool focusOnPress() const;
void setFocusOnPress(bool);
@ -325,6 +329,7 @@ Q_SIGNALS:
void readOnlyChanged(bool isReadOnly);
void cursorVisibleChanged(bool isCursorVisible);
void cursorDelegateChanged();
void overwriteModeChanged(bool overwriteMode);
void maximumLengthChanged(int maximumLength);
void validatorChanged();
void inputMaskChanged(const QString &inputMask);

View File

@ -158,6 +158,7 @@ public:
, m_passwordEchoEditing(false)
, inLayout(false)
, requireImplicitWidth(false)
, overwriteMode(false)
{
}
@ -299,6 +300,7 @@ public:
bool m_passwordEchoEditing : 1;
bool inLayout:1;
bool requireImplicitWidth:1;
bool overwriteMode:1;
static inline QQuickTextInputPrivate *get(QQuickTextInput *t) {
return t->d_func();

View File

@ -108,6 +108,7 @@ private slots:
void selectionOnFocusOut();
void focusOnPress();
void selection();
void overwriteMode();
void isRightToLeft_data();
void isRightToLeft();
void keySelection();
@ -1466,6 +1467,74 @@ void tst_qquicktextedit::selection()
QVERIFY(textEditObject->selectedText().isNull());
}
void tst_qquicktextedit::overwriteMode()
{
QString componentStr = "import QtQuick 2.0\nTextEdit { focus: true; }";
QQmlComponent textEditComponent(&engine);
textEditComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit*>(textEditComponent.create());
QVERIFY(textEdit != 0);
QSignalSpy spy(textEdit, SIGNAL(overwriteModeChanged(bool)));
QQuickWindow window;
textEdit->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QTest::qWaitForWindowActive(&window);
QVERIFY(textEdit->hasActiveFocus());
textEdit->setOverwriteMode(true);
QCOMPARE(spy.count(), 1);
QCOMPARE(true, textEdit->overwriteMode());
textEdit->setOverwriteMode(false);
QCOMPARE(spy.count(), 2);
QCOMPARE(false, textEdit->overwriteMode());
QVERIFY(!textEdit->overwriteMode());
QString insertString = "Some first text";
for (int j = 0; j < insertString.length(); j++)
QTest::keyClick(&window, insertString.at(j).toLatin1());
QCOMPARE(textEdit->text(), QString("Some first text"));
textEdit->setOverwriteMode(true);
QCOMPARE(spy.count(), 3);
textEdit->setCursorPosition(5);
insertString = "shiny";
for (int j = 0; j < insertString.length(); j++)
QTest::keyClick(&window, insertString.at(j).toLatin1());
QCOMPARE(textEdit->text(), QString("Some shiny text"));
textEdit->setCursorPosition(textEdit->text().length());
QTest::keyClick(&window, Qt::Key_Enter);
textEdit->setOverwriteMode(false);
QCOMPARE(spy.count(), 4);
insertString = "Second paragraph";
for (int j = 0; j < insertString.length(); j++)
QTest::keyClick(&window, insertString.at(j).toLatin1());
QCOMPARE(textEdit->lineCount(), 2);
textEdit->setCursorPosition(15);
QCOMPARE(textEdit->cursorPosition(), 15);
textEdit->setOverwriteMode(true);
QCOMPARE(spy.count(), 5);
insertString = " blah";
for (int j = 0; j < insertString.length(); j++)
QTest::keyClick(&window, insertString.at(j).toLatin1());
QCOMPARE(textEdit->lineCount(), 2);
QCOMPARE(textEdit->text(), QString("Some shiny text blah\nSecond paragraph"));
}
void tst_qquicktextedit::isRightToLeft_data()
{
QTest::addColumn<QString>("text");

View File

@ -105,6 +105,7 @@ private slots:
void wrap();
void selection();
void persistentSelection();
void overwriteMode();
void isRightToLeft_data();
void isRightToLeft();
void moveCursorSelection_data();
@ -777,6 +778,48 @@ void tst_qquicktextinput::persistentSelection()
QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
}
void tst_qquicktextinput::overwriteMode()
{
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);
QSignalSpy spy(textInput, SIGNAL(overwriteModeChanged(bool)));
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QTest::qWaitForWindowActive(&window);
QVERIFY(textInput->hasActiveFocus());
textInput->setOverwriteMode(true);
QCOMPARE(spy.count(), 1);
QCOMPARE(true, textInput->overwriteMode());
textInput->setOverwriteMode(false);
QCOMPARE(spy.count(), 2);
QCOMPARE(false, textInput->overwriteMode());
QVERIFY(!textInput->overwriteMode());
QString insertString = "Some first text";
for (int j = 0; j < insertString.length(); j++)
QTest::keyClick(&window, insertString.at(j).toLatin1());
QCOMPARE(textInput->text(), QString("Some first text"));
textInput->setOverwriteMode(true);
QCOMPARE(spy.count(), 3);
textInput->setCursorPosition(5);
insertString = "shiny";
for (int j = 0; j < insertString.length(); j++)
QTest::keyClick(&window, insertString.at(j).toLatin1());
QCOMPARE(textInput->text(), QString("Some shiny text"));
}
void tst_qquicktextinput::isRightToLeft_data()
{
QTest::addColumn<QString>("text");