blob: 2e64c80b85c106f91cf2531d5f3eb9c7ebac131e [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qtest.h>
#include <QtTest/QSignalSpy>
#include "../../shared/util.h"
#include "../../shared/testhttpserver.h"
#include <private/qinputmethod_p.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlexpression.h>
#include <QFile>
#include <QtQuick/qquickview.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qstylehints.h>
#include <QtGui/qvalidator.h>
#include <QInputMethod>
#include <private/qquicktextinput_p.h>
#include <private/qquicktextinput_p_p.h>
#include <private/qquickvalidator_p.h>
#include <QDebug>
#include <QDir>
#include <math.h>
#include <qmath.h>
#ifdef Q_OS_OSX
#include <Carbon/Carbon.h>
#endif
#include "qplatformdefs.h"
#include "../../shared/platformquirks.h"
#include "../../shared/platforminputcontext.h"
Q_DECLARE_METATYPE(QQuickTextInput::SelectionMode)
Q_DECLARE_METATYPE(QQuickTextInput::EchoMode)
Q_DECLARE_METATYPE(Qt::Key)
DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
QString createExpectedFileIfNotFound(const QString& filebasename, const QImage& actual)
{
// XXX This will be replaced by some clever persistent platform image store.
QString persistent_dir = QQmlDataTest::instance()->dataDirectory();
QString arch = "unknown-architecture"; // QTest needs to help with this.
QString expectfile = persistent_dir + QDir::separator() + filebasename + QLatin1Char('-') + arch + ".png";
if (!QFile::exists(expectfile)) {
actual.save(expectfile);
qWarning() << "created" << expectfile;
}
return expectfile;
}
template <typename T> static T evaluate(QObject *scope, const QString &expression)
{
QQmlExpression expr(qmlContext(scope), scope, expression);
T result = expr.evaluate().value<T>();
if (expr.hasError())
qWarning() << expr.error().toString();
return result;
}
template<typename T, int N> int lengthOf(const T (&)[N]) { return N; }
typedef QPair<int, QChar> Key;
class tst_qquicktextinput : public QQmlDataTest
{
Q_OBJECT
public:
tst_qquicktextinput();
private slots:
void cleanup();
void text();
void width();
void font();
void color();
void wrap();
void selection();
void persistentSelection();
void overwriteMode();
void isRightToLeft_data();
void isRightToLeft();
void moveCursorSelection_data();
void moveCursorSelection();
void moveCursorSelectionSequence_data();
void moveCursorSelectionSequence();
void dragMouseSelection();
void mouseSelectionMode_data();
void mouseSelectionMode();
void mouseSelectionMode_accessors();
void selectByMouse();
void renderType();
void tripleClickSelectsAll();
void horizontalAlignment_RightToLeft();
void verticalAlignment();
void clipRect();
void boundingRect();
void positionAt();
void maxLength();
void masks();
void validators();
void inputMethods();
void signal_accepted();
void signal_editingfinished();
void signal_textEdited();
void passwordCharacter();
void cursorDelegate_data();
void cursorDelegate();
void remoteCursorDelegate();
void cursorVisible();
void cursorRectangle_data();
void cursorRectangle();
void navigation();
void navigation_RTL();
#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
void copyAndPaste();
void copyAndPasteKeySequence();
void canPasteEmpty();
void canPaste();
void middleClickPaste();
#endif
void readOnly();
void focusOnPress();
void focusOnPressOnlyOneItem();
void openInputPanel();
void setHAlignClearCache();
void focusOutClearSelection();
void focusOutNotClearSelection();
void echoMode();
void passwordEchoDelay();
void geometrySignals();
void contentSize();
void preeditAutoScroll();
void preeditCursorRectangle();
void inputContextMouseHandler();
void inputMethodComposing();
void inputMethodUpdate();
void cursorRectangleSize();
void getText_data();
void getText();
void insert_data();
void insert();
void remove_data();
void remove();
#if QT_CONFIG(shortcut)
void keySequence_data();
void keySequence();
#endif
void undo_data();
void undo();
void redo_data();
void redo();
#if QT_CONFIG(shortcut)
void undo_keypressevents_data();
void undo_keypressevents();
#endif
void clear();
void backspaceSurrogatePairs();
void QTBUG_19956();
void QTBUG_19956_data();
void QTBUG_19956_regexp();
void implicitSize_data();
void implicitSize();
void implicitSizeBinding_data();
void implicitSizeBinding();
void implicitResize_data();
void implicitResize();
void negativeDimensions();
void setInputMask_data();
void setInputMask();
void inputMask_data();
void inputMask();
void clearInputMask();
void keypress_inputMask_data();
void keypress_inputMask();
void keypress_inputMethod_inputMask();
void keypress_inputMask_withValidator_data();
void keypress_inputMask_withValidator();
void hasAcceptableInputMask_data();
void hasAcceptableInputMask();
void maskCharacter_data();
void maskCharacter();
void fixup();
void baselineOffset_data();
void baselineOffset();
void ensureVisible();
void padding();
void QTBUG_51115_readOnlyResetsSelection();
void QTBUG_77814_InsertRemoveNoSelection();
private:
void simulateKey(QWindow *, int key);
void simulateKeys(QWindow *window, const QList<Key> &keys);
#if QT_CONFIG(shortcut)
void simulateKeys(QWindow *window, const QKeySequence &sequence);
#endif
QQmlEngine engine;
QStringList standard;
QStringList colorStrings;
};
typedef QList<int> IntList;
Q_DECLARE_METATYPE(IntList)
typedef QList<Key> KeyList;
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 & ~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);
QKeyEvent release(QEvent::KeyRelease, Qt::Key(key), Qt::KeyboardModifiers(modifiers), text);
QGuiApplication::sendEvent(window, &press);
QGuiApplication::sendEvent(window, &release);
}
}
#if QT_CONFIG(shortcut)
void tst_qquicktextinput::simulateKeys(QWindow *window, const QKeySequence &sequence)
{
for (int i = 0; i < sequence.count(); ++i) {
const int key = sequence[i];
const int modifiers = key & Qt::KeyboardModifierMask;
QTest::keyClick(window, Qt::Key(key & ~modifiers), Qt::KeyboardModifiers(modifiers));
}
}
QList<Key> &operator <<(QList<Key> &keys, const QKeySequence &sequence)
{
for (int i = 0; i < sequence.count(); ++i)
keys << Key(sequence[i], QChar());
return keys;
}
#endif // QT_CONFIG(shortcut)
template <int N> QList<Key> &operator <<(QList<Key> &keys, const char (&characters)[N])
{
for (int i = 0; i < N - 1; ++i) {
int key = QTest::asciiToKey(characters[i]);
QChar character = QLatin1Char(characters[i]);
keys << Key(key, character);
}
return keys;
}
QList<Key> &operator <<(QList<Key> &keys, Qt::Key key)
{
keys << Key(key, QChar());
return keys;
}
void tst_qquicktextinput::cleanup()
{
// ensure not even skipped tests with custom input context leave it dangling
QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
inputMethodPrivate->testContext = nullptr;
}
tst_qquicktextinput::tst_qquicktextinput()
{
standard << "the quick brown fox jumped over the lazy dog"
<< "It's supercalifragisiticexpialidocious!"
<< "Hello, world!"
<< "!dlrow ,olleH"
<< " spacey text ";
colorStrings << "aliceblue"
<< "antiquewhite"
<< "aqua"
<< "darkkhaki"
<< "darkolivegreen"
<< "dimgray"
<< "palevioletred"
<< "lightsteelblue"
<< "#000000"
<< "#AAAAAA"
<< "#FFFFFF"
<< "#2AC05F";
}
void tst_qquicktextinput::text()
{
{
QQmlComponent textinputComponent(&engine);
textinputComponent.setData("import QtQuick 2.0\nTextInput { text: \"\" }", QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->text(), QString(""));
QCOMPARE(textinputObject->length(), 0);
delete textinputObject;
}
for (int i = 0; i < standard.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->text(), standard.at(i));
QCOMPARE(textinputObject->length(), standard.at(i).length());
delete textinputObject;
}
}
void tst_qquicktextinput::width()
{
// uses Font metrics to find the width for standard
{
QQmlComponent textinputComponent(&engine);
textinputComponent.setData("import QtQuick 2.0\nTextInput { text: \"\" }", QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->width(), 0.0);
delete textinputObject;
}
bool requiresUnhintedMetrics = !qmlDisableDistanceField();
for (int i = 0; i < standard.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QString s = standard.at(i);
s.replace(QLatin1Char('\n'), QChar::LineSeparator);
QTextLayout layout(s);
layout.setFont(textinputObject->font());
layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
if (requiresUnhintedMetrics) {
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
}
layout.beginLayout();
forever {
QTextLine line = layout.createLine();
if (!line.isValid())
break;
}
layout.endLayout();
qreal metricWidth = ceil(layout.boundingRect().width());
QVERIFY(textinputObject != nullptr);
int delta = abs(int(int(textinputObject->width()) - metricWidth));
QVERIFY(delta <= 3.0); // As best as we can hope for cross-platform.
delete textinputObject;
}
}
void tst_qquicktextinput::font()
{
//test size, then bold, then italic, then family
{
QString componentStr = "import QtQuick 2.0\nTextInput { font.pointSize: 40; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->font().pointSize(), 40);
QCOMPARE(textinputObject->font().bold(), false);
QCOMPARE(textinputObject->font().italic(), false);
delete textinputObject;
}
{
QString componentStr = "import QtQuick 2.0\nTextInput { font.bold: true; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->font().bold(), true);
QCOMPARE(textinputObject->font().italic(), false);
delete textinputObject;
}
{
QString componentStr = "import QtQuick 2.0\nTextInput { font.italic: true; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->font().italic(), true);
QCOMPARE(textinputObject->font().bold(), false);
delete textinputObject;
}
{
QString componentStr = "import QtQuick 2.0\nTextInput { font.family: \"Helvetica\"; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->font().family(), QString("Helvetica"));
QCOMPARE(textinputObject->font().bold(), false);
QCOMPARE(textinputObject->font().italic(), false);
delete textinputObject;
}
{
QString componentStr = "import QtQuick 2.0\nTextInput { font.family: \"\"; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->font().family(), QString(""));
delete textinputObject;
}
}
void tst_qquicktextinput::color()
{
//test initial color
{
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello World\" }";
QQmlComponent texteditComponent(&engine);
texteditComponent.setData(componentStr.toLatin1(), QUrl());
QScopedPointer<QObject> object(texteditComponent.create());
QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput*>(object.data());
QVERIFY(textInputObject);
QCOMPARE(textInputObject->color(), QColor("black"));
QCOMPARE(textInputObject->selectionColor(), QColor::fromRgba(0xFF000080));
QCOMPARE(textInputObject->selectedTextColor(), QColor("white"));
QSignalSpy colorSpy(textInputObject, SIGNAL(colorChanged()));
QSignalSpy selectionColorSpy(textInputObject, SIGNAL(selectionColorChanged()));
QSignalSpy selectedTextColorSpy(textInputObject, SIGNAL(selectedTextColorChanged()));
textInputObject->setColor(QColor("white"));
QCOMPARE(textInputObject->color(), QColor("white"));
QCOMPARE(colorSpy.count(), 1);
textInputObject->setSelectionColor(QColor("black"));
QCOMPARE(textInputObject->selectionColor(), QColor("black"));
QCOMPARE(selectionColorSpy.count(), 1);
textInputObject->setSelectedTextColor(QColor("blue"));
QCOMPARE(textInputObject->selectedTextColor(), QColor("blue"));
QCOMPARE(selectedTextColorSpy.count(), 1);
textInputObject->setColor(QColor("white"));
QCOMPARE(colorSpy.count(), 1);
textInputObject->setSelectionColor(QColor("black"));
QCOMPARE(selectionColorSpy.count(), 1);
textInputObject->setSelectedTextColor(QColor("blue"));
QCOMPARE(selectedTextColorSpy.count(), 1);
textInputObject->setColor(QColor("black"));
QCOMPARE(textInputObject->color(), QColor("black"));
QCOMPARE(colorSpy.count(), 2);
textInputObject->setSelectionColor(QColor("blue"));
QCOMPARE(textInputObject->selectionColor(), QColor("blue"));
QCOMPARE(selectionColorSpy.count(), 2);
textInputObject->setSelectedTextColor(QColor("white"));
QCOMPARE(textInputObject->selectedTextColor(), QColor("white"));
QCOMPARE(selectedTextColorSpy.count(), 2);
}
//test color
for (int i = 0; i < colorStrings.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nTextInput { color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->color(), QColor(colorStrings.at(i)));
delete textinputObject;
}
//test selection color
for (int i = 0; i < colorStrings.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nTextInput { selectionColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->selectionColor(), QColor(colorStrings.at(i)));
delete textinputObject;
}
//test selected text color
for (int i = 0; i < colorStrings.size(); i++)
{
QString componentStr = "import QtQuick 2.0\nTextInput { selectedTextColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->selectedTextColor(), QColor(colorStrings.at(i)));
delete textinputObject;
}
{
QString colorStr = "#AA001234";
QColor testColor("#001234");
testColor.setAlpha(170);
QString componentStr = "import QtQuick 2.0\nTextInput { color: \"" + colorStr + "\"; text: \"Hello World\" }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
QCOMPARE(textinputObject->color(), testColor);
delete textinputObject;
}
}
void tst_qquicktextinput::wrap()
{
int textHeight = 0;
// for specified width and wrap set true
{
QQmlComponent textComponent(&engine);
textComponent.setData("import QtQuick 2.0\nTextInput { text: \"Hello\"; wrapMode: Text.WrapAnywhere; width: 300 }", QUrl::fromLocalFile(""));
QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(textComponent.create());
textHeight = textObject->height();
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->wrapMode(), QQuickTextInput::WrapAnywhere);
QCOMPARE(textObject->width(), 300.);
delete textObject;
}
for (int i = 0; i < standard.count(); i++) {
QString componentStr = "import QtQuick 2.0\nTextInput { wrapMode: Text.WrapAnywhere; width: 30; text: \"" + standard.at(i) + "\" }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(textComponent.create());
QVERIFY(textObject != nullptr);
QCOMPARE(textObject->width(), 30.);
QVERIFY(textObject->height() > textHeight);
int oldHeight = textObject->height();
textObject->setWidth(100);
QVERIFY(textObject->height() < oldHeight);
delete textObject;
}
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
QVERIFY(input);
QSignalSpy spy(input, SIGNAL(wrapModeChanged()));
QCOMPARE(input->wrapMode(), QQuickTextInput::NoWrap);
input->setWrapMode(QQuickTextInput::Wrap);
QCOMPARE(input->wrapMode(), QQuickTextInput::Wrap);
QCOMPARE(spy.count(), 1);
input->setWrapMode(QQuickTextInput::Wrap);
QCOMPARE(spy.count(), 1);
input->setWrapMode(QQuickTextInput::NoWrap);
QCOMPARE(input->wrapMode(), QQuickTextInput::NoWrap);
QCOMPARE(spy.count(), 2);
}
}
void tst_qquicktextinput::selection()
{
QString testStr = standard[0];
QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
//Test selection follows cursor
for (int i=0; i<= testStr.size(); i++) {
textinputObject->setCursorPosition(i);
QCOMPARE(textinputObject->cursorPosition(), i);
QCOMPARE(textinputObject->selectionStart(), i);
QCOMPARE(textinputObject->selectionEnd(), i);
QVERIFY(textinputObject->selectedText().isNull());
}
textinputObject->setCursorPosition(0);
QCOMPARE(textinputObject->cursorPosition(), 0);
QCOMPARE(textinputObject->selectionStart(), 0);
QCOMPARE(textinputObject->selectionEnd(), 0);
QVERIFY(textinputObject->selectedText().isNull());
// Verify invalid positions are ignored.
textinputObject->setCursorPosition(-1);
QCOMPARE(textinputObject->cursorPosition(), 0);
QCOMPARE(textinputObject->selectionStart(), 0);
QCOMPARE(textinputObject->selectionEnd(), 0);
QVERIFY(textinputObject->selectedText().isNull());
textinputObject->setCursorPosition(textinputObject->text().count()+1);
QCOMPARE(textinputObject->cursorPosition(), 0);
QCOMPARE(textinputObject->selectionStart(), 0);
QCOMPARE(textinputObject->selectionEnd(), 0);
QVERIFY(textinputObject->selectedText().isNull());
//Test selection
for (int i=0; i<= testStr.size(); i++) {
textinputObject->select(0,i);
QCOMPARE(testStr.mid(0,i), textinputObject->selectedText());
}
for (int i=0; i<= testStr.size(); i++) {
textinputObject->select(i,testStr.size());
QCOMPARE(testStr.mid(i,testStr.size()-i), textinputObject->selectedText());
}
textinputObject->setCursorPosition(0);
QCOMPARE(textinputObject->cursorPosition(), 0);
QCOMPARE(textinputObject->selectionStart(), 0);
QCOMPARE(textinputObject->selectionEnd(), 0);
QVERIFY(textinputObject->selectedText().isNull());
//Test Error Ignoring behaviour
textinputObject->setCursorPosition(0);
QVERIFY(textinputObject->selectedText().isNull());
textinputObject->select(-10,0);
QVERIFY(textinputObject->selectedText().isNull());
textinputObject->select(100,110);
QVERIFY(textinputObject->selectedText().isNull());
textinputObject->select(0,-10);
QVERIFY(textinputObject->selectedText().isNull());
textinputObject->select(0,100);
QVERIFY(textinputObject->selectedText().isNull());
textinputObject->select(0,10);
QCOMPARE(textinputObject->selectedText().size(), 10);
textinputObject->select(-10,10);
QCOMPARE(textinputObject->selectedText().size(), 10);
textinputObject->select(100,101);
QCOMPARE(textinputObject->selectedText().size(), 10);
textinputObject->select(0,-10);
QCOMPARE(textinputObject->selectedText().size(), 10);
textinputObject->select(0,100);
QCOMPARE(textinputObject->selectedText().size(), 10);
textinputObject->deselect();
QVERIFY(textinputObject->selectedText().isNull());
textinputObject->select(0,10);
QCOMPARE(textinputObject->selectedText().size(), 10);
textinputObject->deselect();
QVERIFY(textinputObject->selectedText().isNull());
// test input method selection
QSignalSpy selectionSpy(textinputObject, SIGNAL(selectedTextChanged()));
textinputObject->setFocus(true);
{
QList<QInputMethodEvent::Attribute> attributes;
attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 12, 5, QVariant());
QInputMethodEvent event("", attributes);
QGuiApplication::sendEvent(textinputObject, &event);
}
QCOMPARE(selectionSpy.count(), 1);
QCOMPARE(textinputObject->selectionStart(), 12);
QCOMPARE(textinputObject->selectionEnd(), 17);
delete textinputObject;
}
void tst_qquicktextinput::persistentSelection()
{
QQuickView window(testFileUrl("persistentSelection.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(window.rootObject());
QVERIFY(input);
QVERIFY(input->hasActiveFocus());
QSignalSpy spy(input, SIGNAL(persistentSelectionChanged()));
QCOMPARE(input->persistentSelection(), false);
input->setPersistentSelection(false);
QCOMPARE(input->persistentSelection(), false);
QCOMPARE(spy.count(), 0);
input->select(1, 4);
QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
input->setFocus(false);
QCOMPARE(input->property("selected").toString(), QString());
input->setFocus(true);
QCOMPARE(input->property("selected").toString(), QString());
input->setPersistentSelection(true);
QCOMPARE(input->persistentSelection(), true);
QCOMPARE(spy.count(), 1);
input->select(1, 4);
QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
input->setFocus(false);
QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
input->setFocus(true);
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 != nullptr);
QSignalSpy spy(textInput, SIGNAL(overwriteModeChanged(bool)));
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(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");
QTest::addColumn<bool>("emptyString");
QTest::addColumn<bool>("firstCharacter");
QTest::addColumn<bool>("lastCharacter");
QTest::addColumn<bool>("middleCharacter");
QTest::addColumn<bool>("startString");
QTest::addColumn<bool>("midString");
QTest::addColumn<bool>("endString");
const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
QTest::newRow("Empty") << "" << false << false << false << false << false << false << false;
QTest::newRow("Neutral") << "23244242" << false << false << false << false << false << false << false;
QTest::newRow("LTR") << "Hello world" << false << false << false << false << false << false << false;
QTest::newRow("RTL") << QString::fromUtf16(arabic_str, 11) << false << true << true << true << true << true << true;
QTest::newRow("Bidi RTL + LTR + RTL") << QString::fromUtf16(arabic_str, 11) + QString("Hello world") + QString::fromUtf16(arabic_str, 11) << false << true << true << false << true << true << true;
QTest::newRow("Bidi LTR + RTL + LTR") << QString("Hello world") + QString::fromUtf16(arabic_str, 11) + QString("Hello world") << false << false << false << true << false << false << false;
}
void tst_qquicktextinput::isRightToLeft()
{
QFETCH(QString, text);
QFETCH(bool, emptyString);
QFETCH(bool, firstCharacter);
QFETCH(bool, lastCharacter);
QFETCH(bool, middleCharacter);
QFETCH(bool, startString);
QFETCH(bool, midString);
QFETCH(bool, endString);
QQuickTextInput textInput;
textInput.setText(text);
// first test that the right string is delivered to the QString::isRightToLeft()
QCOMPARE(textInput.isRightToLeft(0,0), text.mid(0,0).isRightToLeft());
QCOMPARE(textInput.isRightToLeft(0,1), text.mid(0,1).isRightToLeft());
QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), text.mid(text.count()-2, text.count()-1).isRightToLeft());
QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), text.mid(text.count()/2, text.count()/2 + 1).isRightToLeft());
QCOMPARE(textInput.isRightToLeft(0,text.count()/4), text.mid(0,text.count()/4).isRightToLeft());
QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), text.mid(text.count()/4,3*text.count()/4).isRightToLeft());
if (text.isEmpty())
QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), text.mid(3*text.count()/4,text.count()-1).isRightToLeft());
// then test that the feature actually works
QCOMPARE(textInput.isRightToLeft(0,0), emptyString);
QCOMPARE(textInput.isRightToLeft(0,1), firstCharacter);
QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), lastCharacter);
QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), middleCharacter);
QCOMPARE(textInput.isRightToLeft(0,text.count()/4), startString);
QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), midString);
if (text.isEmpty())
QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), endString);
}
void tst_qquicktextinput::moveCursorSelection_data()
{
QTest::addColumn<QString>("testStr");
QTest::addColumn<int>("cursorPosition");
QTest::addColumn<int>("movePosition");
QTest::addColumn<QQuickTextInput::SelectionMode>("mode");
QTest::addColumn<int>("selectionStart");
QTest::addColumn<int>("selectionEnd");
QTest::addColumn<bool>("reversible");
// () contains the text selected by the cursor.
// <> contains the actual selection.
QTest::newRow("(t)he|characters")
<< standard[0] << 0 << 1 << QQuickTextInput::SelectCharacters << 0 << 1 << true;
QTest::newRow("do(g)|characters")
<< standard[0] << 43 << 44 << QQuickTextInput::SelectCharacters << 43 << 44 << true;
QTest::newRow("jum(p)ed|characters")
<< standard[0] << 23 << 24 << QQuickTextInput::SelectCharacters << 23 << 24 << true;
QTest::newRow("jumped( )over|characters")
<< standard[0] << 26 << 27 << QQuickTextInput::SelectCharacters << 26 << 27 << true;
QTest::newRow("(the )|characters")
<< standard[0] << 0 << 4 << QQuickTextInput::SelectCharacters << 0 << 4 << true;
QTest::newRow("( dog)|characters")
<< standard[0] << 40 << 44 << QQuickTextInput::SelectCharacters << 40 << 44 << true;
QTest::newRow("( jumped )|characters")
<< standard[0] << 19 << 27 << QQuickTextInput::SelectCharacters << 19 << 27 << true;
QTest::newRow("th(e qu)ick|characters")
<< standard[0] << 2 << 6 << QQuickTextInput::SelectCharacters << 2 << 6 << true;
QTest::newRow("la(zy d)og|characters")
<< standard[0] << 38 << 42 << QQuickTextInput::SelectCharacters << 38 << 42 << true;
QTest::newRow("jum(ped ov)er|characters")
<< standard[0] << 23 << 29 << QQuickTextInput::SelectCharacters << 23 << 29 << true;
QTest::newRow("()the|characters")
<< standard[0] << 0 << 0 << QQuickTextInput::SelectCharacters << 0 << 0 << true;
QTest::newRow("dog()|characters")
<< standard[0] << 44 << 44 << QQuickTextInput::SelectCharacters << 44 << 44 << true;
QTest::newRow("jum()ped|characters")
<< standard[0] << 23 << 23 << QQuickTextInput::SelectCharacters << 23 << 23 << true;
QTest::newRow("<(t)he>|words")
<< standard[0] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 3 << true;
QTest::newRow("<do(g)>|words")
<< standard[0] << 43 << 44 << QQuickTextInput::SelectWords << 41 << 44 << true;
QTest::newRow("<jum(p)ed>|words")
<< standard[0] << 23 << 24 << QQuickTextInput::SelectWords << 20 << 26 << true;
QTest::newRow("<jumped( )>over|words,ltr")
<< standard[0] << 26 << 27 << QQuickTextInput::SelectWords << 20 << 27 << false;
QTest::newRow("jumped<( )over>|words,rtl")
<< standard[0] << 27 << 26 << QQuickTextInput::SelectWords << 26 << 31 << false;
QTest::newRow("<(the )>quick|words,ltr")
<< standard[0] << 0 << 4 << QQuickTextInput::SelectWords << 0 << 4 << false;
QTest::newRow("<(the )quick>|words,rtl")
<< standard[0] << 4 << 0 << QQuickTextInput::SelectWords << 0 << 9 << false;
QTest::newRow("<lazy( dog)>|words,ltr")
<< standard[0] << 40 << 44 << QQuickTextInput::SelectWords << 36 << 44 << false;
QTest::newRow("lazy<( dog)>|words,rtl")
<< standard[0] << 44 << 40 << QQuickTextInput::SelectWords << 40 << 44 << false;
QTest::newRow("<fox( jumped )>over|words,ltr")
<< standard[0] << 19 << 27 << QQuickTextInput::SelectWords << 16 << 27 << false;
QTest::newRow("fox<( jumped )over>|words,rtl")
<< standard[0] << 27 << 19 << QQuickTextInput::SelectWords << 19 << 31 << false;
QTest::newRow("<th(e qu)ick>|words")
<< standard[0] << 2 << 6 << QQuickTextInput::SelectWords << 0 << 9 << true;
QTest::newRow("<la(zy d)og|words>")
<< standard[0] << 38 << 42 << QQuickTextInput::SelectWords << 36 << 44 << true;
QTest::newRow("<jum(ped ov)er>|words")
<< standard[0] << 23 << 29 << QQuickTextInput::SelectWords << 20 << 31 << true;
QTest::newRow("<()>the|words")
<< standard[0] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << true;
QTest::newRow("dog<()>|words")
<< standard[0] << 44 << 44 << QQuickTextInput::SelectWords << 44 << 44 << true;
QTest::newRow("jum<()>ped|words")
<< standard[0] << 23 << 23 << QQuickTextInput::SelectWords << 23 << 23 << true;
QTest::newRow("<Hello(,)> |words,ltr")
<< standard[2] << 5 << 6 << QQuickTextInput::SelectWords << 0 << 6 << false;
QTest::newRow("Hello<(,)> |words,rtl")
<< standard[2] << 6 << 5 << QQuickTextInput::SelectWords << 5 << 6 << false;
QTest::newRow("<Hello(, )>world|words,ltr")
<< standard[2] << 5 << 7 << QQuickTextInput::SelectWords << 0 << 7 << false;
QTest::newRow("Hello<(, )world>|words,rtl")
<< standard[2] << 7 << 5 << QQuickTextInput::SelectWords << 5 << 12 << false;
QTest::newRow("<Hel(lo, )>world|words,ltr")
<< standard[2] << 3 << 7 << QQuickTextInput::SelectWords << 0 << 7 << false;
QTest::newRow("<Hel(lo, )world>|words,rtl")
<< standard[2] << 7 << 3 << QQuickTextInput::SelectWords << 0 << 12 << false;
QTest::newRow("<Hel(lo)>,|words")
<< standard[2] << 3 << 5 << QQuickTextInput::SelectWords << 0 << 5 << true;
QTest::newRow("Hello<()>,|words")
<< standard[2] << 5 << 5 << QQuickTextInput::SelectWords << 5 << 5 << true;
QTest::newRow("Hello,<()>|words")
<< standard[2] << 6 << 6 << QQuickTextInput::SelectWords << 6 << 6 << true;
QTest::newRow("Hello,<( )>world|words,ltr")
<< standard[2] << 6 << 7 << QQuickTextInput::SelectWords << 6 << 7 << false;
QTest::newRow("Hello,<( )world>|words,rtl")
<< standard[2] << 7 << 6 << QQuickTextInput::SelectWords << 6 << 12 << false;
QTest::newRow("Hello,<( world)>|words")
<< standard[2] << 6 << 12 << QQuickTextInput::SelectWords << 6 << 12 << true;
QTest::newRow("Hello,<( world!)>|words")
<< standard[2] << 6 << 13 << QQuickTextInput::SelectWords << 6 << 13 << true;
QTest::newRow("<Hello(, world!)>|words,ltr")
<< standard[2] << 5 << 13 << QQuickTextInput::SelectWords << 0 << 13 << false;
QTest::newRow("Hello<(, world!)>|words,rtl")
<< standard[2] << 13 << 5 << QQuickTextInput::SelectWords << 5 << 13 << false;
QTest::newRow("<world(!)>|words,ltr")
<< standard[2] << 12 << 13 << QQuickTextInput::SelectWords << 7 << 13 << false;
QTest::newRow("world<(!)>|words,rtl")
<< standard[2] << 13 << 12 << QQuickTextInput::SelectWords << 12 << 13 << false;
QTest::newRow("world!<()>)|words")
<< standard[2] << 13 << 13 << QQuickTextInput::SelectWords << 13 << 13 << true;
QTest::newRow("world<()>!)|words")
<< standard[2] << 12 << 12 << QQuickTextInput::SelectWords << 12 << 12 << true;
QTest::newRow("<(,)>olleH |words,ltr")
<< standard[3] << 7 << 8 << QQuickTextInput::SelectWords << 7 << 8 << false;
QTest::newRow("<(,)olleH> |words,rtl")
<< standard[3] << 8 << 7 << QQuickTextInput::SelectWords << 7 << 13 << false;
QTest::newRow("<dlrow( ,)>olleH|words,ltr")
<< standard[3] << 6 << 8 << QQuickTextInput::SelectWords << 1 << 8 << false;
QTest::newRow("dlrow<( ,)olleH>|words,rtl")
<< standard[3] << 8 << 6 << QQuickTextInput::SelectWords << 6 << 13 << false;
QTest::newRow("<dlrow( ,ol)leH>|words,ltr")
<< standard[3] << 6 << 10 << QQuickTextInput::SelectWords << 1 << 13 << false;
QTest::newRow("dlrow<( ,ol)leH>|words,rtl")
<< standard[3] << 10 << 6 << QQuickTextInput::SelectWords << 6 << 13 << false;
QTest::newRow(",<(ol)leH>,|words")
<< standard[3] << 8 << 10 << QQuickTextInput::SelectWords << 8 << 13 << true;
QTest::newRow(",<()>olleH|words")
<< standard[3] << 8 << 8 << QQuickTextInput::SelectWords << 8 << 8 << true;
QTest::newRow("<()>,olleH|words")
<< standard[3] << 7 << 7 << QQuickTextInput::SelectWords << 7 << 7 << true;
QTest::newRow("<dlrow( )>,olleH|words,ltr")
<< standard[3] << 6 << 7 << QQuickTextInput::SelectWords << 1 << 7 << false;
QTest::newRow("dlrow<( )>,olleH|words,rtl")
<< standard[3] << 7 << 6 << QQuickTextInput::SelectWords << 6 << 7 << false;
QTest::newRow("<(dlrow )>,olleH|words")
<< standard[3] << 1 << 7 << QQuickTextInput::SelectWords << 1 << 7 << true;
QTest::newRow("<(!dlrow )>,olleH|words")
<< standard[3] << 0 << 7 << QQuickTextInput::SelectWords << 0 << 7 << true;
QTest::newRow("<(!dlrow ,)>olleH|words,ltr")
<< standard[3] << 0 << 8 << QQuickTextInput::SelectWords << 0 << 8 << false;
QTest::newRow("<(!dlrow ,)olleH>|words,rtl")
<< standard[3] << 8 << 0 << QQuickTextInput::SelectWords << 0 << 13 << false;
QTest::newRow("<(!)>dlrow|words,ltr")
<< standard[3] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 1 << false;
QTest::newRow("<(!)dlrow|words,rtl")
<< standard[3] << 1 << 0 << QQuickTextInput::SelectWords << 0 << 6 << false;
QTest::newRow("<()>!dlrow|words")
<< standard[3] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << true;
QTest::newRow("!<()>dlrow|words")
<< standard[3] << 1 << 1 << QQuickTextInput::SelectWords << 1 << 1 << true;
QTest::newRow(" <s(pac)ey> text |words")
<< standard[4] << 1 << 4 << QQuickTextInput::SelectWords << 1 << 7 << true;
QTest::newRow(" spacey <t(ex)t> |words")
<< standard[4] << 11 << 13 << QQuickTextInput::SelectWords << 10 << 14 << true;
QTest::newRow("<( )>spacey text |words|ltr")
<< standard[4] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 1 << false;
QTest::newRow("<( )spacey> text |words|rtl")
<< standard[4] << 1 << 0 << QQuickTextInput::SelectWords << 0 << 7 << false;
QTest::newRow("spacey <text( )>|words|ltr")
<< standard[4] << 14 << 15 << QQuickTextInput::SelectWords << 10 << 15 << false;
QTest::newRow("spacey text<( )>|words|rtl")
<< standard[4] << 15 << 14 << QQuickTextInput::SelectWords << 14 << 15 << false;
QTest::newRow("<()> spacey text |words")
<< standard[4] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << false;
QTest::newRow(" spacey text <()>|words")
<< standard[4] << 15 << 15 << QQuickTextInput::SelectWords << 15 << 15 << false;
}
void tst_qquicktextinput::moveCursorSelection()
{
QFETCH(QString, testStr);
QFETCH(int, cursorPosition);
QFETCH(int, movePosition);
QFETCH(QQuickTextInput::SelectionMode, mode);
QFETCH(int, selectionStart);
QFETCH(int, selectionEnd);
QFETCH(bool, reversible);
QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
textinputObject->setCursorPosition(cursorPosition);
textinputObject->moveCursorSelection(movePosition, mode);
QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
QCOMPARE(textinputObject->selectionStart(), selectionStart);
QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
if (reversible) {
textinputObject->setCursorPosition(movePosition);
textinputObject->moveCursorSelection(cursorPosition, mode);
QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
QCOMPARE(textinputObject->selectionStart(), selectionStart);
QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
}
delete textinputObject;
}
void tst_qquicktextinput::moveCursorSelectionSequence_data()
{
QTest::addColumn<QString>("testStr");
QTest::addColumn<int>("cursorPosition");
QTest::addColumn<int>("movePosition1");
QTest::addColumn<int>("movePosition2");
QTest::addColumn<int>("selection1Start");
QTest::addColumn<int>("selection1End");
QTest::addColumn<int>("selection2Start");
QTest::addColumn<int>("selection2End");
// () contains the text selected by the cursor.
// <> contains the actual selection.
// ^ is the revised cursor position.
// {} contains the revised selection.
QTest::newRow("the {<quick( bro)wn> f^ox} jumped|ltr")
<< standard[0]
<< 9 << 13 << 17
<< 4 << 15
<< 4 << 19;
QTest::newRow("the quick<( {bro)wn> f^ox} jumped|rtl")
<< standard[0]
<< 13 << 9 << 17
<< 9 << 15
<< 10 << 19;
QTest::newRow("the {<quick( bro)wn> ^}fox jumped|ltr")
<< standard[0]
<< 9 << 13 << 16
<< 4 << 15
<< 4 << 16;
QTest::newRow("the quick<( {bro)wn> ^}fox jumped|rtl")
<< standard[0]
<< 13 << 9 << 16
<< 9 << 15
<< 10 << 16;
QTest::newRow("the {<quick( bro)wn^>} fox jumped|ltr")
<< standard[0]
<< 9 << 13 << 15
<< 4 << 15
<< 4 << 15;
QTest::newRow("the quick<( {bro)wn^>} f^ox jumped|rtl")
<< standard[0]
<< 13 << 9 << 15
<< 9 << 15
<< 10 << 15;
QTest::newRow("the {<quick() ^}bro)wn> fox|ltr")
<< standard[0]
<< 9 << 13 << 10
<< 4 << 15
<< 4 << 10;
QTest::newRow("the quick<( {^bro)wn>} fox|rtl")
<< standard[0]
<< 13 << 9 << 10
<< 9 << 15
<< 10 << 15;
QTest::newRow("the {<quick^}( bro)wn> fox|ltr")
<< standard[0]
<< 9 << 13 << 9
<< 4 << 15
<< 4 << 9;
QTest::newRow("the quick{<(^ bro)wn>} fox|rtl")
<< standard[0]
<< 13 << 9 << 9
<< 9 << 15
<< 9 << 15;
QTest::newRow("the {<qui^ck}( bro)wn> fox|ltr")
<< standard[0]
<< 9 << 13 << 7
<< 4 << 15
<< 4 << 9;
QTest::newRow("the {<qui^ck}( bro)wn> fox|rtl")
<< standard[0]
<< 13 << 9 << 7
<< 9 << 15
<< 4 << 15;
QTest::newRow("the {<^quick}( bro)wn> fox|ltr")
<< standard[0]
<< 9 << 13 << 4
<< 4 << 15
<< 4 << 9;
QTest::newRow("the {<^quick}( bro)wn> fox|rtl")
<< standard[0]
<< 13 << 9 << 4
<< 9 << 15
<< 4 << 15;
QTest::newRow("the{^ <quick}( bro)wn> fox|ltr")
<< standard[0]
<< 9 << 13 << 3
<< 4 << 15
<< 3 << 9;
QTest::newRow("the{^ <quick}( bro)wn> fox|rtl")
<< standard[0]
<< 13 << 9 << 3
<< 9 << 15
<< 3 << 15;
QTest::newRow("{t^he <quick}( bro)wn> fox|ltr")
<< standard[0]
<< 9 << 13 << 1
<< 4 << 15
<< 0 << 9;
QTest::newRow("{t^he <quick}( bro)wn> fox|rtl")
<< standard[0]
<< 13 << 9 << 1
<< 9 << 15
<< 0 << 15;
QTest::newRow("{<He(ll)o>, w^orld}!|ltr")
<< standard[2]
<< 2 << 4 << 8
<< 0 << 5
<< 0 << 12;
QTest::newRow("{<He(ll)o>, w^orld}!|rtl")
<< standard[2]
<< 4 << 2 << 8
<< 0 << 5
<< 0 << 12;
QTest::newRow("!{dlro^w ,<o(ll)eH>}|ltr")
<< standard[3]
<< 9 << 11 << 5
<< 8 << 13
<< 1 << 13;
QTest::newRow("!{dlro^w ,<o(ll)eH>}|rtl")
<< standard[3]
<< 11 << 9 << 5
<< 8 << 13
<< 1 << 13;
QTest::newRow("{<(^} sp)acey> text |ltr")
<< standard[4]
<< 0 << 3 << 0
<< 0 << 7
<< 0 << 0;
QTest::newRow("{<( ^}sp)acey> text |ltr")
<< standard[4]
<< 0 << 3 << 1
<< 0 << 7
<< 0 << 1;
QTest::newRow("<( {s^p)acey>} text |rtl")
<< standard[4]
<< 3 << 0 << 2
<< 0 << 7
<< 1 << 7;
QTest::newRow("<( {^sp)acey>} text |rtl")
<< standard[4]
<< 3 << 0 << 1
<< 0 << 7
<< 1 << 7;
QTest::newRow(" spacey <te(xt {^)>}|rtl")
<< standard[4]
<< 15 << 12 << 15
<< 10 << 15
<< 15 << 15;
QTest::newRow(" spacey <te(xt{^ )>}|rtl")
<< standard[4]
<< 15 << 12 << 14
<< 10 << 15
<< 14 << 15;
QTest::newRow(" spacey {<te(x^t} )>|ltr")
<< standard[4]
<< 12 << 15 << 13
<< 10 << 15
<< 10 << 14;
QTest::newRow(" spacey {<te(xt^} )>|ltr")
<< standard[4]
<< 12 << 15 << 14
<< 10 << 15
<< 10 << 14;
}
void tst_qquicktextinput::moveCursorSelectionSequence()
{
QFETCH(QString, testStr);
QFETCH(int, cursorPosition);
QFETCH(int, movePosition1);
QFETCH(int, movePosition2);
QFETCH(int, selection1Start);
QFETCH(int, selection1End);
QFETCH(int, selection2Start);
QFETCH(int, selection2End);
QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
QQmlComponent textinputComponent(&engine);
textinputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
QVERIFY(textinputObject != nullptr);
textinputObject->setCursorPosition(cursorPosition);
textinputObject->moveCursorSelection(movePosition1, QQuickTextInput::SelectWords);
QCOMPARE(textinputObject->selectedText(), testStr.mid(selection1Start, selection1End - selection1Start));
QCOMPARE(textinputObject->selectionStart(), selection1Start);
QCOMPARE(textinputObject->selectionEnd(), selection1End);
textinputObject->moveCursorSelection(movePosition2, QQuickTextInput::SelectWords);
QCOMPARE(textinputObject->selectedText(), testStr.mid(selection2Start, selection2End - selection2Start));
QCOMPARE(textinputObject->selectionStart(), selection2Start);
QCOMPARE(textinputObject->selectionEnd(), selection2End);
delete textinputObject;
}
void tst_qquicktextinput::dragMouseSelection()
{
QString qmlfile = testFile("mouseselection_true.qml");
QQuickView window(QUrl::fromLocalFile(qmlfile));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(window.rootObject());
QVERIFY(textInputObject != nullptr);
// press-and-drag-and-release from x1 to x2
int x1 = 10;
int x2 = 70;
int y = textInputObject->height()/2;
QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(x1,y));
QTest::mouseMove(&window, QPoint(x2, y));
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(x2,y));
QString str1;
QTRY_VERIFY((str1 = textInputObject->selectedText()).length() > 3);
QTRY_VERIFY(str1.length() > 3);
// press and drag the current selection.
x1 = 40;
x2 = 100;
QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(x1,y));
QTest::mouseMove(&window, QPoint(x2, y));
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(x2,y));
QString str2 = textInputObject->selectedText();
QTRY_VERIFY(str2.length() > 3);
QTRY_VERIFY(str1 != str2);
}
void tst_qquicktextinput::mouseSelectionMode_data()
{
QTest::addColumn<QString>("qmlfile");
QTest::addColumn<bool>("selectWords");
QTest::addColumn<bool>("focus");
QTest::addColumn<bool>("focusOnPress");
// import installed
QTest::newRow("SelectWords focused") << testFile("mouseselectionmode_words.qml") << true << true << true;
QTest::newRow("SelectCharacters focused") << testFile("mouseselectionmode_characters.qml") << false << true << true;
QTest::newRow("default focused") << testFile("mouseselectionmode_default.qml") << false << true << true;
QTest::newRow("SelectWords unfocused") << testFile("mouseselectionmode_words.qml") << true << false << false;
QTest::newRow("SelectCharacters unfocused") << testFile("mouseselectionmode_characters.qml") << false << false << false;
QTest::newRow("default unfocused") << testFile("mouseselectionmode_default.qml") << false << true << false;
QTest::newRow("SelectWords focuss on press") << testFile("mouseselectionmode_words.qml") << true << false << true;
QTest::newRow("SelectCharacters focus on press") << testFile("mouseselectionmode_characters.qml") << false << false << true;
QTest::newRow("default focus on press") << testFile("mouseselectionmode_default.qml") << false << false << true;
}
void tst_qquicktextinput::mouseSelectionMode()
{
QFETCH(QString, qmlfile);
QFETCH(bool, selectWords);
QFETCH(bool, focus);
QFETCH(bool, focusOnPress);
QString text = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
QQuickView window(QUrl::fromLocalFile(qmlfile));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(window.rootObject());
QVERIFY(textInputObject != nullptr);
textInputObject->setFocus(focus);
textInputObject->setFocusOnPress(focusOnPress);
// press-and-drag-and-release from x1 to x2
int x1 = 10;
int x2 = 70;
int y = textInputObject->height()/2;
QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(x1,y));
QTest::mouseMove(&window, QPoint(x2,y)); // doesn't work
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(x2,y));
if (selectWords) {
QTRY_COMPARE(textInputObject->selectedText(), text);
} else {
QTRY_VERIFY(textInputObject->selectedText().length() > 3);
QVERIFY(textInputObject->selectedText() != text);
}
}
void tst_qquicktextinput::mouseSelectionMode_accessors()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
QVERIFY(input);
QSignalSpy spy(input, &QQuickTextInput::mouseSelectionModeChanged);
QCOMPARE(input->mouseSelectionMode(), QQuickTextInput::SelectCharacters);
input->setMouseSelectionMode(QQuickTextInput::SelectWords);
QCOMPARE(input->mouseSelectionMode(), QQuickTextInput::SelectWords);
QCOMPARE(spy.count(), 1);
input->setMouseSelectionMode(QQuickTextInput::SelectWords);
QCOMPARE(spy.count(), 1);
input->setMouseSelectionMode(QQuickTextInput::SelectCharacters);
QCOMPARE(input->mouseSelectionMode(), QQuickTextInput::SelectCharacters);
QCOMPARE(spy.count(), 2);
}
void tst_qquicktextinput::selectByMouse()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
QVERIFY(input);
QSignalSpy spy(input, SIGNAL(selectByMouseChanged(bool)));
QCOMPARE(input->selectByMouse(), false);
input->setSelectByMouse(true);
QCOMPARE(input->selectByMouse(), true);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.at(0).at(0).toBool(), true);
input->setSelectByMouse(true);
QCOMPARE(spy.count(), 1);
input->setSelectByMouse(false);
QCOMPARE(input->selectByMouse(), false);
QCOMPARE(spy.count(), 2);
QCOMPARE(spy.at(1).at(0).toBool(), false);
}
void tst_qquicktextinput::renderType()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
QVERIFY(input);
QSignalSpy spy(input, SIGNAL(renderTypeChanged()));
QCOMPARE(input->renderType(), QQuickTextInput::QtRendering);
input->setRenderType(QQuickTextInput::NativeRendering);
QCOMPARE(input->renderType(), QQuickTextInput::NativeRendering);
QCOMPARE(spy.count(), 1);
input->setRenderType(QQuickTextInput::NativeRendering);
QCOMPARE(spy.count(), 1);
input->setRenderType(QQuickTextInput::QtRendering);
QCOMPARE(input->renderType(), QQuickTextInput::QtRendering);
QCOMPARE(spy.count(), 2);
}
void tst_qquicktextinput::horizontalAlignment_RightToLeft()
{
PlatformInputContext platformInputContext;
QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
inputMethodPrivate->testContext = &platformInputContext;
QQuickView window(testFileUrl("horizontalAlignment_RightToLeft.qml"));
QQuickTextInput *textInput = window.rootObject()->findChild<QQuickTextInput*>("text");
QVERIFY(textInput != nullptr);
window.show();
const QString rtlText = textInput->text();
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// implicit alignment should follow the reading direction of RTL text
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// explicitly left aligned
textInput->setHAlign(QQuickTextInput::AlignLeft);
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
QCOMPARE(textInput->boundingRect().left(), qreal(0));
// explicitly right aligned
textInput->setHAlign(QQuickTextInput::AlignRight);
QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// explicitly center aligned
textInput->setHAlign(QQuickTextInput::AlignHCenter);
QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignHCenter);
QVERIFY(textInput->boundingRect().left() > 0);
QVERIFY(textInput->boundingRect().right() < textInput->width());
// reseted alignment should go back to following the text reading direction
textInput->resetHAlign();
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// mirror the text item
QQuickItemPrivate::get(textInput)->setLayoutMirror(true);
// mirrored implicit alignment should continue to follow the reading direction of the text
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// explicitly right aligned behaves as left aligned
textInput->setHAlign(QQuickTextInput::AlignRight);
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignLeft);
QCOMPARE(textInput->boundingRect().left(), qreal(0));
// mirrored explicitly left aligned behaves as right aligned
textInput->setHAlign(QQuickTextInput::AlignLeft);
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignRight);
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// disable mirroring
QQuickItemPrivate::get(textInput)->setLayoutMirror(false);
QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
textInput->resetHAlign();
// English text should be implicitly left aligned
textInput->setText("Hello world!");
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
QCOMPARE(textInput->boundingRect().left(), qreal(0));
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
// If there is no committed text, the preedit text should determine the alignment.
textInput->setText(QString());
{ QInputMethodEvent ev(rtlText, QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(textInput, &ev); }
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
{ QInputMethodEvent ev("Hello world!", QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(textInput, &ev); }
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
// Clear pre-edit text. TextInput should maybe do this itself on setText, but that may be
// redundant as an actual input method may take care of it.
{ QInputMethodEvent ev; QGuiApplication::sendEvent(textInput, &ev); }
// empty text with implicit alignment follows the system locale-based
// keyboard input direction from QInputMethod::inputDirection()
textInput->setText("");
platformInputContext.setInputDirection(Qt::LeftToRight);
QCOMPARE(qApp->inputMethod()->inputDirection(), Qt::LeftToRight);
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
QCOMPARE(textInput->boundingRect().left(), qreal(0));
QSignalSpy cursorRectangleSpy(textInput, SIGNAL(cursorRectangleChanged()));
platformInputContext.setInputDirection(Qt::RightToLeft);
QCOMPARE(qApp->inputMethod()->inputDirection(), Qt::RightToLeft);
QCOMPARE(cursorRectangleSpy.count(), 1);
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// set input direction while having content
platformInputContext.setInputDirection(Qt::LeftToRight);
textInput->setText("a");
platformInputContext.setInputDirection(Qt::RightToLeft);
QTest::keyClick(&window, Qt::Key_Backspace);
QVERIFY(textInput->text().isEmpty());
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// input direction changed while not having focus
platformInputContext.setInputDirection(Qt::LeftToRight);
textInput->setFocus(false);
platformInputContext.setInputDirection(Qt::RightToLeft);
textInput->setFocus(true);
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
textInput->setHAlign(QQuickTextInput::AlignRight);
QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
// neutral text should fall back to input direction
textInput->setFocus(true);
textInput->resetHAlign();
textInput->setText(" ()((=<>");
platformInputContext.setInputDirection(Qt::LeftToRight);
QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignLeft);
platformInputContext.setInputDirection(Qt::RightToLeft);
QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignRight);
// changing width keeps right aligned cursor on proper position
textInput->setText("");
textInput->setWidth(500);
QVERIFY(textInput->positionToRectangle(0).x() > textInput->width() / 2);
}
void tst_qquicktextinput::verticalAlignment()
{
QQuickView window(testFileUrl("horizontalAlignment.qml"));
QQuickTextInput *textInput = window.rootObject()->findChild<QQuickTextInput*>("text");
QVERIFY(textInput != nullptr);
window.showNormal();
QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignTop);
QVERIFY(textInput->boundingRect().bottom() < window.height() / 2);
QVERIFY(textInput->cursorRectangle().bottom() < window.height() / 2);
QVERIFY(textInput->positionToRectangle(0).bottom() < window.height() / 2);
// bottom aligned
textInput->setVAlign(QQuickTextInput::AlignBottom);
QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignBottom);
QVERIFY(textInput->boundingRect().top() > window.height() / 2);
QVERIFY(textInput->cursorRectangle().top() > window.height() / 2);
QVERIFY(textInput->positionToRectangle(0).top() > window.height() / 2);
// explicitly center aligned
textInput->setVAlign(QQuickTextInput::AlignVCenter);
QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignVCenter);
QVERIFY(textInput->boundingRect().top() < window.height() / 2);
QVERIFY(textInput->boundingRect().bottom() > window.height() / 2);
QVERIFY(textInput->cursorRectangle().top() < window.height() / 2);
QVERIFY(textInput->cursorRectangle().bottom() > window.height() / 2);
QVERIFY(textInput->positionToRectangle(0).top() < window.height() / 2);
QVERIFY(textInput->positionToRectangle(0).bottom() > window.height() / 2);
}
void tst_qquicktextinput::clipRect()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
QVERIFY(input);
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
QCOMPARE(input->clipRect().height(), input->height());
input->setText("Hello World");
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
QCOMPARE(input->clipRect().height(), input->height());
// clip rect shouldn't exceed the size of the item, expect for the cursor width;
input->setWidth(input->width() / 2);
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
QCOMPARE(input->clipRect().height(), input->height());
input->setHeight(input->height() * 2);
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
QCOMPARE(input->clipRect().height(), input->height());
QQmlComponent cursorComponent(&engine);
cursorComponent.setData("import QtQuick 2.0\nRectangle { height: 20; width: 8 }", QUrl());
input->setCursorDelegate(&cursorComponent);
input->setCursorVisible(true);
// If a cursor delegate is used it's size should determine the excess width.
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + 8);
QCOMPARE(input->clipRect().height(), input->height());
// Alignment, auto scroll, wrapping all don't affect the clip rect.
input->setAutoScroll(false);
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + 8);
QCOMPARE(input->clipRect().height(), input->height());
input->setHAlign(QQuickTextInput::AlignRight);
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + 8);
QCOMPARE(input->clipRect().height(), input->height());
input->setWrapMode(QQuickTextInput::Wrap);
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + 8);
QCOMPARE(input->clipRect().height(), input->height());
input->setVAlign(QQuickTextInput::AlignBottom);
QCOMPARE(input->clipRect().x(), qreal(0));
QCOMPARE(input->clipRect().y(), qreal(0));
QCOMPARE(input->clipRect().width(), input->width() + 8);
QCOMPARE(input->clipRect().height(), input->height());
}
void tst_qquicktextinput::boundingRect()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
QVERIFY(input);
QTextLayout layout;
layout.setFont(input->font());
if (!qmlDisableDistanceField()) {
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
}
layout.beginLayout();
QTextLine line = layout.createLine();
layout.endLayout();
QCOMPARE(input->boundingRect().x(), qreal(0));
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), input->cursorRectangle().width());
QCOMPARE(input->boundingRect().height(), line.height());
input->setText("Hello World");
layout.setText(input->text());
layout.beginLayout();
line = layout.createLine();
layout.endLayout();
QCOMPARE(input->boundingRect().x(), qreal(0));
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
QCOMPARE(input->boundingRect().height(), line.height());
// the size of the bounding rect shouldn't be bounded by the size of item.
input->setWidth(input->width() / 2);
QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
QCOMPARE(input->boundingRect().height(), line.height());
input->setHeight(input->height() * 2);
QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
QCOMPARE(input->boundingRect().height(), line.height());
QQmlComponent cursorComponent(&engine);
cursorComponent.setData("import QtQuick 2.0\nRectangle { height: 20; width: 8 }", QUrl());
input->setCursorDelegate(&cursorComponent);
input->setCursorVisible(true);
// Don't include the size of a cursor delegate as it has its own bounding rect.
QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(input->boundingRect().height(), line.height());
// Bounding rect left aligned when auto scroll is disabled;
input->setAutoScroll(false);
QCOMPARE(input->boundingRect().x(), qreal(0));
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(input->boundingRect().height(), line.height());
input->setHAlign(QQuickTextInput::AlignRight);
QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
QCOMPARE(input->boundingRect().height(), line.height());
input->setWrapMode(QQuickTextInput::Wrap);
QCOMPARE(input->boundingRect().right(), input->width());
QCOMPARE(input->boundingRect().y(), qreal(0));
QVERIFY(input->boundingRect().width() < line.naturalTextWidth());
QVERIFY(input->boundingRect().height() > line.height());
input->setVAlign(QQuickTextInput::AlignBottom);
QCOMPARE(input->boundingRect().right(), input->width());
QCOMPARE(input->boundingRect().bottom(), input->height());
QVERIFY(input->boundingRect().width() < line.naturalTextWidth());
QVERIFY(input->boundingRect().height() > line.height());
}
void tst_qquicktextinput::positionAt()
{
QQuickView window(testFileUrl("positionAt.qml"));
QVERIFY(window.rootObject() != nullptr);
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(window.rootObject());
QVERIFY(textinputObject != nullptr);
// Check autoscrolled...
int pos = evaluate<int>(textinputObject, QString("positionAt(%1)").arg(textinputObject->width()/2));
QTextLayout layout(textinputObject->text());
layout.setFont(textinputObject->font());
if (!qmlDisableDistanceField()) {
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
}
layout.beginLayout();
QTextLine line = layout.createLine();
layout.endLayout();
int textLeftWidthBegin = floor(line.cursorToX(pos - 1));
int textLeftWidthEnd = ceil(line.cursorToX(pos + 1));
int textWidth = floor(line.horizontalAdvance());
QVERIFY(textLeftWidthBegin <= textWidth - textinputObject->width() / 2);
QVERIFY(textLeftWidthEnd >= textWidth - textinputObject->width() / 2);
int x = textinputObject->positionToRectangle(pos + 1).x() - 1;
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1);
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos);
// Check without autoscroll...
textinputObject->setAutoScroll(false);
pos = evaluate<int>(textinputObject, QString("positionAt(%1)").arg(textinputObject->width() / 2));
textLeftWidthBegin = floor(line.cursorToX(pos - 1));
textLeftWidthEnd = ceil(line.cursorToX(pos + 1));
QVERIFY(textLeftWidthBegin <= textinputObject->width() / 2);
QVERIFY(textLeftWidthEnd >= textinputObject->width() / 2);
x = textinputObject->positionToRectangle(pos + 1).x() - 1;
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1);
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos);
const qreal x0 = textinputObject->positionToRectangle(pos).x();
const qreal x1 = textinputObject->positionToRectangle(pos + 1).x();
QString preeditText = textinputObject->text().mid(0, pos);
textinputObject->setText(textinputObject->text().mid(pos));
textinputObject->setCursorPosition(0);
{ QInputMethodEvent inputEvent(preeditText, QList<QInputMethodEvent::Attribute>());
QVERIFY(qGuiApp->focusObject());
QGuiApplication::sendEvent(textinputObject, &inputEvent); }
// Check all points within the preedit text return the same position.
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(0)), 0);
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x0 / 2)), 0);
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x0)), 0);
// Verify positioning returns to normal after the preedit text.
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x1)), 1);
QCOMPARE(textinputObject->positionToRectangle(1).x(), x1);
{ QInputMethodEvent inputEvent;
QVERIFY(qGuiApp->focusObject());
QGuiApplication::sendEvent(textinputObject, &inputEvent); }
// With wrapping.
textinputObject->setWrapMode(QQuickTextInput::WrapAnywhere);
const qreal y0 = line.height() / 2;
const qreal y1 = line.height() * 3 / 2;
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y0)), pos);
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y0)), pos + 1);
int newLinePos = evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y1));
QVERIFY(newLinePos > pos);
QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y1)), newLinePos + 1);
}
void tst_qquicktextinput::maxLength()
{
QQuickView window(testFileUrl("maxLength.qml"));
QVERIFY(window.rootObject() != nullptr);
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(window.rootObject());
QVERIFY(textinputObject != nullptr);
QVERIFY(textinputObject->text().isEmpty());
QCOMPARE(textinputObject->maxLength(), 10);
foreach (const QString &str, standard) {
QVERIFY(textinputObject->text().length() <= 10);
textinputObject->setText(str);
QVERIFY(textinputObject->text().length() <= 10);
}
textinputObject->setText("");
QTRY_VERIFY(textinputObject->hasActiveFocus());
for (int i=0; i<20; i++) {
QTRY_COMPARE(textinputObject->text().length(), qMin(i,10));
//simulateKey(&window, Qt::Key_A);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
}
}
void tst_qquicktextinput::masks()
{
//Not a comprehensive test of the possible masks, that's done elsewhere (QLineEdit)
//QString componentStr = "import QtQuick 2.0\nTextInput { inputMask: 'HHHHhhhh'; }";
QQuickView window(testFileUrl("masks.qml"));
window.show();
window.requestActivate();
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(window.rootObject());
QVERIFY(textinputObject != nullptr);
QTRY_VERIFY(textinputObject->hasActiveFocus());
QCOMPARE(textinputObject->text().length(), 0);
QCOMPARE(textinputObject->inputMask(), QString("HHHHhhhh; "));
QCOMPARE(textinputObject->length(), 8);
for (int i=0; i<10; i++) {
QTRY_COMPARE(qMin(i,8), textinputObject->text().length());
QCOMPARE(textinputObject->length(), 8);
QCOMPARE(textinputObject->getText(0, qMin(i, 8)), QString(qMin(i, 8), 'a'));
QCOMPARE(textinputObject->getText(qMin(i, 8), 8), QString(8 - qMin(i, 8), ' '));
QCOMPARE(i>=4, textinputObject->hasAcceptableInput());
//simulateKey(&window, Qt::Key_A);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
}
}
void tst_qquicktextinput::validators()
{
// Note that this test assumes that the validators are working properly
// so you may need to run their tests first. All validators are checked
// here to ensure that their exposure to QML is working.
QLocale::setDefault(QLocale(QStringLiteral("C")));
QQuickView window(testFileUrl("validators.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QLocale defaultLocale;
QLocale enLocale("en");
QLocale deLocale("de_DE");
QQuickTextInput *intInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("intInput")));
QVERIFY(intInput);
QSignalSpy intSpy(intInput, SIGNAL(acceptableInputChanged()));
QQuickIntValidator *intValidator = qobject_cast<QQuickIntValidator *>(intInput->validator());
QVERIFY(intValidator);
QCOMPARE(intValidator->localeName(), defaultLocale.name());
QCOMPARE(intInput->validator()->locale(), defaultLocale);
intValidator->setLocaleName(enLocale.name());
QCOMPARE(intValidator->localeName(), enLocale.name());
QCOMPARE(intInput->validator()->locale(), enLocale);
intValidator->resetLocaleName();
QCOMPARE(intValidator->localeName(), defaultLocale.name());
QCOMPARE(intInput->validator()->locale(), defaultLocale);
intInput->setFocus(true);
QTRY_VERIFY(intInput->hasActiveFocus());
QCOMPARE(intInput->hasAcceptableInput(), false);
QCOMPARE(intInput->property("acceptable").toBool(), false);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(intInput->text(), QLatin1String("1"));
QCOMPARE(intInput->hasAcceptableInput(), false);
QCOMPARE(intInput->property("acceptable").toBool(), false);
QCOMPARE(intSpy.count(), 0);
QCOMPARE(intInput->hasAcceptableInput(), false);
QCOMPARE(intInput->property("acceptable").toBool(), false);
QCOMPARE(intSpy.count(), 0);
QTest::keyPress(&window, Qt::Key_Period);
QTest::keyRelease(&window, Qt::Key_Period, Qt::NoModifier);
QTRY_COMPARE(intInput->text(), QLatin1String("1"));
QCOMPARE(intInput->hasAcceptableInput(), false);
QTest::keyPress(&window, Qt::Key_Comma);
QTest::keyRelease(&window, Qt::Key_Comma, Qt::NoModifier);
QTRY_COMPARE(intInput->text(), QLatin1String("1,"));
QCOMPARE(intInput->hasAcceptableInput(), false);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(intInput->text(), QLatin1String("1"));
QCOMPARE(intInput->hasAcceptableInput(), false);
intValidator->setLocaleName(deLocale.name());
QTest::keyPress(&window, Qt::Key_Period);
QTest::keyRelease(&window, Qt::Key_Period, Qt::NoModifier);
QTRY_COMPARE(intInput->text(), QLatin1String("1."));
QCOMPARE(intInput->hasAcceptableInput(), false);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(intInput->text(), QLatin1String("1"));
QCOMPARE(intInput->hasAcceptableInput(), false);
intValidator->resetLocaleName();
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QCOMPARE(intInput->text(), QLatin1String("11"));
QCOMPARE(intInput->hasAcceptableInput(), true);
QCOMPARE(intInput->property("acceptable").toBool(), true);
QCOMPARE(intSpy.count(), 1);
QTest::keyPress(&window, Qt::Key_0);
QTest::keyRelease(&window, Qt::Key_0, Qt::NoModifier);
QCOMPARE(intInput->text(), QLatin1String("11"));
QCOMPARE(intInput->hasAcceptableInput(), true);
QCOMPARE(intInput->property("acceptable").toBool(), true);
QCOMPARE(intSpy.count(), 1);
QQuickTextInput *dblInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("dblInput")));
QVERIFY(dblInput);
QSignalSpy dblSpy(dblInput, SIGNAL(acceptableInputChanged()));
QQuickDoubleValidator *dblValidator = qobject_cast<QQuickDoubleValidator *>(dblInput->validator());
QVERIFY(dblValidator);
QCOMPARE(dblValidator->localeName(), defaultLocale.name());
QCOMPARE(dblInput->validator()->locale(), defaultLocale);
dblValidator->setLocaleName(enLocale.name());
QCOMPARE(dblValidator->localeName(), enLocale.name());
QCOMPARE(dblInput->validator()->locale(), enLocale);
dblValidator->resetLocaleName();
QCOMPARE(dblValidator->localeName(), defaultLocale.name());
QCOMPARE(dblInput->validator()->locale(), defaultLocale);
dblInput->setFocus(true);
QVERIFY(dblInput->hasActiveFocus());
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QCOMPARE(dblSpy.count(), 0);
QTest::keyPress(&window, Qt::Key_2);
QTest::keyRelease(&window, Qt::Key_2, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QCOMPARE(dblInput->property("acceptable").toBool(), true);
QCOMPARE(dblSpy.count(), 1);
QTest::keyPress(&window, Qt::Key_Comma);
QTest::keyRelease(&window, Qt::Key_Comma, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
int extraSignals = 2;
if (dblInput->hasAcceptableInput()) {
// TODO: old behavior of QDoubleValidator - remove when merged from qtbase
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
extraSignals = 0;
}
dblValidator->setLocaleName(deLocale.name());
QCOMPARE(dblInput->hasAcceptableInput(), true);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12,1"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12,11"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12,1"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
dblValidator->resetLocaleName();
QTest::keyPress(&window, Qt::Key_Period);
QTest::keyRelease(&window, Qt::Key_Period, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QCOMPARE(dblInput->property("acceptable").toBool(), true);
QCOMPARE(dblSpy.count(), 1 + extraSignals);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QCOMPARE(dblInput->property("acceptable").toBool(), true);
QCOMPARE(dblSpy.count(), 1 + extraSignals);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QCOMPARE(dblInput->property("acceptable").toBool(), true);
QCOMPARE(dblSpy.count(), 1 + extraSignals);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
QCOMPARE(dblInput->hasAcceptableInput(), true);
QCOMPARE(dblInput->property("acceptable").toBool(), true);
QCOMPARE(dblSpy.count(), 1 + extraSignals);
// Ensure the validator doesn't prevent characters being removed.
dblInput->setValidator(intInput->validator());
QCOMPARE(dblInput->text(), QLatin1String("12.11"));
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QCOMPARE(dblSpy.count(), 2 + extraSignals);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QCOMPARE(dblSpy.count(), 2 + extraSignals);
// Once unacceptable input is in anything goes until it reaches an acceptable state again.
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblSpy.count(), 2 + extraSignals);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QCOMPARE(dblSpy.count(), 2 + extraSignals);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QCOMPARE(dblSpy.count(), 2 + extraSignals);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QCOMPARE(dblSpy.count(), 2 + extraSignals);
QTest::keyPress(&window, Qt::Key_Backspace);
QTest::keyRelease(&window, Qt::Key_Backspace, Qt::NoModifier);
QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QCOMPARE(dblSpy.count(), 2 + extraSignals);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QCOMPARE(dblInput->text(), QLatin1String("11"));
QCOMPARE(dblInput->property("acceptable").toBool(), true);
QCOMPARE(dblInput->hasAcceptableInput(), true);
QCOMPARE(dblSpy.count(), 3 + extraSignals);
// Changing the validator properties will re-evaluate whether the input is acceptable.
intValidator->setTop(10);
QCOMPARE(dblInput->property("acceptable").toBool(), false);
QCOMPARE(dblInput->hasAcceptableInput(), false);
QCOMPARE(dblSpy.count(), 4 + extraSignals);
intValidator->setTop(12);
QCOMPARE(dblInput->property("acceptable").toBool(), true);
QCOMPARE(dblInput->hasAcceptableInput(), true);
QCOMPARE(dblSpy.count(), 5 + extraSignals);
QQuickTextInput *strInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("strInput")));
QVERIFY(strInput);
QSignalSpy strSpy(strInput, SIGNAL(acceptableInputChanged()));
strInput->setFocus(true);
QVERIFY(strInput->hasActiveFocus());
QCOMPARE(strInput->hasAcceptableInput(), false);
QCOMPARE(strInput->property("acceptable").toBool(), false);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(strInput->text(), QLatin1String(""));
QCOMPARE(strInput->hasAcceptableInput(), false);
QCOMPARE(strInput->property("acceptable").toBool(), false);
QCOMPARE(strSpy.count(), 0);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(strInput->text(), QLatin1String("a"));
QCOMPARE(strInput->hasAcceptableInput(), false);
QCOMPARE(strInput->property("acceptable").toBool(), false);
QCOMPARE(strSpy.count(), 0);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(strInput->text(), QLatin1String("aa"));
QCOMPARE(strInput->hasAcceptableInput(), true);
QCOMPARE(strInput->property("acceptable").toBool(), true);
QCOMPARE(strSpy.count(), 1);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(strInput->text(), QLatin1String("aaa"));
QCOMPARE(strInput->hasAcceptableInput(), true);
QCOMPARE(strInput->property("acceptable").toBool(), true);
QCOMPARE(strSpy.count(), 1);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
QCOMPARE(strInput->hasAcceptableInput(), true);
QCOMPARE(strInput->property("acceptable").toBool(), true);
QCOMPARE(strSpy.count(), 1);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
QCOMPARE(strInput->hasAcceptableInput(), true);
QCOMPARE(strInput->property("acceptable").toBool(), true);
QCOMPARE(strSpy.count(), 1);
QQuickTextInput *unvalidatedInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("unvalidatedInput")));
QVERIFY(unvalidatedInput);
QSignalSpy unvalidatedSpy(unvalidatedInput, SIGNAL(acceptableInputChanged()));
unvalidatedInput->setFocus(true);
QVERIFY(unvalidatedInput->hasActiveFocus());
QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
QTest::keyPress(&window, Qt::Key_1);
QTest::keyRelease(&window, Qt::Key_1, Qt::NoModifier);
QTRY_COMPARE(unvalidatedInput->text(), QLatin1String("1"));
QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
QCOMPARE(unvalidatedSpy.count(), 0);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(unvalidatedInput->text(), QLatin1String("1a"));
QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
QCOMPARE(unvalidatedSpy.count(), 0);
}
void tst_qquicktextinput::inputMethods()
{
QQuickView window(testFileUrl("inputmethods.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
// test input method hints
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(window.rootObject());
QVERIFY(input != nullptr);
QVERIFY(input->inputMethodHints() & Qt::ImhNoPredictiveText);
QSignalSpy inputMethodHintSpy(input, SIGNAL(inputMethodHintsChanged()));
input->setInputMethodHints(Qt::ImhUppercaseOnly);
QVERIFY(input->inputMethodHints() & Qt::ImhUppercaseOnly);
QCOMPARE(inputMethodHintSpy.count(), 1);
input->setInputMethodHints(Qt::ImhUppercaseOnly);
QCOMPARE(inputMethodHintSpy.count(), 1);
// default value
QQuickTextInput plainInput;
QCOMPARE(plainInput.inputMethodHints(), Qt::ImhNone);
input->setFocus(true);
QVERIFY(input->hasActiveFocus());
// test that input method event is committed
QInputMethodEvent event;
event.setCommitString( "My ", -12, 0);
QTRY_COMPARE(qGuiApp->focusObject(), input);
QGuiApplication::sendEvent(input, &event);
QCOMPARE(input->text(), QString("My Hello world!"));
QCOMPARE(input->displayText(), QString("My Hello world!"));
input->setCursorPosition(2);
event.setCommitString("Your", -2, 2);
QGuiApplication::sendEvent(input, &event);
QCOMPARE(input->text(), QString("Your Hello world!"));
QCOMPARE(input->displayText(), QString("Your Hello world!"));
QCOMPARE(input->cursorPosition(), 4);
input->setCursorPosition(7);
event.setCommitString("Goodbye", -2, 5);
QGuiApplication::sendEvent(input, &event);
QCOMPARE(input->text(), QString("Your Goodbye world!"));
QCOMPARE(input->displayText(), QString("Your Goodbye world!"));
QCOMPARE(input->cursorPosition(), 12);
input->setCursorPosition(8);
event.setCommitString("Our", -8, 4);
QGuiApplication::sendEvent(input, &event);
QCOMPARE(input->text(), QString("Our Goodbye world!"));
QCOMPARE(input->displayText(), QString("Our Goodbye world!"));
QCOMPARE(input->cursorPosition(), 3);
input->setCursorPosition(7);
QInputMethodEvent preeditEvent("PREEDIT", QList<QInputMethodEvent::Attribute>());
QGuiApplication::sendEvent(input, &preeditEvent);
QCOMPARE(input->text(), QString("Our Goodbye world!"));
QCOMPARE(input->displayText(), QString("Our GooPREEDITdbye world!"));
QCOMPARE(input->preeditText(), QString("PREEDIT"));
QInputMethodEvent preeditEvent2("PREEDIT2", QList<QInputMethodEvent::Attribute>());
QGuiApplication::sendEvent(input, &preeditEvent2);
QCOMPARE(input->text(), QString("Our Goodbye world!"));
QCOMPARE(input->displayText(), QString("Our GooPREEDIT2dbye world!"));
QCOMPARE(input->preeditText(), QString("PREEDIT2"));
QInputMethodEvent preeditEvent3("", QList<QInputMethodEvent::Attribute>());
QGuiApplication::sendEvent(input, &preeditEvent3);
QCOMPARE(input->text(), QString("Our Goodbye world!"));
QCOMPARE(input->displayText(), QString("Our Goodbye world!"));
QCOMPARE(input->preeditText(), QString(""));
// input should reset selection even if replacement parameters are out of bounds
input->setText("text");
input->setCursorPosition(0);
input->moveCursorSelection(input->text().length());
event.setCommitString("replacement", -input->text().length(), input->text().length());
QGuiApplication::sendEvent(input, &event);
QCOMPARE(input->selectionStart(), input->selectionEnd());
QCOMPARE(input->text(), QString("replacement"));
QCOMPARE(input->displayText(), QString("replacement"));
QInputMethodQueryEvent enabledQueryEvent(Qt::ImEnabled);
QGuiApplication::sendEvent(input, &enabledQueryEvent);
QCOMPARE(enabledQueryEvent.value(Qt::ImEnabled).toBool(), true);
input->setReadOnly(true);
QGuiApplication::sendEvent(input, &enabledQueryEvent);
QCOMPARE(enabledQueryEvent.value(Qt::ImEnabled).toBool(), false);
}
void tst_qquicktextinput::signal_accepted()
{
QQuickView window(testFileUrl("signal_accepted.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("input")));
QVERIFY(input);
QSignalSpy acceptedSpy(input, SIGNAL(accepted()));
QSignalSpy inputSpy(input, SIGNAL(acceptableInputChanged()));
input->setFocus(true);
QTRY_VERIFY(input->hasActiveFocus());
QCOMPARE(input->hasAcceptableInput(), false);
QCOMPARE(input->property("acceptable").toBool(), false);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(input->text(), QLatin1String("a"));
QCOMPARE(input->hasAcceptableInput(), false);
QCOMPARE(input->property("acceptable").toBool(), false);
QTRY_COMPARE(inputSpy.count(), 0);
QTest::keyPress(&window, Qt::Key_Enter);
QTest::keyRelease(&window, Qt::Key_Enter, Qt::NoModifier);
QTRY_COMPARE(acceptedSpy.count(), 0);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(input->text(), QLatin1String("aa"));
QCOMPARE(input->hasAcceptableInput(), true);
QCOMPARE(input->property("acceptable").toBool(), true);
QTRY_COMPARE(inputSpy.count(), 1);
QTest::keyPress(&window, Qt::Key_Enter);
QTest::keyRelease(&window, Qt::Key_Enter, Qt::NoModifier);
QTRY_COMPARE(acceptedSpy.count(), 1);
}
void tst_qquicktextinput::signal_editingfinished()
{
QQuickView window(testFileUrl("signal_editingfinished.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input1 = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("input1")));
QVERIFY(input1);
QQuickTextInput *input2 = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("input2")));
QVERIFY(input2);
QSignalSpy editingFinished1Spy(input1, SIGNAL(editingFinished()));
QSignalSpy input1Spy(input1, SIGNAL(acceptableInputChanged()));
input1->setFocus(true);
QTRY_VERIFY(input1->hasActiveFocus());
QTRY_VERIFY(!input2->hasActiveFocus());
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(input1->text(), QLatin1String("a"));
QCOMPARE(input1->hasAcceptableInput(), false);
QCOMPARE(input1->property("acceptable").toBool(), false);
QTRY_COMPARE(input1Spy.count(), 0);
QTest::keyPress(&window, Qt::Key_Enter);
QTest::keyRelease(&window, Qt::Key_Enter, Qt::NoModifier);
QTRY_COMPARE(editingFinished1Spy.count(), 0);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(input1->text(), QLatin1String("aa"));
QCOMPARE(input1->hasAcceptableInput(), true);
QCOMPARE(input1->property("acceptable").toBool(), true);
QTRY_COMPARE(input1Spy.count(), 1);
QTest::keyPress(&window, Qt::Key_Enter);
QTest::keyRelease(&window, Qt::Key_Enter, Qt::NoModifier);
QTRY_COMPARE(editingFinished1Spy.count(), 1);
QTRY_COMPARE(input1Spy.count(), 1);
QSignalSpy editingFinished2Spy(input2, SIGNAL(editingFinished()));
QSignalSpy input2Spy(input2, SIGNAL(acceptableInputChanged()));
input2->setFocus(true);
QTRY_VERIFY(!input1->hasActiveFocus());
QTRY_VERIFY(input2->hasActiveFocus());
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(input2->text(), QLatin1String("a"));
QCOMPARE(input2->hasAcceptableInput(), false);
QCOMPARE(input2->property("acceptable").toBool(), false);
QTRY_COMPARE(input2Spy.count(), 0);
QTest::keyPress(&window, Qt::Key_A);
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier);
QTRY_COMPARE(input2->text(), QLatin1String("aa"));
QCOMPARE(input2->hasAcceptableInput(), true);
QCOMPARE(input2->property("acceptable").toBool(), true);
QTRY_COMPARE(input2Spy.count(), 1);
input1->setFocus(true);
QTRY_VERIFY(input1->hasActiveFocus());
QTRY_VERIFY(!input2->hasActiveFocus());
QTRY_COMPARE(editingFinished2Spy.count(), 1);
}
void tst_qquicktextinput::signal_textEdited()
{
QQuickWindow window;
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QQuickTextInput *input = new QQuickTextInput(window.contentItem());
QVERIFY(input);
QSignalSpy textChangedSpy(input, SIGNAL(textChanged()));
QVERIFY(textChangedSpy.isValid());
QSignalSpy textEditedSpy(input, SIGNAL(textEdited()));
QVERIFY(textEditedSpy.isValid());
input->forceActiveFocus();
QTRY_VERIFY(input->hasActiveFocus());
int textChanges = 0;
int textEdits = 0;
QTest::keyClick(&window, Qt::Key_A);
QCOMPARE(textChangedSpy.count(), ++textChanges);
QCOMPARE(textEditedSpy.count(), ++textEdits);
QTest::keyClick(&window, Qt::Key_B);
QCOMPARE(textChangedSpy.count(), ++textChanges);
QCOMPARE(textEditedSpy.count(), ++textEdits);
QTest::keyClick(&window, Qt::Key_C);
QCOMPARE(textChangedSpy.count(), ++textChanges);
QCOMPARE(textEditedSpy.count(), ++textEdits);
QTest::keyClick(&window, Qt::Key_Space);
QCOMPARE(textChangedSpy.count(), ++textChanges);
QCOMPARE(textEditedSpy.count(), ++textEdits);
QTest::keyClick(&window, Qt::Key_Backspace);
QCOMPARE(textChangedSpy.count(), ++textChanges);
QCOMPARE(textEditedSpy.count(), ++textEdits);
input->clear();
QCOMPARE(textChangedSpy.count(), ++textChanges);
QCOMPARE(textEditedSpy.count(), textEdits);
input->setText("TextInput");
QCOMPARE(textChangedSpy.count(), ++textChanges);
QCOMPARE(textEditedSpy.count(), textEdits);
}
/*
TextInput element should only handle left/right keys until the cursor reaches
the extent of the text, then they should ignore the keys.
*/
void tst_qquicktextinput::navigation()
{
QQuickView window(testFileUrl("navigation.qml"));
window.show();
window.requestActivate();
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("myInput")));
QVERIFY(input != nullptr);
input->setCursorPosition(0);
QTRY_VERIFY(input->hasActiveFocus());
simulateKey(&window, Qt::Key_Left);
QVERIFY(!input->hasActiveFocus());
simulateKey(&window, Qt::Key_Right);
QVERIFY(input->hasActiveFocus());
//QT-2944: If text is selected, ensure we deselect upon cursor motion
input->setCursorPosition(input->text().length());
input->select(0,input->text().length());
QVERIFY(input->selectionStart() != input->selectionEnd());
simulateKey(&window, Qt::Key_Right);
QCOMPARE(input->selectionStart(), input->selectionEnd());
QCOMPARE(input->selectionStart(), input->text().length());
QVERIFY(input->hasActiveFocus());
simulateKey(&window, Qt::Key_Right);
QVERIFY(!input->hasActiveFocus());
simulateKey(&window, Qt::Key_Left);
QVERIFY(input->hasActiveFocus());
// Up and Down should NOT do Home/End, even on OS X (QTBUG-10438).
input->setCursorPosition(2);
QCOMPARE(input->cursorPosition(),2);
simulateKey(&window, Qt::Key_Up);
QCOMPARE(input->cursorPosition(),2);
simulateKey(&window, Qt::Key_Down);
QCOMPARE(input->cursorPosition(),2);
// Test left and right navigation works if the TextInput is empty (QTBUG-25447).
input->setText(QString());
QCOMPARE(input->cursorPosition(), 0);
simulateKey(&window, Qt::Key_Right);
QCOMPARE(input->hasActiveFocus(), false);
simulateKey(&window, Qt::Key_Left);
QCOMPARE(input->hasActiveFocus(), true);
simulateKey(&window, Qt::Key_Left);
QCOMPARE(input->hasActiveFocus(), false);
}
void tst_qquicktextinput::navigation_RTL()
{
QQuickView window(testFileUrl("navigation.qml"));
window.show();
window.requestActivate();
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("myInput")));
QVERIFY(input != nullptr);
const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
input->setText(QString::fromUtf16(arabic_str, 11));
input->setCursorPosition(0);
QTRY_VERIFY(input->hasActiveFocus());
// move off
simulateKey(&window, Qt::Key_Right);
QVERIFY(!input->hasActiveFocus());
// move back
simulateKey(&window, Qt::Key_Left);
QVERIFY(input->hasActiveFocus());
input->setCursorPosition(input->text().length());
QVERIFY(input->hasActiveFocus());
// move off
simulateKey(&window, Qt::Key_Left);
QVERIFY(!input->hasActiveFocus());
// move back
simulateKey(&window, Qt::Key_Right);
QVERIFY(input->hasActiveFocus());
}
#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
void tst_qquicktextinput::copyAndPaste()
{
if (!PlatformQuirks::isClipboardAvailable())
QSKIP("This machine doesn't support the clipboard");
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
// copy and paste
QCOMPARE(textInput->text().length(), 12);
textInput->select(0, textInput->text().length());
textInput->copy();
QCOMPARE(textInput->selectedText(), QString("Hello world!"));
QCOMPARE(textInput->selectedText().length(), 12);
textInput->setCursorPosition(0);
QTRY_VERIFY(textInput->canPaste());
textInput->paste();
QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
QCOMPARE(textInput->text().length(), 24);
// can paste
QVERIFY(textInput->canPaste());
textInput->setReadOnly(true);
QVERIFY(!textInput->canPaste());
textInput->paste();
QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
QCOMPARE(textInput->text().length(), 24);
textInput->setReadOnly(false);
QVERIFY(textInput->canPaste());
// cut: no selection
textInput->cut();
QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
// select word
textInput->setCursorPosition(0);
textInput->selectWord();
QCOMPARE(textInput->selectedText(), QString("Hello"));
// cut: read only.
textInput->setReadOnly(true);
textInput->cut();
QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
textInput->setReadOnly(false);
// select all and cut
textInput->selectAll();
textInput->cut();
QCOMPARE(textInput->text().length(), 0);
textInput->paste();
QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
QCOMPARE(textInput->text().length(), 24);
// Copy first word.
textInput->setCursorPosition(0);
textInput->selectWord();
textInput->copy();
// copy: no selection, previous copy retained;
textInput->setCursorPosition(0);
QCOMPARE(textInput->selectedText(), QString());
textInput->copy();
textInput->setText(QString());
textInput->paste();
QCOMPARE(textInput->text(), QString("Hello"));
// clear copy buffer
QClipboard *clipboard = QGuiApplication::clipboard();
QVERIFY(clipboard);
clipboard->clear();
QTRY_VERIFY(!textInput->canPaste());
// test that copy functionality is disabled
// when echo mode is set to hide text/password mode
int index = 0;
while (index < 4) {
QQuickTextInput::EchoMode echoMode = QQuickTextInput::EchoMode(index);
textInput->setEchoMode(echoMode);
textInput->setText("My password");
textInput->select(0, textInput->text().length());
textInput->copy();
if (echoMode == QQuickTextInput::Normal) {
QVERIFY(!clipboard->text().isEmpty());
QCOMPARE(clipboard->text(), QString("My password"));
clipboard->clear();
} else {
QVERIFY(!clipboard->ownsSelection() || clipboard->text().isEmpty());
}
index++;
}
delete textInput;
}
#endif
#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
void tst_qquicktextinput::copyAndPasteKeySequence()
{
if (!PlatformQuirks::isClipboardAvailable())
QSKIP("This machine doesn't support the clipboard");
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\"; focus: true }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
// copy and paste
QVERIFY(textInput->hasActiveFocus());
QCOMPARE(textInput->text().length(), 12);
textInput->select(0, textInput->text().length());
simulateKeys(&window, QKeySequence::Copy);
QCOMPARE(textInput->selectedText(), QString("Hello world!"));
QCOMPARE(textInput->selectedText().length(), 12);
textInput->setCursorPosition(0);
QVERIFY(textInput->canPaste());
simulateKeys(&window, QKeySequence::Paste);
QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
QCOMPARE(textInput->text().length(), 24);
// select all and cut
simulateKeys(&window, QKeySequence::SelectAll);
simulateKeys(&window, QKeySequence::Cut);
QCOMPARE(textInput->text().length(), 0);
simulateKeys(&window, QKeySequence::Paste);
QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
QCOMPARE(textInput->text().length(), 24);
// clear copy buffer
QClipboard *clipboard = QGuiApplication::clipboard();
QVERIFY(clipboard);
clipboard->clear();
QTRY_VERIFY(!textInput->canPaste());
// test that copy functionality is disabled
// when echo mode is set to hide text/password mode
int index = 0;
while (index < 4) {
QQuickTextInput::EchoMode echoMode = QQuickTextInput::EchoMode(index);
textInput->setEchoMode(echoMode);
textInput->setText("My password");
textInput->select(0, textInput->text().length());
simulateKeys(&window, QKeySequence::Copy);
if (echoMode == QQuickTextInput::Normal) {
QVERIFY(!clipboard->text().isEmpty());
QCOMPARE(clipboard->text(), QString("My password"));
clipboard->clear();
} else {
QVERIFY(!clipboard->ownsClipboard() || clipboard->text().isEmpty());
}
index++;
}
delete textInput;
}
#endif
#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
void tst_qquicktextinput::canPasteEmpty()
{
QGuiApplication::clipboard()->clear();
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
bool cp = !textInput->isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
QCOMPARE(textInput->canPaste(), cp);
}
#endif
#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
void tst_qquicktextinput::canPaste()
{
QGuiApplication::clipboard()->setText("Some text");
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
bool cp = !textInput->isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
QCOMPARE(textInput->canPaste(), cp);
}
#endif
#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
void tst_qquicktextinput::middleClickPaste()
{
if (!PlatformQuirks::isClipboardAvailable())
QSKIP("This machine doesn't support the clipboard");
QQuickView window(testFileUrl("mouseselection_true.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(window.rootObject());
QVERIFY(textInputObject != nullptr);
textInputObject->setFocus(true);
QString originalText = textInputObject->text();
QString selectedText = "234567";
// press-and-drag-and-release from x1 to x2
const QPoint p1 = textInputObject->positionToRectangle(2).center().toPoint();
const QPoint p2 = textInputObject->positionToRectangle(8).center().toPoint();
const QPoint p3 = textInputObject->positionToRectangle(1).center().toPoint();
QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, p1);
QTest::mouseMove(&window, p2);
QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, p2);
QTRY_COMPARE(textInputObject->selectedText(), selectedText);
// Middle click pastes the selected text, assuming the platform supports it.
QTest::mouseClick(&window, Qt::MiddleButton, Qt::NoModifier, p3);
// ### This is to prevent double click detection from carrying over to the next test.
QTest::qWait(QGuiApplication::styleHints()->mouseDoubleClickInterval() + 10);
if (QGuiApplication::clipboard()->supportsSelection())
QCOMPARE(textInputObject->text().mid(1, selectedText.length()), selectedText);
else
QCOMPARE(textInputObject->text(), originalText);
}
#endif
void tst_qquicktextinput::passwordCharacter()
{
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\"; font.family: \"Helvetica\"; echoMode: TextInput.Password }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
textInput->setPasswordCharacter("X");
qreal implicitWidth = textInput->implicitWidth();
textInput->setPasswordCharacter(".");
// QTBUG-12383 content is updated and redrawn
QVERIFY(textInput->implicitWidth() < implicitWidth);
delete textInput;
}
void tst_qquicktextinput::cursorDelegate_data()
{
QTest::addColumn<QUrl>("source");
QTest::newRow("out of line") << testFileUrl("cursorTest.qml");
QTest::newRow("in line") << testFileUrl("cursorTestInline.qml");
QTest::newRow("external") << testFileUrl("cursorTestExternal.qml");
}
void tst_qquicktextinput::cursorDelegate()
{
QFETCH(QUrl, source);
QQuickView view(source);
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickTextInput *textInputObject = view.rootObject()->findChild<QQuickTextInput*>("textInputObject");
QVERIFY(textInputObject != nullptr);
// Delegate is created on demand, and so won't be available immediately. Focus in or
// setCursorVisible(true) will trigger creation.
QTRY_VERIFY(!textInputObject->findChild<QQuickItem*>("cursorInstance"));
QVERIFY(!textInputObject->isCursorVisible());
//Test Delegate gets created
textInputObject->setFocus(true);
QVERIFY(textInputObject->isCursorVisible());
QQuickItem* delegateObject = textInputObject->findChild<QQuickItem*>("cursorInstance");
QVERIFY(delegateObject);
QCOMPARE(delegateObject->property("localProperty").toString(), QString("Hello"));
//Test Delegate gets moved
for (int i=0; i<= textInputObject->text().length(); i++) {
textInputObject->setCursorPosition(i);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
}
textInputObject->setCursorPosition(0);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
// Test delegate gets moved on mouse press.
textInputObject->setSelectByMouse(true);
textInputObject->setCursorPosition(0);
const QPoint point1 = textInputObject->positionToRectangle(5).center().toPoint();
QTest::qWait(400); //ensure this isn't treated as a double-click
QTest::mouseClick(&view, Qt::LeftButton, Qt::NoModifier, point1);
QTest::qWait(50);
QTRY_VERIFY(textInputObject->cursorPosition() != 0);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
// Test delegate gets moved on mouse drag
textInputObject->setCursorPosition(0);
const QPoint point2 = textInputObject->positionToRectangle(10).center().toPoint();
QTest::qWait(400); //ensure this isn't treated as a double-click
QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, point1);
QMouseEvent mv(QEvent::MouseMove, point2, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
QGuiApplication::sendEvent(&view, &mv);
QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, point2);
QTest::qWait(50);
QTRY_COMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
textInputObject->setReadOnly(true);
textInputObject->setCursorPosition(0);
QTest::qWait(400); //ensure this isn't treated as a double-click
QTest::mouseClick(&view, Qt::LeftButton, Qt::NoModifier, textInputObject->positionToRectangle(5).center().toPoint());
QTest::qWait(50);
QTRY_VERIFY(textInputObject->cursorPosition() != 0);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
textInputObject->setCursorPosition(0);
QTest::qWait(400); //ensure this isn't treated as a double-click
QTest::mouseClick(&view, Qt::LeftButton, Qt::NoModifier, textInputObject->positionToRectangle(5).center().toPoint());
QTest::qWait(50);
QTRY_VERIFY(textInputObject->cursorPosition() != 0);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
textInputObject->setCursorPosition(0);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
textInputObject->setReadOnly(false);
// Delegate moved when text is entered
textInputObject->setText(QString());
for (int i = 0; i < 20; ++i) {
QTest::keyClick(&view, Qt::Key_A);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
}
// Delegate moved when text is entered by im.
textInputObject->setText(QString());
for (int i = 0; i < 20; ++i) {
QInputMethodEvent event;
event.setCommitString("w");
QGuiApplication::sendEvent(textInputObject, &event);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
}
// Delegate moved when text is removed by im.
for (int i = 19; i > 1; --i) {
QInputMethodEvent event;
event.setCommitString(QString(), -1, 1);
QGuiApplication::sendEvent(textInputObject, &event);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
}
{ // Delegate moved the text is changed in place by im.
QInputMethodEvent event;
event.setCommitString("i", -1, 1);
QGuiApplication::sendEvent(textInputObject, &event);
QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
}
//Test Delegate gets deleted
textInputObject->setCursorDelegate(nullptr);
QVERIFY(!textInputObject->findChild<QQuickItem*>("cursorInstance"));
}
void tst_qquicktextinput::remoteCursorDelegate()
{
ThreadedTestHTTPServer server(dataDirectory(), TestHTTPServer::Delay);
QQuickView view;
QQmlComponent component(view.engine(), server.url("/RemoteCursor.qml"));
view.rootContext()->setContextProperty("contextDelegate", &component);
view.setSource(testFileUrl("cursorTestRemote.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickTextInput *textInputObject = view.rootObject()->findChild<QQuickTextInput*>("textInputObject");
QVERIFY(textInputObject != nullptr);
// Delegate is created on demand, and so won't be available immediately. Focus in or
// setCursorVisible(true) will trigger creation.
QTRY_VERIFY(!textInputObject->findChild<QQuickItem*>("cursorInstance"));
QVERIFY(!textInputObject->isCursorVisible());
textInputObject->setFocus(true);
QVERIFY(textInputObject->isCursorVisible());
// Wait for component to load.
QTRY_COMPARE(component.status(), QQmlComponent::Ready);
QVERIFY(textInputObject->findChild<QQuickItem*>("cursorInstance"));
}
void tst_qquicktextinput::cursorVisible()
{
QSKIP("This test is unstable");
QQuickTextInput input;
input.componentComplete();
QSignalSpy spy(&input, SIGNAL(cursorVisibleChanged(bool)));
QQuickView view(testFileUrl("cursorVisible.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QCOMPARE(input.isCursorVisible(), false);
input.setCursorVisible(true);
QCOMPARE(input.isCursorVisible(), true);
QCOMPARE(spy.count(), 1);
input.setCursorVisible(false);
QCOMPARE(input.isCursorVisible(), false);
QCOMPARE(spy.count(), 2);
input.setFocus(true);
QCOMPARE(input.isCursorVisible(), false);
QCOMPARE(spy.count(), 2);
input.setParentItem(view.rootObject());
QCOMPARE(input.isCursorVisible(), true);
QCOMPARE(spy.count(), 3);
input.setFocus(false);
QCOMPARE(input.isCursorVisible(), false);
QCOMPARE(spy.count(), 4);
input.setFocus(true);
QCOMPARE(input.isCursorVisible(), true);
QCOMPARE(spy.count(), 5);
QQuickView alternateView;
alternateView.show();
alternateView.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&alternateView));
QCOMPARE(input.isCursorVisible(), false);
QCOMPARE(spy.count(), 6);
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&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()
{
const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
QTest::addColumn<QString>("text");
QTest::addColumn<int>("positionAtWidth");
QTest::addColumn<int>("wrapPosition");
QTest::addColumn<QString>("shortText");
QTest::addColumn<bool>("leftToRight");
QTest::newRow("left to right")
<< "Hello World!" << 5 << 11
<< "Hi"
<< true;
QTest::newRow("right to left")
<< QString::fromUtf16(arabic_str, lengthOf(arabic_str)) << 5 << 11
<< QString::fromUtf16(arabic_str, 3)
<< false;
}
#if QT_CONFIG(im)
#define COMPARE_INPUT_METHOD_QUERY(type, input, property, method, result) \
QCOMPARE((type) input->inputMethodQuery(property).method(), result);
#else
#define COMPARE_INPUT_METHOD_QUERY(type, input, property, method, result) \
qt_noop()
#endif
void tst_qquicktextinput::cursorRectangle()
{
QFETCH(QString, text);
QFETCH(int, positionAtWidth);
QFETCH(int, wrapPosition);
QFETCH(QString, shortText);
QFETCH(bool, leftToRight);
QQuickTextInput input;
input.setText(text);
input.componentComplete();
QTextLayout layout(text);
layout.setFont(input.font());
if (!qmlDisableDistanceField()) {
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
}
layout.beginLayout();
QTextLine line = layout.createLine();
layout.endLayout();
qreal offset = 0;
if (leftToRight) {
input.setWidth(line.cursorToX(positionAtWidth, QTextLine::Leading));
} else {
input.setWidth(line.horizontalAdvance() - line.cursorToX(positionAtWidth, QTextLine::Leading));
offset = line.horizontalAdvance() - input.width();
}
input.setHeight(qCeil(line.height() * 3 / 2));
QRectF r;
for (int i = 0; i <= positionAtWidth; ++i) {
input.setCursorPosition(i);
r = input.cursorRectangle();
QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
// Check the cursor rectangle remains within the input bounding rect when auto scrolling.
QCOMPARE(r.left(), leftToRight ? input.width() : 0);
for (int i = positionAtWidth + 1; i < text.length(); ++i) {
input.setCursorPosition(i);
QCOMPARE(r, input.cursorRectangle());
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
for (int i = text.length() - 2; i >= 0; --i) {
input.setCursorPosition(i);
r = input.cursorRectangle();
QCOMPARE(r.top(), 0.);
if (leftToRight) {
QVERIFY(r.right() >= 0);
} else {
QVERIFY(r.left() <= input.width());
}
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
// Check position with word wrap.
input.setWrapMode(QQuickTextInput::WordWrap);
input.setAutoScroll(false);
for (int i = 0; i < wrapPosition; ++i) {
input.setCursorPosition(i);
r = input.cursorRectangle();
QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
QCOMPARE(r.top(), 0.);
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
input.setCursorPosition(wrapPosition);
r = input.cursorRectangle();
if (leftToRight) {
QCOMPARE(r.left(), 0.);
} else {
QCOMPARE(r.left(), input.width());
}
// we can't be exact here, as the space character can have a different ascent/descent from the arabic chars
// this then leads to different line heights between the wrapped and non wrapped texts
QVERIFY(r.top() >= line.height() - 5);
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(11), r);
for (int i = wrapPosition + 1; i < text.length(); ++i) {
input.setCursorPosition(i);
r = input.cursorRectangle();
QVERIFY(r.top() >= line.height() - 5);
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
// Check vertical scrolling with word wrap.
input.setAutoScroll(true);
for (int i = 0; i <= positionAtWidth; ++i) {
input.setCursorPosition(i);
r = input.cursorRectangle();
QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
QCOMPARE(r.top(), 0.);
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
// Whitespace doesn't wrap, so scroll horizontally until the until the cursor
// reaches the next non-whitespace character.
QCOMPARE(r.left(), leftToRight ? input.width() : 0);
for (int i = positionAtWidth + 1; i < wrapPosition && leftToRight; ++i) {
input.setCursorPosition(i);
QCOMPARE(r, input.cursorRectangle());
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
input.setCursorPosition(wrapPosition);
r = input.cursorRectangle();
if (leftToRight) {
QCOMPARE(r.left(), 0.);
} else {
QCOMPARE(r.left(), input.width());
}
QVERIFY(r.bottom() >= input.height());
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(11), r);
for (int i = wrapPosition + 1; i < text.length(); ++i) {
input.setCursorPosition(i);
r = input.cursorRectangle();
QVERIFY(r.bottom() >= input.height());
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
for (int i = text.length() - 2; i >= wrapPosition; --i) {
input.setCursorPosition(i);
r = input.cursorRectangle();
QVERIFY(r.bottom() >= input.height());
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
input.setCursorPosition(wrapPosition - 1);
r = input.cursorRectangle();
QCOMPARE(r.top(), 0.);
QCOMPARE(r.left(), leftToRight ? input.width() : 0);
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(10), r);
for (int i = wrapPosition - 2; i >= positionAtWidth + 1; --i) {
input.setCursorPosition(i);
QCOMPARE(r, input.cursorRectangle());
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
for (int i = positionAtWidth; i >= 0; --i) {
input.setCursorPosition(i);
r = input.cursorRectangle();
QCOMPARE(r.top(), 0.);
COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
QCOMPARE(input.positionToRectangle(i), r);
}
input.setText(shortText);
input.setHAlign(leftToRight ? QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft);
r = input.cursorRectangle();
QCOMPARE(r.left(), leftToRight ? input.width() : 0);
QSignalSpy cursorRectangleSpy(&input, SIGNAL(cursorRectangleChanged()));
QString widerText = shortText;
widerText[1] = 'W'; // Assumes shortText is at least two characters long.
input.setText(widerText);
QCOMPARE(cursorRectangleSpy.count(), 1);
}
void tst_qquicktextinput::readOnly()
{
QQuickView window(testFileUrl("readOnly.qml"));
window.show();
window.requestActivate();
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("myInput")));
QVERIFY(input != nullptr);
QTRY_VERIFY(input->hasActiveFocus());
QVERIFY(input->isReadOnly());
QVERIFY(!input->isCursorVisible());
QString initial = input->text();
for (int k=Qt::Key_0; k<=Qt::Key_Z; k++)
simulateKey(&window, k);
simulateKey(&window, Qt::Key_Return);
simulateKey(&window, Qt::Key_Space);
simulateKey(&window, Qt::Key_Escape);
QCOMPARE(input->text(), initial);
input->setCursorPosition(3);
input->setReadOnly(false);
QCOMPARE(input->isReadOnly(), false);
QCOMPARE(input->cursorPosition(), input->text().length());
QVERIFY(input->isCursorVisible());
}
void tst_qquicktextinput::echoMode()
{
QQuickView window(testFileUrl("echoMode.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("myInput")));
QVERIFY(input != nullptr);
QTRY_VERIFY(input->hasActiveFocus());
QString initial = input->text();
Qt::InputMethodHints ref;
QCOMPARE(initial, QLatin1String("ABCDefgh"));
QCOMPARE(input->echoMode(), QQuickTextInput::Normal);
QCOMPARE(input->displayText(), input->text());
const QString passwordMaskCharacter = qApp->styleHints()->passwordMaskCharacter();
//Normal
ref &= ~Qt::ImhHiddenText;
ref &= ~(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
input->setEchoMode(QQuickTextInput::NoEcho);
QCOMPARE(input->text(), initial);
QCOMPARE(input->displayText(), QLatin1String(""));
QCOMPARE(input->passwordCharacter(), passwordMaskCharacter);
//NoEcho
ref |= Qt::ImhHiddenText;
ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
input->setEchoMode(QQuickTextInput::Password);
//Password
ref |= Qt::ImhHiddenText;
ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
QCOMPARE(input->text(), initial);
QCOMPARE(input->displayText(), QString(8, passwordMaskCharacter.at(0)));
COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
// clearing input hints do not clear bits set by echo mode
input->setInputMethodHints(Qt::ImhNone);
COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
input->setPasswordCharacter(QChar('Q'));
QCOMPARE(input->passwordCharacter(), QLatin1String("Q"));
QCOMPARE(input->text(), initial);
QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
input->setEchoMode(QQuickTextInput::PasswordEchoOnEdit);
//PasswordEchoOnEdit
ref &= ~Qt::ImhHiddenText;
ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
QCOMPARE(input->text(), initial);
QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
COMPARE_INPUT_METHOD_QUERY(QString, input, Qt::ImSurroundingText, toString,
QLatin1String("QQQQQQQQ"));
QTest::keyPress(&window, Qt::Key_A);//Clearing previous entry is part of PasswordEchoOnEdit
QTest::keyRelease(&window, Qt::Key_A, Qt::NoModifier ,10);
QCOMPARE(input->text(), QLatin1String("a"));
QCOMPARE(input->displayText(), QLatin1String("a"));
COMPARE_INPUT_METHOD_QUERY(QString, input, Qt::ImSurroundingText, toString, QLatin1String("a"));
input->setFocus(false);
QVERIFY(!input->hasActiveFocus());
QCOMPARE(input->displayText(), QLatin1String("Q"));
COMPARE_INPUT_METHOD_QUERY(QString, input, Qt::ImSurroundingText, toString, QLatin1String("Q"));
input->setFocus(true);
QVERIFY(input->hasActiveFocus());
QInputMethodEvent inputEvent;
inputEvent.setCommitString(initial);
QGuiApplication::sendEvent(input, &inputEvent);
QCOMPARE(input->text(), initial);
QCOMPARE(input->displayText(), initial);
COMPARE_INPUT_METHOD_QUERY(QString, input, Qt::ImSurroundingText, toString, initial);
}
void tst_qquicktextinput::passwordEchoDelay()
{
int maskDelay = qGuiApp->styleHints()->passwordMaskDelay();
if (maskDelay <= 0)
QSKIP("No mask delay in use");
QQuickView window(testFileUrl("echoMode.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("myInput")));
QVERIFY(input);
QVERIFY(input->hasActiveFocus());
QQuickItem *cursor = input->findChild<QQuickItem *>("cursor");
QVERIFY(cursor);
QChar fillChar = qApp->styleHints()->passwordMaskCharacter();
input->setEchoMode(QQuickTextInput::Password);
QCOMPARE(input->displayText(), QString(8, fillChar));
input->setText(QString());
QCOMPARE(input->displayText(), QString());
QTest::keyPress(&window, '0');
QTest::keyPress(&window, '1');
QTest::keyPress(&window, '2');
QCOMPARE(input->displayText(), QString(2, fillChar) + QLatin1Char('2'));
QTest::keyPress(&window, '3');
QTest::keyPress(&window, '4');
QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
QTest::keyPress(&window, Qt::Key_Backspace);
QCOMPARE(input->displayText(), QString(4, fillChar));
QTest::keyPress(&window, '4');
QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
QCOMPARE(input->cursorRectangle().topLeft(), cursor->position());
// Verify the last character entered is replaced by the fill character after a delay.
// Also check the cursor position is updated to accomdate a size difference between
// the fill character and the replaced character.
QSignalSpy cursorSpy(input, SIGNAL(cursorRectangleChanged()));
QTest::qWait(maskDelay);
QTRY_COMPARE(input->displayText(), QString(5, fillChar));
QCOMPARE(cursorSpy.count(), 1);
QCOMPARE(input->cursorRectangle().topLeft(), cursor->position());
QTest::keyPress(&window, '5');
QCOMPARE(input->displayText(), QString(5, fillChar) + QLatin1Char('5'));
input->setFocus(false);
QVERIFY(!input->hasFocus());
QCOMPARE(input->displayText(), QString(6, fillChar));
input->setFocus(true);
QTRY_VERIFY(input->hasFocus());
QCOMPARE(input->displayText(), QString(6, fillChar));
QTest::keyPress(&window, '6');
QCOMPARE(input->displayText(), QString(6, fillChar) + QLatin1Char('6'));
QInputMethodEvent ev;
ev.setCommitString(QLatin1String("7"));
QGuiApplication::sendEvent(input, &ev);
QCOMPARE(input->displayText(), QString(7, fillChar) + QLatin1Char('7'));
input->setCursorPosition(3);
QCOMPARE(input->displayText(), QString(7, fillChar) + QLatin1Char('7'));
QTest::keyPress(&window, 'a');
QCOMPARE(input->displayText(), QString(3, fillChar) + QLatin1Char('a') + QString(5, fillChar));
QTest::keyPress(&window, Qt::Key_Backspace);
QCOMPARE(input->displayText(), QString(8, fillChar));
}
void tst_qquicktextinput::simulateKey(QWindow *view, int key)
{
QKeyEvent press(QKeyEvent::KeyPress, key, nullptr);
QKeyEvent release(QKeyEvent::KeyRelease, key, nullptr);
QGuiApplication::sendEvent(view, &press);
QGuiApplication::sendEvent(view, &release);
}
void tst_qquicktextinput::focusOnPress()
{
QString componentStr =
"import QtQuick 2.0\n"
"TextInput {\n"
"property bool selectOnFocus: false\n"
"width: 100; height: 50\n"
"activeFocusOnPress: true\n"
"text: \"Hello World\"\n"
"onFocusChanged: { if (focus && selectOnFocus) selectAll() }"
" }";
QQmlComponent texteditComponent(&engine);
texteditComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput*>(texteditComponent.create());
QVERIFY(textInputObject != nullptr);
QCOMPARE(textInputObject->focusOnPress(), true);
QCOMPARE(textInputObject->hasFocus(), false);
QSignalSpy focusSpy(textInputObject, SIGNAL(focusChanged(bool)));
QSignalSpy activeFocusSpy(textInputObject, SIGNAL(focusChanged(bool)));
QSignalSpy activeFocusOnPressSpy(textInputObject, SIGNAL(activeFocusOnPressChanged(bool)));
textInputObject->setFocusOnPress(true);
QCOMPARE(textInputObject->focusOnPress(), true);
QCOMPARE(activeFocusOnPressSpy.count(), 0);
QQuickWindow window;
window.resize(100, 50);
textInputObject->setParentItem(window.contentItem());
window.showNormal();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QCOMPARE(textInputObject->hasFocus(), false);
QCOMPARE(textInputObject->hasActiveFocus(), false);
Qt::KeyboardModifiers noModifiers = Qt::NoModifier;
QTest::mousePress(&window, Qt::LeftButton, noModifiers);
QGuiApplication::processEvents();
QCOMPARE(textInputObject->hasFocus(), true);
QCOMPARE(textInputObject->hasActiveFocus(), true);
QCOMPARE(focusSpy.count(), 1);
QCOMPARE(activeFocusSpy.count(), 1);
QCOMPARE(textInputObject->selectedText(), QString());
QTest::mouseRelease(&window, Qt::LeftButton, noModifiers);
textInputObject->setFocusOnPress(false);
QCOMPARE(textInputObject->focusOnPress(), false);
QCOMPARE(activeFocusOnPressSpy.count(), 1);
textInputObject->setFocus(false);
QCOMPARE(textInputObject->hasFocus(), false);
QCOMPARE(textInputObject->hasActiveFocus(), false);
QCOMPARE(focusSpy.count(), 2);
QCOMPARE(activeFocusSpy.count(), 2);
// Wait for double click timeout to expire before clicking again.
QTest::qWait(400);
QTest::mousePress(&window, Qt::LeftButton, noModifiers);
QGuiApplication::processEvents();
QCOMPARE(textInputObject->hasFocus(), false);
QCOMPARE(textInputObject->hasActiveFocus(), false);
QCOMPARE(focusSpy.count(), 2);
QCOMPARE(activeFocusSpy.count(), 2);
QTest::mouseRelease(&window, Qt::LeftButton, noModifiers);
textInputObject->setFocusOnPress(true);
QCOMPARE(textInputObject->focusOnPress(), true);
QCOMPARE(activeFocusOnPressSpy.count(), 2);
// Test a selection made in the on(Active)FocusChanged handler isn't overwritten.
textInputObject->setProperty("selectOnFocus", true);
QTest::qWait(400);
QTest::mousePress(&window, Qt::LeftButton, noModifiers);
QGuiApplication::processEvents();
QCOMPARE(textInputObject->hasFocus(), true);
QCOMPARE(textInputObject->hasActiveFocus(), true);
QCOMPARE(focusSpy.count(), 3);
QCOMPARE(activeFocusSpy.count(), 3);
QCOMPARE(textInputObject->selectedText(), textInputObject->text());
QTest::mouseRelease(&window, Qt::LeftButton, noModifiers);
}
void tst_qquicktextinput::focusOnPressOnlyOneItem()
{
QQuickView window(testFileUrl("focusOnlyOneOnPress.qml"));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QQuickTextInput *first = window.rootObject()->findChild<QQuickTextInput*>("first");
QQuickTextInput *second = window.rootObject()->findChild<QQuickTextInput*>("second");
QQuickTextInput *third = window.rootObject()->findChild<QQuickTextInput*>("third");
// second is focused onComplete
QVERIFY(second->hasActiveFocus());
// and first will try focus when we press it
QVERIFY(first->focusOnPress());
// write some text to start editing
QTest::keyClick(&window, Qt::Key_A);
// click the first input. naturally, we are giving focus on press, but
// second's editingFinished also attempts to assign focus. lastly, focus
// should bounce back to second from first's editingFinished signal.
//
// this is a contrived example to be sure, but at the end of this, the
// important thing is that only one thing should have activeFocus.
Qt::KeyboardModifiers noModifiers = nullptr;
QTest::mousePress(&window, Qt::LeftButton, noModifiers, QPoint(10, 10));
// make sure the press is processed.
QGuiApplication::processEvents();
QVERIFY(second->hasActiveFocus()); // make sure it's still there
QVERIFY(!third->hasActiveFocus()); // make sure it didn't end up anywhere else
QVERIFY(!first->hasActiveFocus()); // make sure it didn't end up anywhere else
// reset state
QTest::mouseRelease(&window, Qt::LeftButton, noModifiers, QPoint(10, 10));
}
void tst_qquicktextinput::openInputPanel()
{
PlatformInputContext platformInputContext;
QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
inputMethodPrivate->testContext = &platformInputContext;
QQuickView view(testFileUrl("openInputPanel.qml"));
view.showNormal();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
QVERIFY(input);
// check default values
QVERIFY(input->focusOnPress());
QVERIFY(!input->hasActiveFocus());
QVERIFY(qApp->focusObject() != input);
QCOMPARE(qApp->inputMethod()->isVisible(), false);
// input panel should open on focus
Qt::KeyboardModifiers noModifiers = nullptr;
QTest::mousePress(&view, Qt::LeftButton, noModifiers);
QGuiApplication::processEvents();
QVERIFY(input->hasActiveFocus());
QCOMPARE(qApp->focusObject(), input);
QCOMPARE(qApp->inputMethod()->isVisible(), true);
QTest::mouseRelease(&view, Qt::LeftButton, noModifiers);
// input panel should be re-opened when pressing already focused TextInput
qApp->inputMethod()->hide();
QCOMPARE(qApp->inputMethod()->isVisible(), false);
QVERIFY(input->hasActiveFocus());
QTest::mousePress(&view, Qt::LeftButton, noModifiers);
QGuiApplication::processEvents();
QCOMPARE(qApp->inputMethod()->isVisible(), true);
QTest::mouseRelease(&view, Qt::LeftButton, noModifiers);
// input panel should stay visible if focus is lost to another text inputor
QSignalSpy inputPanelVisibilitySpy(qApp->inputMethod(), SIGNAL(visibleChanged()));
QQuickTextInput anotherInput;
anotherInput.componentComplete();
anotherInput.setParentItem(view.rootObject());
anotherInput.setFocus(true);
QCOMPARE(qApp->inputMethod()->isVisible(), true);
QCOMPARE(qApp->focusObject(), qobject_cast<QObject*>(&anotherInput));
QCOMPARE(inputPanelVisibilitySpy.count(), 0);
anotherInput.setFocus(false);
QVERIFY(qApp->focusObject() != &anotherInput);
QCOMPARE(view.activeFocusItem(), view.contentItem());
anotherInput.setFocus(true);
qApp->inputMethod()->hide();
// input panel should not be opened if TextInput is read only
input->setReadOnly(true);
input->setFocus(true);
QCOMPARE(qApp->inputMethod()->isVisible(), false);
QTest::mousePress(&view, Qt::LeftButton, noModifiers);
QTest::mouseRelease(&view, Qt::LeftButton, noModifiers);
QGuiApplication::processEvents();
QCOMPARE(qApp->inputMethod()->isVisible(), false);
// input panel should not be opened if focusOnPress is set to false
input->setFocusOnPress(false);
input->setFocus(false);
input->setFocus(true);
QCOMPARE(qApp->inputMethod()->isVisible(), false);
QTest::mousePress(&view, Qt::LeftButton, noModifiers);
QTest::mouseRelease(&view, Qt::LeftButton, noModifiers);
QCOMPARE(qApp->inputMethod()->isVisible(), false);
}
class MyTextInput : public QQuickTextInput
{
public:
MyTextInput(QQuickItem *parent = nullptr) : QQuickTextInput(parent)
{
nbPaint = 0;
}
virtual QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data)
{
nbPaint++;
return QQuickTextInput::updatePaintNode(node, data);
}
int nbPaint;
};
void tst_qquicktextinput::setHAlignClearCache()
{
QQuickView view;
view.resize(200, 200);
MyTextInput input;
input.setText("Hello world");
input.setParentItem(view.contentItem());
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QTRY_COMPARE(input.nbPaint, 1);
input.setHAlign(QQuickTextInput::AlignRight);
//Changing the alignment should trigger a repaint
QTRY_COMPARE(input.nbPaint, 2);
}
void tst_qquicktextinput::focusOutClearSelection()
{
QQuickView view;
QQuickTextInput input;
QQuickTextInput input2;
input.setText(QLatin1String("Hello world"));
input.setFocus(true);
input2.setParentItem(view.contentItem());
input.setParentItem(view.contentItem());
input.componentComplete();
input2.componentComplete();
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QVERIFY(input.hasActiveFocus());
input.select(2,5);
//The selection should work
QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
input2.setFocus(true);
QGuiApplication::processEvents();
//The input lost the focus selection should be cleared
QTRY_COMPARE(input.selectedText(), QLatin1String(""));
}
void tst_qquicktextinput::focusOutNotClearSelection()
{
QQuickView view;
QQuickTextInput input;
input.setText(QLatin1String("Hello world"));
input.setFocus(true);
input.setParentItem(view.contentItem());
input.componentComplete();
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QVERIFY(input.hasActiveFocus());
input.select(2,5);
QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
// The selection should not be cleared when the focus
// out event has one of the following reason:
// Qt::ActiveWindowFocusReason
// Qt::PopupFocusReason
input.setFocus(false, Qt::ActiveWindowFocusReason);
QGuiApplication::processEvents();
QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
QTRY_COMPARE(input.hasActiveFocus(), false);
input.setFocus(true);
QTRY_COMPARE(input.hasActiveFocus(), true);
input.setFocus(false, Qt::PopupFocusReason);
QGuiApplication::processEvents();
QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
// QTBUG-36332 and 36292: a popup window does not take focus
QTRY_COMPARE(input.hasActiveFocus(), true);
input.setFocus(true);
QTRY_COMPARE(input.hasActiveFocus(), true);
input.setFocus(false, Qt::OtherFocusReason);
QGuiApplication::processEvents();
QTRY_COMPARE(input.selectedText(), QLatin1String(""));
QTRY_COMPARE(input.hasActiveFocus(), false);
}
void tst_qquicktextinput::geometrySignals()
{
QQmlComponent component(&engine, testFileUrl("geometrySignals.qml"));
QObject *o = component.create();
QVERIFY(o);
QCOMPARE(o->property("bindingWidth").toInt(), 400);
QCOMPARE(o->property("bindingHeight").toInt(), 500);
delete o;
}
void tst_qquicktextinput::contentSize()
{
QString componentStr = "import QtQuick 2.0\nTextInput { width: 75; height: 16; font.pixelSize: 10 }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(textComponent.create());
QQuickTextInput *textObject = qobject_cast<QQuickTextInput *>(object.data());
QSignalSpy spy(textObject, SIGNAL(contentSizeChanged()));
textObject->setText("The quick red fox jumped over the lazy brown dog");
QVERIFY(textObject->contentWidth() > textObject->width());
QVERIFY(textObject->contentHeight() < textObject->height());
QCOMPARE(spy.count(), 1);
textObject->setWrapMode(QQuickTextInput::WordWrap);
QVERIFY(textObject->contentWidth() <= textObject->width());
QVERIFY(textObject->contentHeight() > textObject->height());
QCOMPARE(spy.count(), 2);
textObject->setText("The quickredfoxjumpedoverthe lazy brown dog");
QVERIFY(textObject->contentWidth() > textObject->width());
QVERIFY(textObject->contentHeight() > textObject->height());
QCOMPARE(spy.count(), 3);
textObject->setText("The quick red fox jumped over the lazy brown dog");
for (int w = 60; w < 120; ++w) {
textObject->setWidth(w);
QVERIFY(textObject->contentWidth() <= textObject->width());
QVERIFY(textObject->contentHeight() > textObject->height());
}
}
static void sendPreeditText(QQuickItem *item, const QString &text, int cursor)
{
QInputMethodEvent event(text, QList<QInputMethodEvent::Attribute>()
<< QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursor, text.length(), QVariant()));
QCoreApplication::sendEvent(item, &event);
}
void tst_qquicktextinput::preeditAutoScroll()
{
QString preeditText = "califragisiticexpialidocious!";
QQuickView view(testFileUrl("preeditAutoScroll.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
QVERIFY(input);
QVERIFY(input->hasActiveFocus());
input->setWidth(input->implicitWidth());
QSignalSpy cursorRectangleSpy(input, SIGNAL(cursorRectangleChanged()));
int cursorRectangleChanges = 0;
// test the text is scrolled so the preedit is visible.
sendPreeditText(input, preeditText.mid(0, 3), 1);
QVERIFY(evaluate<int>(input, QString("positionAt(0)")) != 0);
QVERIFY(input->cursorRectangle().left() < input->boundingRect().width());
QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
// test the text is scrolled back when the preedit is removed.
QInputMethodEvent imEvent;
QCoreApplication::sendEvent(input, &imEvent);
QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(0)), 0);
QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(input->width())), 5);
QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
QTextLayout layout(preeditText);
layout.setFont(input->font());
if (!qmlDisableDistanceField()) {
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
}
layout.beginLayout();
QTextLine line = layout.createLine();
layout.endLayout();
// test if the preedit is larger than the text input that the
// character preceding the cursor is still visible.
qreal x = input->positionToRectangle(0).x();
for (int i = 0; i < 3; ++i) {
sendPreeditText(input, preeditText, i + 1);
int width = ceil(line.cursorToX(i, QTextLine::Trailing)) - floor(line.cursorToX(i));
QVERIFY(input->cursorRectangle().right() >= width - 3);
QVERIFY(input->positionToRectangle(0).x() < x);
QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
x = input->positionToRectangle(0).x();
}
for (int i = 1; i >= 0; --i) {
sendPreeditText(input, preeditText, i + 1);
int width = ceil(line.cursorToX(i, QTextLine::Trailing)) - floor(line.cursorToX(i));
QVERIFY(input->cursorRectangle().right() >= width - 3);
QVERIFY(input->positionToRectangle(0).x() > x);
QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
x = input->positionToRectangle(0).x();
}
// Test incrementing the preedit cursor doesn't cause further
// scrolling when right most text is visible.
sendPreeditText(input, preeditText, preeditText.length() - 3);
QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
x = input->positionToRectangle(0).x();
for (int i = 2; i >= 0; --i) {
sendPreeditText(input, preeditText, preeditText.length() - i);
QCOMPARE(input->positionToRectangle(0).x(), x);
QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
}
for (int i = 1; i < 3; ++i) {
sendPreeditText(input, preeditText, preeditText.length() - i);
QCOMPARE(input->positionToRectangle(0).x(), x);
QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
}
// Test disabling auto scroll.
QCoreApplication::sendEvent(input, &imEvent);
input->setAutoScroll(false);
sendPreeditText(input, preeditText.mid(0, 3), 1);
QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(0)), 0);
QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(input->width())), 5);
}
void tst_qquicktextinput::preeditCursorRectangle()
{
QString preeditText = "super";
QQuickView view(testFileUrl("inputMethodEvent.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
QVERIFY(input);
QVERIFY(input->hasActiveFocus());
QQuickItem *cursor = input->findChild<QQuickItem *>("cursor");
QVERIFY(cursor);
QRectF currentRect;
QInputMethodQueryEvent query(Qt::ImCursorRectangle);
QCoreApplication::sendEvent(input, &query);
QRectF previousRect = query.value(Qt::ImCursorRectangle).toRectF();
// Verify that the micro focus rect is positioned the same for position 0 as
// it would be if there was no preedit text.
sendPreeditText(input, preeditText, 0);
QCoreApplication::sendEvent(input, &query);
currentRect = query.value(Qt::ImCursorRectangle).toRectF();
QCOMPARE(currentRect, previousRect);
QCOMPARE(input->cursorRectangle(), currentRect);
QCOMPARE(cursor->position(), currentRect.topLeft());
QSignalSpy inputSpy(input, SIGNAL(cursorRectangleChanged()));
QSignalSpy panelSpy(qGuiApp->inputMethod(), SIGNAL(cursorRectangleChanged()));
// Verify that the micro focus rect moves to the left as the cursor position
// is incremented.
for (int i = 1; i <= 5; ++i) {
sendPreeditText(input, preeditText, i);
QCoreApplication::sendEvent(input, &query);
currentRect = query.value(Qt::ImCursorRectangle).toRectF();
QVERIFY(previousRect.left() < currentRect.left());
QCOMPARE(input->cursorRectangle(), currentRect);
QCOMPARE(cursor->position(), currentRect.topLeft());
QVERIFY(inputSpy.count() > 0); inputSpy.clear();
QVERIFY(panelSpy.count() > 0); panelSpy.clear();
previousRect = currentRect;
}
// Verify that if the cursor rectangle is updated if the pre-edit text changes
// but the (non-zero) cursor position is the same.
inputSpy.clear();
panelSpy.clear();
sendPreeditText(input, "wwwww", 5);
QCoreApplication::sendEvent(input, &query);
currentRect = query.value(Qt::ImCursorRectangle).toRectF();
QCOMPARE(input->cursorRectangle(), currentRect);
QCOMPARE(cursor->position(), currentRect.topLeft());
QCOMPARE(inputSpy.count(), 1);
QCOMPARE(panelSpy.count(), 1);
// Verify that if there is no preedit cursor then the micro focus rect is the
// same as it would be if it were positioned at the end of the preedit text.
inputSpy.clear();
panelSpy.clear();
{ QInputMethodEvent imEvent(preeditText, QList<QInputMethodEvent::Attribute>());
QCoreApplication::sendEvent(input, &imEvent); }
QCoreApplication::sendEvent(input, &query);
currentRect = query.value(Qt::ImCursorRectangle).toRectF();
QCOMPARE(currentRect, previousRect);
QCOMPARE(input->cursorRectangle(), currentRect);
QCOMPARE(cursor->position(), currentRect.topLeft());
QCOMPARE(inputSpy.count(), 1);
QCOMPARE(panelSpy.count(), 1);
}
void tst_qquicktextinput::inputContextMouseHandler()
{
PlatformInputContext platformInputContext;
QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
inputMethodPrivate->testContext = &platformInputContext;
QString text = "supercalifragisiticexpialidocious!";
QQuickView view(testFileUrl("inputContext.qml"));
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
QVERIFY(input);
input->setFocus(true);
input->setText("");
view.showNormal();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QTextLayout layout(text);
layout.setFont(input->font());
if (!qmlDisableDistanceField()) {
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
}
layout.beginLayout();
QTextLine line = layout.createLine();
layout.endLayout();
const qreal x = line.cursorToX(2, QTextLine::Leading);
const qreal y = line.height() / 2;
QPoint position = QPointF(x, y).toPoint();
QInputMethodEvent inputEvent(text.mid(0, 5), QList<QInputMethodEvent::Attribute>());
QGuiApplication::sendEvent(input, &inputEvent);
QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, position);
QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, position);
QGuiApplication::processEvents();
QCOMPARE(platformInputContext.m_action, QInputMethod::Click);
QCOMPARE(platformInputContext.m_invokeActionCallCount, 1);
QCOMPARE(platformInputContext.m_cursorPosition, 2);
}
void tst_qquicktextinput::inputMethodComposing()
{
QString text = "supercalifragisiticexpialidocious!";
QQuickView view(testFileUrl("inputContext.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
QVERIFY(input);
QVERIFY(input->hasActiveFocus());
QSignalSpy spy(input, SIGNAL(inputMethodComposingChanged()));
QCOMPARE(input->isInputMethodComposing(), false);
{
QInputMethodEvent event(text.mid(3), QList<QInputMethodEvent::Attribute>());
QGuiApplication::sendEvent(input, &event);
}
QCOMPARE(input->isInputMethodComposing(), true);
QCOMPARE(spy.count(), 1);
{
QInputMethodEvent event(text.mid(12), QList<QInputMethodEvent::Attribute>());
QGuiApplication::sendEvent(input, &event);
}
QCOMPARE(spy.count(), 1);
{
QInputMethodEvent event;
QGuiApplication::sendEvent(input, &event);
}
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()
{
PlatformInputContext platformInputContext;
QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
inputMethodPrivate->testContext = &platformInputContext;
QQuickView view(testFileUrl("inputContext.qml"));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
QVERIFY(input);
QVERIFY(input->hasActiveFocus());
// text change even without cursor position change needs to trigger update
input->setText("test");
platformInputContext.clear();
input->setText("xxxx");
QVERIFY(platformInputContext.m_updateCallCount > 0);
// input method event replacing text
platformInputContext.clear();
{
QInputMethodEvent inputMethodEvent;
inputMethodEvent.setCommitString("y", -1, 1);
QGuiApplication::sendEvent(input, &inputMethodEvent);
}
QVERIFY(platformInputContext.m_updateCallCount > 0);
// input method changing selection
platformInputContext.clear();
{
QList<QInputMethodEvent::Attribute> attributes;
attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 0, 2, QVariant());
QInputMethodEvent inputMethodEvent("", attributes);
QGuiApplication::sendEvent(input, &inputMethodEvent);
}
QVERIFY(input->selectionStart() != input->selectionEnd());
QVERIFY(platformInputContext.m_updateCallCount > 0);
// programmatical selections trigger update
platformInputContext.clear();
input->selectAll();
QVERIFY(platformInputContext.m_updateCallCount > 0);
// font changes
platformInputContext.clear();
QFont font = input->font();
font.setBold(!font.bold());
input->setFont(font);
QVERIFY(platformInputContext.m_updateCallCount > 0);
// normal input
platformInputContext.clear();
{
QInputMethodEvent inputMethodEvent;
inputMethodEvent.setCommitString("y");
QGuiApplication::sendEvent(input, &inputMethodEvent);
}
QVERIFY(platformInputContext.m_updateCallCount > 0);
// changing cursor position
platformInputContext.clear();
input->setCursorPosition(0);
QVERIFY(platformInputContext.m_updateCallCount > 0);
// read only disabled input method
platformInputContext.clear();
input->setReadOnly(true);
QVERIFY(platformInputContext.m_updateCallCount > 0);
input->setReadOnly(false);
// no updates while no focus
input->setFocus(false);
platformInputContext.clear();
input->setText("Foo");
QCOMPARE(platformInputContext.m_updateCallCount, 0);
input->setCursorPosition(1);
QCOMPARE(platformInputContext.m_updateCallCount, 0);
input->selectAll();
QCOMPARE(platformInputContext.m_updateCallCount, 0);
input->setReadOnly(true);
QCOMPARE(platformInputContext.m_updateCallCount, 0);
}
void tst_qquicktextinput::cursorRectangleSize()
{
QQuickView *window = new QQuickView(testFileUrl("positionAt.qml"));
QVERIFY(window->rootObject() != nullptr);
QQuickTextInput *textInput = qobject_cast<QQuickTextInput *>(window->rootObject());
// make sure cursor rectangle is not at (0,0)
textInput->setX(10);
textInput->setY(10);
textInput->setCursorPosition(3);
QVERIFY(textInput != nullptr);
textInput->setFocus(true);
window->show();
window->requestActivate();
QVERIFY(QTest::qWaitForWindowActive(window));
QVERIFY(textInput->hasActiveFocus());
QInputMethodQueryEvent event(Qt::ImCursorRectangle);
qApp->sendEvent(textInput, &event);
QRectF cursorRectFromQuery = event.value(Qt::ImCursorRectangle).toRectF();
QRectF cursorRectFromItem = textInput->cursorRectangle();
QRectF cursorRectFromPositionToRectangle = textInput->positionToRectangle(textInput->cursorPosition());
// item and input query cursor rectangles match
QCOMPARE(cursorRectFromItem, cursorRectFromQuery);
// item cursor rectangle and positionToRectangle calculations match
QCOMPARE(cursorRectFromItem, cursorRectFromPositionToRectangle);
// item-window transform and input item transform match
QCOMPARE(QQuickItemPrivate::get(textInput)->itemToWindowTransform(), qApp->inputMethod()->inputItemTransform());
// input panel cursorRectangle property and tranformed item cursor rectangle match
QRectF sceneCursorRect = QQuickItemPrivate::get(textInput)->itemToWindowTransform().mapRect(cursorRectFromItem);
QCOMPARE(sceneCursorRect, qApp->inputMethod()->cursorRectangle());
delete window;
}
void tst_qquicktextinput::tripleClickSelectsAll()
{
QString qmlfile = testFile("positionAt.qml");
QQuickView view(QUrl::fromLocalFile(qmlfile));
view.show();
view.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&view));
QQuickTextInput* input = qobject_cast<QQuickTextInput*>(view.rootObject());
QVERIFY(input);
QLatin1String hello("Hello world!");
input->setSelectByMouse(true);
input->setText(hello);
// Clicking on the same point inside TextInput three times in a row
// should trigger a triple click, thus selecting all the text.
QPoint pointInside = input->position().toPoint() + QPoint(2,2);
QTest::mouseDClick(&view, Qt::LeftButton, Qt::NoModifier, pointInside);
QTest::mouseClick(&view, Qt::LeftButton, Qt::NoModifier, pointInside);
QGuiApplication::processEvents();
QCOMPARE(input->selectedText(), hello);
// Now it simulates user moving the mouse between the second and the third click.
// In this situation, we don't expect a triple click.
QPoint pointInsideButFar = QPoint(input->width(),input->height()) - QPoint(2,2);
QTest::mouseDClick(&view, Qt::LeftButton, Qt::NoModifier, pointInside);
QTest::mouseClick(&view, Qt::LeftButton, Qt::NoModifier, pointInsideButFar);
QGuiApplication::processEvents();
QVERIFY(input->selectedText().isEmpty());
// And now we press the third click too late, so no triple click event is triggered.
QTest::mouseDClick(&view, Qt::LeftButton, Qt::NoModifier, pointInside);
QGuiApplication::processEvents();
QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 1);
QTest::mouseClick(&view, Qt::LeftButton, Qt::NoModifier, pointInside);
QGuiApplication::processEvents();
QVERIFY(input->selectedText().isEmpty());
}
void tst_qquicktextinput::QTBUG_19956_data()
{
QTest::addColumn<QString>("url");
QTest::newRow("intvalidator") << "qtbug-19956int.qml";
QTest::newRow("doublevalidator") << "qtbug-19956double.qml";
}
void tst_qquicktextinput::getText_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("inputMask");
QTest::addColumn<int>("start");
QTest::addColumn<int>("end");
QTest::addColumn<QString>("expectedText");
QTest::newRow("all plain text")
<< standard.at(0)
<< QString()
<< 0 << standard.at(0).length()
<< standard.at(0);
QTest::newRow("plain text sub string")
<< standard.at(0)
<< QString()
<< 0 << 12
<< standard.at(0).mid(0, 12);
QTest::newRow("plain text sub string reversed")
<< standard.at(0)
<< QString()
<< 12 << 0
<< standard.at(0).mid(0, 12);
QTest::newRow("plain text cropped beginning")
<< standard.at(0)
<< QString()
<< -3 << 4
<< standard.at(0).mid(0, 4);
QTest::newRow("plain text cropped end")
<< standard.at(0)
<< QString()
<< 23 << standard.at(0).length() + 8
<< standard.at(0).mid(23);
QTest::newRow("plain text cropped beginning and end")
<< standard.at(0)
<< QString()
<< -9 << standard.at(0).length() + 4
<< standard.at(0);
}
void tst_qquicktextinput::getText()
{
QFETCH(QString, text);
QFETCH(QString, inputMask);
QFETCH(int, start);
QFETCH(int, end);
QFETCH(QString, expectedText);
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
QCOMPARE(textInput->getText(start, end), expectedText);
}
void tst_qquicktextinput::insert_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("inputMask");
QTest::addColumn<int>("selectionStart");
QTest::addColumn<int>("selectionEnd");
QTest::addColumn<int>("insertPosition");
QTest::addColumn<QString>("insertText");
QTest::addColumn<QString>("expectedText");
QTest::addColumn<int>("expectedSelectionStart");
QTest::addColumn<int>("expectedSelectionEnd");
QTest::addColumn<int>("expectedCursorPosition");
QTest::addColumn<bool>("selectionChanged");
QTest::addColumn<bool>("cursorPositionChanged");
QTest::newRow("at cursor position (beginning)")
<< standard.at(0)
<< QString()
<< 0 << 0 << 0
<< QString("Hello")
<< QString("Hello") + standard.at(0)
<< 5 << 5 << 5
<< false << true;
QTest::newRow("at cursor position (end)")
<< standard.at(0)
<< QString()
<< standard.at(0).length() << standard.at(0).length() << standard.at(0).length()
<< QString("Hello")
<< standard.at(0) + QString("Hello")
<< standard.at(0).length() + 5 << standard.at(0).length() + 5 << standard.at(0).length() + 5
<< false << true;
QTest::newRow("at cursor position (middle)")
<< standard.at(0)
<< QString()
<< 18 << 18 << 18
<< QString("Hello")
<< standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
<< 23 << 23 << 23
<< false << true;
QTest::newRow("after cursor position (beginning)")
<< standard.at(0)
<< QString()
<< 0 << 0 << 18
<< QString("Hello")
<< standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
<< 0 << 0 << 0
<< false << false;
QTest::newRow("before cursor position (end)")
<< standard.at(0)
<< QString()
<< standard.at(0).length() << standard.at(0).length() << 18
<< QString("Hello")
<< standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
<< standard.at(0).length() + 5 << standard.at(0).length() + 5 << standard.at(0).length() + 5
<< false << true;
QTest::newRow("before cursor position (middle)")
<< standard.at(0)
<< QString()
<< 18 << 18 << 0
<< QString("Hello")
<< QString("Hello") + standard.at(0)
<< 23 << 23 << 23
<< false << true;
QTest::newRow("after cursor position (middle)")
<< standard.at(0)
<< QString()
<< 18 << 18 << standard.at(0).length()
<< QString("Hello")
<< standard.at(0) + QString("Hello")
<< 18 << 18 << 18
<< false << false;
QTest::newRow("before selection")
<< standard.at(0)
<< QString()
<< 14 << 19 << 0
<< QString("Hello")
<< QString("Hello") + standard.at(0)
<< 19 << 24 << 24
<< false << true;
QTest::newRow("before reversed selection")
<< standard.at(0)
<< QString()
<< 19 << 14 << 0
<< QString("Hello")
<< QString("Hello") + standard.at(0)
<< 19 << 24 << 19
<< false << true;
QTest::newRow("after selection")
<< standard.at(0)
<< QString()
<< 14 << 19 << standard.at(0).length()
<< QString("Hello")
<< standard.at(0) + QString("Hello")
<< 14 << 19 << 19
<< false << false;
QTest::newRow("after reversed selection")
<< standard.at(0)
<< QString()
<< 19 << 14 << standard.at(0).length()
<< QString("Hello")
<< standard.at(0) + QString("Hello")
<< 14 << 19 << 14
<< false << false;
QTest::newRow("into selection")
<< standard.at(0)
<< QString()
<< 14 << 19 << 18
<< QString("Hello")
<< standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
<< 14 << 24 << 24
<< true << true;
QTest::newRow("into reversed selection")
<< standard.at(0)
<< QString()
<< 19 << 14 << 18
<< QString("Hello")
<< standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
<< 14 << 24 << 14
<< true << false;
QTest::newRow("rich text into plain text")
<< standard.at(0)
<< QString()
<< 0 << 0 << 0
<< QString("<b>Hello</b>")
<< QString("<b>Hello</b>") + standard.at(0)
<< 12 << 12 << 12
<< false << true;
QTest::newRow("before start")
<< standard.at(0)
<< QString()
<< 0 << 0 << -3
<< QString("Hello")
<< standard.at(0)
<< 0 << 0 << 0
<< false << false;
QTest::newRow("past end")
<< standard.at(0)
<< QString()
<< 0 << 0 << standard.at(0).length() + 3
<< QString("Hello")
<< standard.at(0)
<< 0 << 0 << 0
<< false << false;
const QString inputMask = "009.009.009.009";
const QString ip = "192.168.2.14";
QTest::newRow("mask: at cursor position (beginning)")
<< ip
<< inputMask
<< 0 << 0 << 0
<< QString("125")
<< QString("125.168.2.14")
<< 0 << 0 << 0
<< false << false;
QTest::newRow("mask: at cursor position (end)")
<< ip
<< inputMask
<< inputMask.length() << inputMask.length() << inputMask.length()
<< QString("8")
<< ip
<< inputMask.length() << inputMask.length() << inputMask.length()
<< false << false;
QTest::newRow("mask: at cursor position (middle)")
<< ip
<< inputMask
<< 6 << 6 << 6
<< QString("75.2")
<< QString("192.167.5.24")
<< 6 << 6 << 6
<< false << false;
QTest::newRow("mask: after cursor position (beginning)")
<< ip
<< inputMask
<< 0 << 0 << 6
<< QString("75.2")
<< QString("192.167.5.24")
<< 0 << 0 << 0
<< false << false;
QTest::newRow("mask: before cursor position (end)")
<< ip
<< inputMask
<< inputMask.length() << inputMask.length() << 6
<< QString("75.2")
<< QString("192.167.5.24")
<< inputMask.length() << inputMask.length() << inputMask.length()
<< false << false;
QTest::newRow("mask: before cursor position (middle)")
<< ip
<< inputMask
<< 6 << 6 << 0
<< QString("125")
<< QString("125.168.2.14")
<< 6 << 6 << 6
<< false << false;
QTest::newRow("mask: after cursor position (middle)")
<< ip
<< inputMask
<< 6 << 6 << 13
<< QString("8")
<< "192.168.2.18"
<< 6 << 6 << 6
<< false << false;
QTest::newRow("mask: before selection")
<< ip
<< inputMask
<< 6 << 8 << 0
<< QString("125")
<< QString("125.168.2.14")
<< 6 << 8 << 8
<< false << false;
QTest::newRow("mask: before reversed selection")
<< ip
<< inputMask
<< 8 << 6 << 0
<< QString("125")
<< QString("125.168.2.14")
<< 6 << 8 << 6
<< false << false;
QTest::newRow("mask: after selection")
<< ip
<< inputMask
<< 6 << 8 << 13
<< QString("8")
<< "192.168.2.18"
<< 6 << 8 << 8
<< false << false;
QTest::newRow("mask: after reversed selection")
<< ip
<< inputMask
<< 8 << 6 << 13
<< QString("8")
<< "192.168.2.18"
<< 6 << 8 << 6
<< false << false;
QTest::newRow("mask: into selection")
<< ip
<< inputMask
<< 5 << 8 << 6
<< QString("75.2")
<< QString("192.167.5.24")
<< 5 << 8 << 8
<< true << false;
QTest::newRow("mask: into reversed selection")
<< ip
<< inputMask
<< 8 << 5 << 6
<< QString("75.2")
<< QString("192.167.5.24")
<< 5 << 8 << 5
<< true << false;
QTest::newRow("mask: before start")
<< ip
<< inputMask
<< 0 << 0 << -3
<< QString("4")
<< ip
<< 0 << 0 << 0
<< false << false;
QTest::newRow("mask: past end")
<< ip
<< inputMask
<< 0 << 0 << ip.length() + 3
<< QString("4")
<< ip
<< 0 << 0 << 0
<< false << false;
QTest::newRow("mask: invalid characters")
<< ip
<< inputMask
<< 0 << 0 << 0
<< QString("abc")
<< QString("192.168.2.14")
<< 0 << 0 << 0
<< false << false;
QTest::newRow("mask: mixed validity")
<< ip
<< inputMask
<< 0 << 0 << 0
<< QString("a1b2c5")
<< QString("125.168.2.14")
<< 0 << 0 << 0
<< false << false;
}
void tst_qquicktextinput::insert()
{
QFETCH(QString, text);
QFETCH(QString, inputMask);
QFETCH(int, selectionStart);
QFETCH(int, selectionEnd);
QFETCH(int, insertPosition);
QFETCH(QString, insertText);
QFETCH(QString, expectedText);
QFETCH(int, expectedSelectionStart);
QFETCH(int, expectedSelectionEnd);
QFETCH(int, expectedCursorPosition);
QFETCH(bool, selectionChanged);
QFETCH(bool, cursorPositionChanged);
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
textInput->select(selectionStart, selectionEnd);
QSignalSpy selectionSpy(textInput, SIGNAL(selectedTextChanged()));
QSignalSpy selectionStartSpy(textInput, SIGNAL(selectionStartChanged()));
QSignalSpy selectionEndSpy(textInput, SIGNAL(selectionEndChanged()));
QSignalSpy textSpy(textInput, SIGNAL(textChanged()));
QSignalSpy cursorPositionSpy(textInput, SIGNAL(cursorPositionChanged()));
textInput->insert(insertPosition, insertText);
QCOMPARE(textInput->text(), expectedText);
QCOMPARE(textInput->length(), inputMask.isEmpty() ? expectedText.length() : inputMask.length());
QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);
if (selectionStart > selectionEnd)
qSwap(selectionStart, selectionEnd);
QCOMPARE(selectionSpy.count() > 0, selectionChanged);
QCOMPARE(selectionStartSpy.count() > 0, selectionStart != expectedSelectionStart);
QCOMPARE(selectionEndSpy.count() > 0, selectionEnd != expectedSelectionEnd);
QCOMPARE(textSpy.count() > 0, text != expectedText);
QCOMPARE(cursorPositionSpy.count() > 0, cursorPositionChanged);
}
void tst_qquicktextinput::remove_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("inputMask");
QTest::addColumn<int>("selectionStart");
QTest::addColumn<int>("selectionEnd");
QTest::addColumn<int>("removeStart");
QTest::addColumn<int>("removeEnd");
QTest::addColumn<QString>("expectedText");
QTest::addColumn<int>("expectedSelectionStart");
QTest::addColumn<int>("expectedSelectionEnd");
QTest::addColumn<int>("expectedCursorPosition");
QTest::addColumn<bool>("selectionChanged");
QTest::addColumn<bool>("cursorPositionChanged");
QTest::newRow("from cursor position (beginning)")
<< standard.at(0)
<< QString()
<< 0 << 0
<< 0 << 5
<< standard.at(0).mid(5)
<< 0 << 0 << 0
<< false << false;
QTest::newRow("to cursor position (beginning)")
<< standard.at(0)
<< QString()
<< 0 << 0
<< 5 << 0
<< standard.at(0).mid(5)
<< 0 << 0 << 0
<< false << false;
QTest::newRow("to cursor position (end)")
<< standard.at(0)
<< QString()
<< standard.at(0).length() << standard.at(0).length()
<< standard.at(0).length() << standard.at(0).length() - 5
<< standard.at(0).mid(0, standard.at(0).length() - 5)
<< standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
<< false << true;
QTest::newRow("to cursor position (end)")
<< standard.at(0)
<< QString()
<< standard.at(0).length() << standard.at(0).length()
<< standard.at(0).length() - 5 << standard.at(0).length()
<< standard.at(0).mid(0, standard.at(0).length() - 5)
<< standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
<< false << true;
QTest::newRow("from cursor position (middle)")
<< standard.at(0)
<< QString()
<< 18 << 18
<< 18 << 23
<< standard.at(0).mid(0, 18) + standard.at(0).mid(23)
<< 18 << 18 << 18
<< false << false;
QTest::newRow("to cursor position (middle)")
<< standard.at(0)
<< QString()
<< 23 << 23
<< 18 << 23
<< standard.at(0).mid(0, 18) + standard.at(0).mid(23)
<< 18 << 18 << 18
<< false << true;
QTest::newRow("after cursor position (beginning)")
<< standard.at(0)
<< QString()
<< 0 << 0
<< 18 << 23
<< standard.at(0).mid(0, 18) + standard.at(0).mid(23)
<< 0 << 0 << 0
<< false << false;
QTest::newRow("before cursor position (end)")
<< standard.at(0)
<< QString()
<< standard.at(0).length() << standard.at(0).length()
<< 18 << 23
<< standard.at(0).mid(0, 18) + standard.at(0).mid(23)
<< standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
<< false << true;
QTest::newRow("before cursor position (middle)")
<< standard.at(0)
<< QString()
<< 23 << 23
<< 0 << 5
<< standard.at(0).mid(5)
<< 18 << 18 << 18
<< false << true;
QTest::newRow("after cursor position (middle)")
<< standard.at(0)
<< QString()
<< 18 << 18
<< 18 << 23
<< standard.at(0).mid(0, 18) + standard.at(0).mid(23)
<< 18 << 18 << 18
<< false << false;
QTest::newRow("before selection")
<< standard.at(0)
<< QString()
<< 14 << 19
<< 0 << 5
<< standard.at(0).mid(5)
<< 9 << 14 << 14
<< false << true;
QTest::newRow("before reversed selection")
<< standard.at(0)
<< QString()
<< 19 << 14
<< 0 << 5
<< standard.at(0).mid(5)
<< 9 << 14 << 9
<< false << true;
QTest::newRow("after selection")
<< standard.at(0)
<< QString()
<< 14 << 19
<< standard.at(0).length() - 5 << standard.at(0).length()
<< standard.at(0).mid(0, standard.at(0).length() - 5)
<< 14 << 19 << 19
<< false << false;
QTest::newRow("after reversed selection")
<< standard.at(0)
<< QString()
<< 19 << 14
<< standard.at(0).length() - 5 << standard.at(0).length()
<< standard.at(0).mid(0, standard.at(0).length() - 5)
<< 14 << 19 << 14
<< false << false;
QTest::newRow("from selection")
<< standard.at(0)
<< QString()
<< 14 << 24
<< 18 << 23
<< standard.at(0).mid(0, 18) + standard.at(0).mid(23)
<< 14 << 19 << 19
<< true << true;
QTest::newRow("from reversed selection")
<< standard.at(0)
<< QString()
<< 24 << 14
<< 18 << 23
<< standard.at(0).mid(0, 18) + standard.at(0).mid(23)
<< 14 << 19 << 14
<< true << false;
QTest::newRow("cropped beginning")
<< standard.at(0)
<< QString()
<< 0 << 0
<< -3 << 4
<< standard.at(0).mid(4)
<< 0 << 0 << 0
<< false << false;
QTest::newRow("cropped end")
<< standard.at(0)
<< QString()
<< 0 << 0
<< 23 << standard.at(0).length() + 8
<< standard.at(0).mid(0, 23)
<< 0 << 0 << 0
<< false << false;
QTest::newRow("cropped beginning and end")
<< standard.at(0)
<< QString()
<< 0 << 0
<< -9 << standard.at(0).length() + 4
<< QString()
<< 0 << 0 << 0
<< false << false;
const QString inputMask = "009.009.009.009";
const QString ip = "192.168.2.14";
QTest::newRow("mask: from cursor position")
<< ip
<< inputMask
<< 6 << 6
<< 6 << 9
<< QString("192.16..14")
<< 6 << 6 << 6
<< false << false;
QTest::newRow("mask: to cursor position")
<< ip
<< inputMask
<< 6 << 6
<< 2 << 6
<< QString("19.8.2.14")
<< 6 << 6 << 6
<< false << false;
QTest::newRow("mask: before cursor position")
<< ip
<< inputMask
<< 6 << 6
<< 0 << 2
<< QString("2.168.2.14")
<< 6 << 6 << 6
<< false << false;
QTest::newRow("mask: after cursor position")
<< ip
<< inputMask
<< 6 << 6
<< 12 << 16
<< QString("192.168.2.")
<< 6 << 6 << 6
<< false << false;
QTest::newRow("mask: before selection")
<< ip
<< inputMask
<< 6 << 8
<< 0 << 2
<< QString("2.168.2.14")
<< 6 << 8 << 8
<< false << false;
QTest::newRow("mask: before reversed selection")
<< ip
<< inputMask
<< 8 << 6
<< 0 << 2
<< QString("2.168.2.14")
<< 6 << 8 << 6
<< false << false;
QTest::newRow("mask: after selection")
<< ip
<< inputMask
<< 6 << 8
<< 12 << 16
<< QString("192.168.2.")
<< 6 << 8 << 8
<< false << false;
QTest::newRow("mask: after reversed selection")
<< ip
<< inputMask
<< 8 << 6
<< 12 << 16
<< QString("192.168.2.")
<< 6 << 8 << 6
<< false << false;
QTest::newRow("mask: from selection")
<< ip
<< inputMask
<< 6 << 13
<< 8 << 10
<< QString("192.168..14")
<< 6 << 13 << 13
<< true << false;
QTest::newRow("mask: from reversed selection")
<< ip
<< inputMask
<< 13 << 6
<< 8 << 10
<< QString("192.168..14")
<< 6 << 13 << 6
<< true << false;
QTest::newRow("mask: cropped beginning")
<< ip
<< inputMask
<< 0 << 0
<< -3 << 4
<< QString(".168.2.14")
<< 0 << 0 << 0
<< false << false;
QTest::newRow("mask: cropped end")
<< ip
<< inputMask
<< 0 << 0
<< 13 << 28
<< QString("192.168.2.1")
<< 0 << 0 << 0
<< false << false;
QTest::newRow("mask: cropped beginning and end")
<< ip
<< inputMask
<< 0 << 0
<< -9 << 28
<< QString("...")
<< 0 << 0 << 0
<< false << false;
}
void tst_qquicktextinput::remove()
{
QFETCH(QString, text);
QFETCH(QString, inputMask);
QFETCH(int, selectionStart);
QFETCH(int, selectionEnd);
QFETCH(int, removeStart);
QFETCH(int, removeEnd);
QFETCH(QString, expectedText);
QFETCH(int, expectedSelectionStart);
QFETCH(int, expectedSelectionEnd);
QFETCH(int, expectedCursorPosition);
QFETCH(bool, selectionChanged);
QFETCH(bool, cursorPositionChanged);
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
textInput->select(selectionStart, selectionEnd);
QSignalSpy selectionSpy(textInput, SIGNAL(selectedTextChanged()));
QSignalSpy selectionStartSpy(textInput, SIGNAL(selectionStartChanged()));
QSignalSpy selectionEndSpy(textInput, SIGNAL(selectionEndChanged()));
QSignalSpy textSpy(textInput, SIGNAL(textChanged()));
QSignalSpy cursorPositionSpy(textInput, SIGNAL(cursorPositionChanged()));
textInput->remove(removeStart, removeEnd);
QCOMPARE(textInput->text(), expectedText);
QCOMPARE(textInput->length(), inputMask.isEmpty() ? expectedText.length() : inputMask.length());
if (selectionStart > selectionEnd) //
qSwap(selectionStart, selectionEnd);
QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);
QCOMPARE(selectionSpy.count() > 0, selectionChanged);
QCOMPARE(selectionStartSpy.count() > 0, selectionStart != expectedSelectionStart);
QCOMPARE(selectionEndSpy.count() > 0, selectionEnd != expectedSelectionEnd);
QCOMPARE(textSpy.count() > 0, text != expectedText);
if (cursorPositionChanged) //
QVERIFY(cursorPositionSpy.count() > 0);
}
#if QT_CONFIG(shortcut)
void tst_qquicktextinput::keySequence_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QKeySequence>("sequence");
QTest::addColumn<int>("selectionStart");
QTest::addColumn<int>("selectionEnd");
QTest::addColumn<int>("cursorPosition");
QTest::addColumn<QString>("expectedText");
QTest::addColumn<QString>("selectedText");
QTest::addColumn<QQuickTextInput::EchoMode>("echoMode");
QTest::addColumn<Qt::Key>("layoutDirection");
// standard[0] == "the [4]quick [10]brown [16]fox [20]jumped [27]over [32]the [36]lazy [41]dog"
QTest::newRow("select all")
<< standard.at(0) << QKeySequence(QKeySequence::SelectAll) << 0 << 0
<< 44 << standard.at(0) << standard.at(0)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select start of line")
<< standard.at(0) << QKeySequence(QKeySequence::SelectStartOfLine) << 5 << 5
<< 0 << standard.at(0) << standard.at(0).mid(0, 5)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select start of block")
<< standard.at(0) << QKeySequence(QKeySequence::SelectStartOfBlock) << 5 << 5
<< 0 << standard.at(0) << standard.at(0).mid(0, 5)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select end of line")
<< standard.at(0) << QKeySequence(QKeySequence::SelectEndOfLine) << 5 << 5
<< 44 << standard.at(0) << standard.at(0).mid(5)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select end of document") // ### Not handled.
<< standard.at(0) << QKeySequence(QKeySequence::SelectEndOfDocument) << 3 << 3
<< 3 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select end of block")
<< standard.at(0) << QKeySequence(QKeySequence::SelectEndOfBlock) << 18 << 18
<< 44 << standard.at(0) << standard.at(0).mid(18)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("delete end of line")
<< standard.at(0) << QKeySequence(QKeySequence::DeleteEndOfLine) << 24 << 24
<< 24 << standard.at(0).mid(0, 24) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("move to start of line")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToStartOfLine) << 31 << 31
<< 0 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("move to start of block")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToStartOfBlock) << 25 << 25
<< 0 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("move to next char")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToNextChar) << 12 << 12
<< 13 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("move to previous char (ltr)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 3
<< 2 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("move to previous char (rtl)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 3
<< 4 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_R;
QTest::newRow("move to previous char with selection")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 7
<< 3 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select next char (ltr)")
<< standard.at(0) << QKeySequence(QKeySequence::SelectNextChar) << 23 << 23
<< 24 << standard.at(0) << standard.at(0).mid(23, 1)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select next char (rtl)")
<< standard.at(0) << QKeySequence(QKeySequence::SelectNextChar) << 23 << 23
<< 22 << standard.at(0) << standard.at(0).mid(22, 1)
<< QQuickTextInput::Normal << Qt::Key_Direction_R;
QTest::newRow("select previous char (ltr)")
<< standard.at(0) << QKeySequence(QKeySequence::SelectPreviousChar) << 19 << 19
<< 18 << standard.at(0) << standard.at(0).mid(18, 1)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select previous char (rtl)")
<< standard.at(0) << QKeySequence(QKeySequence::SelectPreviousChar) << 19 << 19
<< 20 << standard.at(0) << standard.at(0).mid(19, 1)
<< QQuickTextInput::Normal << Qt::Key_Direction_R;
QTest::newRow("move to next word (ltr)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
<< 10 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("move to next word (rtl)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
<< 4 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_R;
QTest::newRow("move to next word (password,ltr)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
<< 44 << standard.at(0) << QString()
<< QQuickTextInput::Password << Qt::Key_Direction_L;
QTest::newRow("move to next word (password,rtl)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
<< 0 << standard.at(0) << QString()
<< QQuickTextInput::Password << Qt::Key_Direction_R;
QTest::newRow("move to previous word (ltr)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
<< 4 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("move to previous word (rlt)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
<< 10 << standard.at(0) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_R;
QTest::newRow("move to previous word (password,ltr)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
<< 0 << standard.at(0) << QString()
<< QQuickTextInput::Password << Qt::Key_Direction_L;
QTest::newRow("move to previous word (password,rtl)")
<< standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
<< 44 << standard.at(0) << QString()
<< QQuickTextInput::Password << Qt::Key_Direction_R;
QTest::newRow("select next word")
<< standard.at(0) << QKeySequence(QKeySequence::SelectNextWord) << 11 << 11
<< 16 << standard.at(0) << standard.at(0).mid(11, 5)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("select previous word")
<< standard.at(0) << QKeySequence(QKeySequence::SelectPreviousWord) << 11 << 11
<< 10 << standard.at(0) << standard.at(0).mid(10, 1)
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("delete (selection)")
<< standard.at(0) << QKeySequence(QKeySequence::Delete) << 12 << 15
<< 12 << (standard.at(0).mid(0, 12) + standard.at(0).mid(15)) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("delete (no selection)")
<< standard.at(0) << QKeySequence(QKeySequence::Delete) << 15 << 15
<< 15 << (standard.at(0).mid(0, 15) + standard.at(0).mid(16)) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("delete end of word")
<< standard.at(0) << QKeySequence(QKeySequence::DeleteEndOfWord) << 24 << 24
<< 24 << (standard.at(0).mid(0, 24) + standard.at(0).mid(27)) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("delete start of word")
<< standard.at(0) << QKeySequence(QKeySequence::DeleteStartOfWord) << 7 << 7
<< 4 << (standard.at(0).mid(0, 4) + standard.at(0).mid(7)) << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
QTest::newRow("delete complete line")
<< standard.at(0) << QKeySequence(QKeySequence::DeleteCompleteLine) << 0 << 0
<< 0 << QString() << QString()
<< QQuickTextInput::Normal << Qt::Key_Direction_L;
}
void tst_qquicktextinput::keySequence()
{
QFETCH(QString, text);
QFETCH(QKeySequence, sequence);
QFETCH(int, selectionStart);
QFETCH(int, selectionEnd);
QFETCH(int, cursorPosition);
QFETCH(QString, expectedText);
QFETCH(QString, selectedText);
QFETCH(QQuickTextInput::EchoMode, echoMode);
QFETCH(Qt::Key, layoutDirection);
if (sequence.isEmpty()) {
QSKIP("Key sequence is undefined");
}
QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; text: \"" + text + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
textInput->setEchoMode(echoMode);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
simulateKey(&window, layoutDirection);
textInput->select(selectionStart, selectionEnd);
simulateKeys(&window, sequence);
QCOMPARE(textInput->cursorPosition(), cursorPosition);
QCOMPARE(textInput->text(), expectedText);
QCOMPARE(textInput->selectedText(), selectedText);
}
#endif // QT_CONFIG(shortcut)
#define NORMAL 0
#define REPLACE_UNTIL_END 1
void tst_qquicktextinput::undo_data()
{
QTest::addColumn<QStringList>("insertString");
QTest::addColumn<IntList>("insertIndex");
QTest::addColumn<IntList>("insertMode");
QTest::addColumn<QStringList>("expectedString");
QTest::addColumn<bool>("use_keys");
for (int i=0; i<2; i++) {
QString keys_str = "keyboard";
bool use_keys = true;
if (i==0) {
keys_str = "insert";
use_keys = false;
}
{
IntList insertIndex;
IntList insertMode;
QStringList insertString;
QStringList expectedString;
insertIndex << -1;
insertMode << NORMAL;
insertString << "1";
insertIndex << -1;
insertMode << NORMAL;
insertString << "5";
insertIndex << 1;
insertMode << NORMAL;
insertString << "3";
insertIndex << 1;
insertMode << NORMAL;
insertString << "2";
insertIndex << 3;
insertMode << NORMAL;
insertString << "4";
expectedString << "12345";
expectedString << "1235";
expectedString << "135";
expectedString << "15";
expectedString << "";
QTest::newRow(QString(keys_str + "_numbers").toLatin1()) <<
insertString <<
insertIndex <<
insertMode <<
expectedString <<
bool(use_keys);
}
{
IntList insertIndex;
IntList insertMode;
QStringList insertString;
QStringList expectedString;
insertIndex << -1;
insertMode << NORMAL;
insertString << "World"; // World
insertIndex << 0;
insertMode << NORMAL;
insertString << "Hello"; // HelloWorld
insertIndex << 0;
insertMode << NORMAL;
insertString << "Well"; // WellHelloWorld
insertIndex << 9;
insertMode << NORMAL;
insertString << "There"; // WellHelloThereWorld;
expectedString << "WellHelloThereWorld";
expectedString << "WellHelloWorld";
expectedString << "HelloWorld";
expectedString << "World";
expectedString << "";
QTest::newRow(QString(keys_str + "_helloworld").toLatin1()) <<
insertString <<
insertIndex <<
insertMode <<
expectedString <<
bool(use_keys);
}
{
IntList insertIndex;
IntList insertMode;
QStringList insertString;
QStringList expectedString;
insertIndex << -1;
insertMode << NORMAL;
insertString << "Ensuring";
insertIndex << -1;
insertMode << NORMAL;
insertString << " instan";
insertIndex << 9;
insertMode << NORMAL;
insertString << "an ";
insertIndex << 10;
insertMode << REPLACE_UNTIL_END;
insertString << " unique instance.";
expectedString << "Ensuring a unique instance.";
expectedString << "Ensuring an instan";
expectedString << "Ensuring instan";
expectedString << "";
QTest::newRow(QString(keys_str + "_patterns").toLatin1()) <<
insertString <<
insertIndex <<
insertMode <<
expectedString <<
bool(use_keys);
}
}
}
void tst_qquicktextinput::undo()
{
QFETCH(QStringList, insertString);
QFETCH(IntList, insertIndex);
QFETCH(IntList, insertMode);
QFETCH(QStringList, expectedString);
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 != nullptr);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
QVERIFY(!textInput->canUndo());
QSignalSpy spy(textInput, 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]);
// experimental stuff
if (insertMode[i] == REPLACE_UNTIL_END) {
textInput->select(insertIndex[i], insertIndex[i] + 8);
// This is what I actually want...
// QTest::keyClick(testWidget, Qt::Key_End, Qt::ShiftModifier);
}
for (int j = 0; j < insertString.at(i).length(); j++)
QTest::keyClick(&window, 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]);
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()
{
QTest::addColumn<QStringList>("insertString");
QTest::addColumn<IntList>("insertIndex");
QTest::addColumn<QStringList>("expectedString");
{
IntList insertIndex;
QStringList insertString;
QStringList expectedString;
insertIndex << -1;
insertString << "World"; // World
insertIndex << 0;
insertString << "Hello"; // HelloWorld
insertIndex << 0;
insertString << "Well"; // WellHelloWorld
insertIndex << 9;
insertString << "There"; // WellHelloThereWorld;
expectedString << "World";
expectedString << "HelloWorld";
expectedString << "WellHelloWorld";
expectedString << "WellHelloThereWorld";
QTest::newRow("Inserts and setting cursor") << insertString << insertIndex << expectedString;
}
}
void tst_qquicktextinput::redo()
{
QFETCH(QStringList, insertString);
QFETCH(IntList, insertIndex);
QFETCH(QStringList, expectedString);
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 != nullptr);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
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) {
if (insertIndex[i] > -1)
textInput->setCursorPosition(insertIndex[i]);
for (int j = 0; j < insertString.at(i).length(); j++)
QTest::keyClick(&window, insertString.at(i).at(j).toLatin1());
QVERIFY(textInput->canUndo());
QVERIFY(!textInput->canRedo());
}
QCOMPARE(spy.count(), 0);
// undo everything
while (!textInput->text().isEmpty()) {
QVERIFY(textInput->canUndo());
textInput->undo();
QVERIFY(textInput->canRedo());
}
QCOMPARE(spy.count(), 1);
for (i = 0; i < expectedString.size(); ++i) {
QVERIFY(textInput->canRedo());
textInput->redo();
QCOMPARE(textInput->text() , expectedString[i]);
QVERIFY(textInput->canUndo());
}
QVERIFY(!textInput->canRedo());
QCOMPARE(spy.count(), 2);
}
#if QT_CONFIG(shortcut)
void tst_qquicktextinput::undo_keypressevents_data()
{
QTest::addColumn<KeyList>("keys");
QTest::addColumn<QStringList>("expectedString");
{
KeyList keys;
QStringList expectedString;
keys << "AFRAID"
<< QKeySequence::MoveToStartOfLine
<< "VERY"
<< Qt::Key_Left
<< Qt::Key_Left
<< Qt::Key_Left
<< Qt::Key_Left
<< "BE"
<< QKeySequence::MoveToEndOfLine
<< "!";
expectedString << "BEVERYAFRAID!";
expectedString << "BEVERYAFRAID";
expectedString << "VERYAFRAID";
expectedString << "AFRAID";
QTest::newRow("Inserts and moving cursor") << keys << expectedString;
} {
KeyList keys;
QStringList expectedString;
// inserting '1234'
keys << "1234" << QKeySequence::MoveToStartOfLine
// skipping '12'
<< Qt::Key_Right << Qt::Key_Right
// selecting '34'
<< (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
// deleting '34'
<< Qt::Key_Delete;
expectedString << "12";
expectedString << "1234";
QTest::newRow("Inserts,moving,selection and delete") << keys << expectedString;
} {
KeyList keys;
QStringList expectedString;
// inserting 'AB12'
keys << "AB12"
<< QKeySequence::MoveToStartOfLine
// selecting 'AB'
<< (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
<< Qt::Key_Backspace
<< QKeySequence::Undo
<< Qt::Key_Right
<< (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
<< Qt::Key_Delete;
expectedString << "AB";
expectedString << "AB12";
QTest::newRow("Inserts,moving,selection, delete and undo") << keys << expectedString;
} {
KeyList keys;
QStringList expectedString;
// inserting 'ABCD'
keys << "abcd"
//move left two
<< Qt::Key_Left << Qt::Key_Left
// inserting '1234'
<< "1234"
// selecting '1234'
<< (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier)
// overwriting '1234' with '5'
<< "5"
// undoing deletion of 'AB'
<< QKeySequence::Undo
// overwriting '1234' with '6'
<< "6";
expectedString << "ab6cd";
// for versions previous to 3.2 we overwrite needed two undo operations
expectedString << "ab1234cd";
expectedString << "abcd";
QTest::newRow("Inserts,moving,selection and undo, removing selection") << keys << expectedString;
} {
KeyList keys;
QStringList expectedString;
// inserting 'ABC'
keys << "ABC"
// removes 'C'
<< Qt::Key_Backspace;
expectedString << "AB";
expectedString << "ABC";
QTest::newRow("Inserts,backspace") << keys << expectedString;
} {
KeyList keys;
QStringList expectedString;
keys << "ABC"
// removes 'C'
<< Qt::Key_Backspace
// inserting 'Z'
<< "Z";
expectedString << "ABZ";
expectedString << "AB";
expectedString << "ABC";
QTest::newRow("Inserts,backspace,inserts") << keys << expectedString;
} {
KeyList keys;
QStringList expectedString;
// inserting '123'
keys << "123" << QKeySequence::MoveToStartOfLine
// selecting '123'
<< QKeySequence::SelectEndOfLine
// overwriting '123' with 'ABC'
<< "ABC";
expectedString << "ABC";
expectedString << "123";
QTest::newRow("Inserts,moving,selection and overwriting") << keys << expectedString;
} {
KeyList keys;
QStringList expectedString;
// inserting '123'
keys << "123"
<< QKeySequence::Undo
<< QKeySequence::Redo;
expectedString << "123";
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;
}
bool canCopyPaste = PlatformQuirks::isClipboardAvailable();
if (canCopyPaste) {
KeyList keys;
keys << "123"
<< QKeySequence(QKeySequence::SelectStartOfLine)
<< QKeySequence(QKeySequence::Cut)
<< "ABC"
<< QKeySequence(QKeySequence::Paste);
QStringList expectedString = QStringList()
<< "ABC123"
<< "ABC"
// TextEdit: ""
<< "123";
QTest::newRow("Cut,paste") << keys << expectedString;
}
if (canCopyPaste) {
KeyList keys;
keys << "123"
<< QKeySequence(QKeySequence::SelectStartOfLine)
<< QKeySequence(QKeySequence::Copy)
<< "ABC"
<< QKeySequence(QKeySequence::Paste);
QStringList expectedString = QStringList()
<< "ABC123"
<< "ABC"
// TextEdit: "A"
<< "123";
QTest::newRow("Copy,paste") << keys << expectedString;
}
}
void tst_qquicktextinput::undo_keypressevents()
{
QFETCH(KeyList, keys);
QFETCH(QStringList, expectedString);
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 != nullptr);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
simulateKeys(&window, keys);
for (int i = 0; i < expectedString.size(); ++i) {
QCOMPARE(textInput->text() , expectedString[i]);
textInput->undo();
}
QVERIFY(textInput->text().isEmpty());
}
#endif // QT_CONFIG(shortcut)
void tst_qquicktextinput::clear()
{
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 != nullptr);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
QVERIFY(!textInput->canUndo());
QSignalSpy spy(textInput, SIGNAL(canUndoChanged()));
textInput->setText("I am Legend");
QCOMPARE(textInput->text(), QString("I am Legend"));
textInput->clear();
QVERIFY(textInput->text().isEmpty());
QCOMPARE(spy.count(), 1);
// checks that clears can be undone
textInput->undo();
QVERIFY(!textInput->canUndo());
QCOMPARE(spy.count(), 2);
QCOMPARE(textInput->text(), QString("I am Legend"));
textInput->setCursorPosition(4);
QInputMethodEvent preeditEvent("PREEDIT", QList<QInputMethodEvent::Attribute>());
QGuiApplication::sendEvent(textInput, &preeditEvent);
QCOMPARE(textInput->text(), QString("I am Legend"));
QCOMPARE(textInput->displayText(), QString("I amPREEDIT Legend"));
QCOMPARE(textInput->preeditText(), QString("PREEDIT"));
textInput->clear();
QVERIFY(textInput->text().isEmpty());
QCOMPARE(spy.count(), 3);
// checks that clears can be undone
textInput->undo();
QVERIFY(!textInput->canUndo());
QCOMPARE(spy.count(), 4);
QCOMPARE(textInput->text(), QString("I am Legend"));
}
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 != nullptr);
textInput->setText(text);
textInput->setCursorPosition(text.length());
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QCOMPARE(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);
QQuickView window(testFileUrl(url));
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput*>(window.rootObject());
QVERIFY(input);
input->setFocus(true);
QVERIFY(input->hasActiveFocus());
QCOMPARE(window.rootObject()->property("topvalue").toInt(), 30);
QCOMPARE(window.rootObject()->property("bottomvalue").toInt(), 10);
QCOMPARE(window.rootObject()->property("text").toString(), QString("20"));
QVERIFY(window.rootObject()->property("acceptableInput").toBool());
window.rootObject()->setProperty("topvalue", 15);
QCOMPARE(window.rootObject()->property("topvalue").toInt(), 15);
QVERIFY(!window.rootObject()->property("acceptableInput").toBool());
window.rootObject()->setProperty("topvalue", 25);
QCOMPARE(window.rootObject()->property("topvalue").toInt(), 25);
QVERIFY(window.rootObject()->property("acceptableInput").toBool());
window.rootObject()->setProperty("bottomvalue", 21);
QCOMPARE(window.rootObject()->property("bottomvalue").toInt(), 21);
QVERIFY(!window.rootObject()->property("acceptableInput").toBool());
window.rootObject()->setProperty("bottomvalue", 10);
QCOMPARE(window.rootObject()->property("bottomvalue").toInt(), 10);
QVERIFY(window.rootObject()->property("acceptableInput").toBool());
}
void tst_qquicktextinput::QTBUG_19956_regexp()
{
QUrl url = testFileUrl("qtbug-19956regexp.qml");
QString warning = url.toString() + ":11:9: Unable to assign [undefined] to QRegExp";
QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
QQuickView window(url);
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(window.rootObject() != nullptr);
QQuickTextInput *input = qobject_cast<QQuickTextInput*>(window.rootObject());
QVERIFY(input);
input->setFocus(true);
QVERIFY(input->hasActiveFocus());
window.rootObject()->setProperty("regexvalue", QRegExp("abc"));
QCOMPARE(window.rootObject()->property("regexvalue").toRegExp(), QRegExp("abc"));
QCOMPARE(window.rootObject()->property("text").toString(), QString("abc"));
QVERIFY(window.rootObject()->property("acceptableInput").toBool());
window.rootObject()->setProperty("regexvalue", QRegExp("abcd"));
QCOMPARE(window.rootObject()->property("regexvalue").toRegExp(), QRegExp("abcd"));
QVERIFY(!window.rootObject()->property("acceptableInput").toBool());
window.rootObject()->setProperty("regexvalue", QRegExp("abc"));
QCOMPARE(window.rootObject()->property("regexvalue").toRegExp(), QRegExp("abc"));
QVERIFY(window.rootObject()->property("acceptableInput").toBool());
}
void tst_qquicktextinput::implicitSize_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QString>("wrap");
QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog" << "TextInput.NoWrap";
QTest::newRow("plain_wrap") << "The quick red fox jumped over the lazy brown dog" << "TextInput.Wrap";
}
void tst_qquicktextinput::implicitSize()
{
QFETCH(QString, text);
QFETCH(QString, wrap);
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; width: 50; wrapMode: " + wrap + " }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(textComponent.create());
QVERIFY(textObject->width() < textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
textObject->resetWidth();
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
}
void tst_qquicktextinput::implicitSizeBinding_data()
{
implicitSize_data();
}
void tst_qquicktextinput::implicitSizeBinding()
{
QFETCH(QString, text);
QFETCH(QString, wrap);
QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; width: implicitWidth; height: implicitHeight; wrapMode: " + wrap + " }";
QQmlComponent textComponent(&engine);
textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
QScopedPointer<QObject> object(textComponent.create());
QQuickTextInput *textObject = qobject_cast<QQuickTextInput *>(object.data());
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
textObject->resetWidth();
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
textObject->resetHeight();
QCOMPARE(textObject->width(), textObject->implicitWidth());
QCOMPARE(textObject->height(), textObject->implicitHeight());
}
void tst_qquicktextinput::implicitResize_data()
{
QTest::addColumn<int>("alignment");
QTest::newRow("left") << int(Qt::AlignLeft);
QTest::newRow("center") << int(Qt::AlignHCenter);
QTest::newRow("right") << int(Qt::AlignRight);
}
void tst_qquicktextinput::implicitResize()
{
QFETCH(int, alignment);
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\nTextInput { }", QUrl::fromLocalFile(""));
QScopedPointer<QQuickTextInput> textInput(qobject_cast<QQuickTextInput *>(component.create()));
QVERIFY(!textInput.isNull());
QScopedPointer<QQuickTextInput> textField(qobject_cast<QQuickTextInput *>(component.create()));
QVERIFY(!textField.isNull());
QQuickTextInputPrivate::get(textField.data())->setImplicitResizeEnabled(false);
textInput->setWidth(200);
textField->setImplicitWidth(200);
textInput->setHAlign(QQuickTextInput::HAlignment(alignment));
textField->setHAlign(QQuickTextInput::HAlignment(alignment));
textInput->setText("Qt");
textField->setText("Qt");
QCOMPARE(textField->positionToRectangle(0), textInput->positionToRectangle(0));
}
void tst_qquicktextinput::negativeDimensions()
{
// Verify this doesn't assert during initialization.
QQmlComponent component(&engine, testFileUrl("negativeDimensions.qml"));
QScopedPointer<QObject> o(component.create());
QVERIFY(o);
QQuickTextInput *input = o->findChild<QQuickTextInput *>("input");
QVERIFY(input);
QCOMPARE(input->width(), qreal(-1));
QCOMPARE(input->height(), qreal(-1));
}
void tst_qquicktextinput::keypress_inputMask_withValidator_data()
{
QTest::addColumn<QString>("mask");
QTest::addColumn<qreal>("validatorMinimum");
QTest::addColumn<qreal>("validatorMaximum");
QTest::addColumn<int>("decimals");
QTest::addColumn<QString>("validatorRegExp");
QTest::addColumn<KeyList>("keys");
QTest::addColumn<QString>("expectedText");
QTest::addColumn<QString>("expectedDisplayText");
{
KeyList keys;
// inserting '1212' then two backspaces
keys << Qt::Key_Home << "1212" << Qt::Key_Backspace << Qt::Key_Backspace;
QTest::newRow("backspaceWithInt") << QString("9999;_") << 1.0 << 9999.00 << 0 << QString()
<< keys << QString("12") << QString("12__");
}
{
KeyList keys;
// inserting '12.12' then two backspaces
keys << Qt::Key_Home << "12.12" << Qt::Key_Backspace << Qt::Key_Backspace;
QTest::newRow("backspaceWithDouble") << QString("99.99;_") << 1.0 << 99.99 << 2 << QString()
<< keys << QString("12.") << QString("12.__");
}
{
KeyList keys;
// inserting '1111.11' then two backspaces
keys << Qt::Key_Home << "1111.11" << Qt::Key_Backspace << Qt::Key_Backspace;
QTest::newRow("backspaceWithRegExp") << QString("9999;_") << 0.0 << 0.0 << 0
<< QString("/^[-]?((\\.\\d+)|(\\d+(\\.\\d+)?))$/")
<< keys << QString("11") << QString("11__");
}
{
KeyList keys;
// inserting '99' - QTBUG-64616
keys << Qt::Key_Home << "99";
QTest::newRow("invalidTextWithRegExp") << QString("X9;_") << 0.0 << 0.0 << 0
<< QString("/[+-][0+9]/")
<< keys << QString("") << QString("__");
}
}
void tst_qquicktextinput::keypress_inputMask_withValidator()
{
QFETCH(QString, mask);
QFETCH(qreal, validatorMinimum);
QFETCH(qreal, validatorMaximum);
QFETCH(int, decimals);
QFETCH(QString, validatorRegExp);
QFETCH(KeyList, keys);
QFETCH(QString, expectedText);
QFETCH(QString, expectedDisplayText);
QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\"\n";
if (!validatorRegExp.isEmpty())
componentStr += "validator: RegExpValidator { regExp: " + validatorRegExp + " }\n}";
else if (decimals > 0)
componentStr += QString("validator: DoubleValidator { bottom: %1; decimals: %2; top: %3 }\n}").
arg(validatorMinimum).arg(decimals).arg(validatorMaximum);
else
componentStr += QString("validator: IntValidator { bottom: %1; top: %2 }\n}").
arg((int)validatorMinimum).arg((int)validatorMaximum);
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
simulateKeys(&window, keys);
QCOMPARE(textInput->text(), expectedText);
QCOMPARE(textInput->displayText(), expectedDisplayText);
}
void tst_qquicktextinput::setInputMask_data()
{
QTest::addColumn<QString>("mask");
QTest::addColumn<QString>("input");
QTest::addColumn<QString>("expectedText");
QTest::addColumn<QString>("expectedDisplay");
QTest::addColumn<bool>("insert_text");
// both keyboard and insert()
for (int i=0; i<2; i++) {
bool insert_text = i==0 ? false : true;
QString insert_mode = "keys ";
if (insert_text)
insert_mode = "insert ";
QTest::newRow(QString(insert_mode + "ip_localhost").toLatin1())
<< QString("000.000.000.000")
<< QString("127.0.0.1")
<< QString("127.0.0.1")
<< QString("127.0 .0 .1 ")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "mac").toLatin1())
<< QString("HH:HH:HH:HH:HH:HH;#")
<< QString("00:E0:81:21:9E:8E")
<< QString("00:E0:81:21:9E:8E")
<< QString("00:E0:81:21:9E:8E")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "mac2").toLatin1())
<< QString("<HH:>HH:!HH:HH:HH:HH;#")
<< QString("AAe081219E8E")
<< QString("aa:E0:81:21:9E:8E")
<< QString("aa:E0:81:21:9E:8E")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "byte").toLatin1())
<< QString("BBBBBBBB;0")
<< QString("11011001")
<< QString("11111")
<< QString("11011001")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "halfbytes").toLatin1())
<< QString("bbbb.bbbb;-")
<< QString("110. 0001")
<< QString("110.0001")
<< QString("110-.0001")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "blank char same type as content").toLatin1())
<< QString("000.000.000.000;0")
<< QString("127.0.0.1")
<< QString("127...1")
<< QString("127.000.000.100")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "parts of ip_localhost").toLatin1())
<< QString("000.000.000.000")
<< QString(".0.0.1")
<< QString(".0.0.1")
<< QString(" .0 .0 .1 ")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "ip_null").toLatin1())
<< QString("000.000.000.000")
<< QString()
<< QString("...")
<< QString(" . . . ")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "ip_null_hash").toLatin1())
<< QString("000.000.000.000;#")
<< QString()
<< QString("...")
<< QString("###.###.###.###")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "ip_overflow").toLatin1())
<< QString("000.000.000.000")
<< QString("1234123412341234")
<< QString("123.412.341.234")
<< QString("123.412.341.234")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "uppercase").toLatin1())
<< QString(">AAAA")
<< QString("AbCd")
<< QString("ABCD")
<< QString("ABCD")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "lowercase").toLatin1())
<< QString("<AAAA")
<< QString("AbCd")
<< QString("abcd")
<< QString("abcd")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "nocase").toLatin1())
<< QString("!AAAA")
<< QString("AbCd")
<< QString("AbCd")
<< QString("AbCd")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "nocase1").toLatin1())
<< QString("!A!A!A!A")
<< QString("AbCd")
<< QString("AbCd")
<< QString("AbCd")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "nocase2").toLatin1())
<< QString("AAAA")
<< QString("AbCd")
<< QString("AbCd")
<< QString("AbCd")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "reserved").toLatin1())
<< QString("{n}[0]")
<< QString("A9")
<< QString("A9")
<< QString("A9")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "escape01").toLatin1())
<< QString("\\\\N\\\\n00")
<< QString("9")
<< QString("Nn9")
<< QString("Nn9 ")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "escape02").toLatin1())
<< QString("\\\\\\\\00")
<< QString("0")
<< QString("\\0")
<< QString("\\0 ")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "escape03").toLatin1())
<< QString("\\\\(00\\\\)")
<< QString("0")
<< QString("(0)")
<< QString("(0 )")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "upper_lower_nocase1").toLatin1())
<< QString(">AAAA<AAAA!AAAA")
<< QString("AbCdEfGhIjKl")
<< QString("ABCDefghIjKl")
<< QString("ABCDefghIjKl")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "upper_lower_nocase2").toLatin1())
<< QString(">aaaa<aaaa!aaaa")
<< QString("AbCdEfGhIjKl")
<< QString("ABCDefghIjKl")
<< QString("ABCDefghIjKl")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "exact_case1").toLatin1())
<< QString(">A<A<A>A>A<A!A!A")
<< QString("AbCdEFGH")
<< QString("AbcDEfGH")
<< QString("AbcDEfGH")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "exact_case2").toLatin1())
<< QString(">A<A<A>A>A<A!A!A")
<< QString("aBcDefgh")
<< QString("AbcDEfgh")
<< QString("AbcDEfgh")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "exact_case3").toLatin1())
<< QString(">a<a<a>a>a<a!a!a")
<< QString("AbCdEFGH")
<< QString("AbcDEfGH")
<< QString("AbcDEfGH")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "exact_case4").toLatin1())
<< QString(">a<a<a>a>a<a!a!a")
<< QString("aBcDefgh")
<< QString("AbcDEfgh")
<< QString("AbcDEfgh")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "exact_case5").toLatin1())
<< QString(">H<H<H>H>H<H!H!H")
<< QString("aBcDef01")
<< QString("AbcDEf01")
<< QString("AbcDEf01")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "exact_case6").toLatin1())
<< QString(">h<h<h>h>h<h!h!h")
<< QString("aBcDef92")
<< QString("AbcDEf92")
<< QString("AbcDEf92")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "illegal_keys1").toLatin1())
<< QString("AAAAAAAA")
<< QString("A2#a;.0!")
<< QString("Aa")
<< QString("Aa ")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "illegal_keys2").toLatin1())
<< QString("AAAA")
<< QString("f4f4f4f4")
<< QString("ffff")
<< QString("ffff")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "blank=input").toLatin1())
<< QString("9999;0")
<< QString("2004")
<< QString("24")
<< QString("2004")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "any_opt").toLatin1())
<< QString("@xxx@")
<< QString("@A C@")
<< QString("@AC@")
<< QString("@A C@")
<< bool(insert_text);
QTest::newRow(QString(insert_mode + "any_req").toLatin1())
<< QString("@XXX@")
<< QString("@A C@")
<< QString("@AC@@")
<< QString("@AC@@")
<< bool(insert_text);
}
}
void tst_qquicktextinput::setInputMask()
{
QFETCH(QString, mask);
QFETCH(QString, input);
QFETCH(QString, expectedText);
QFETCH(QString, expectedDisplay);
QFETCH(bool, insert_text);
QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
// [QTBUG-80190] check if setting the same property value again doesn't emit an
// inputMaskChanged signal
QString unescapedMask = mask; // mask is escaped, because '\' is also escape in a JS string
unescapedMask.replace(QLatin1String("\\\\"), QLatin1String("\\")); // simple unescape
QSignalSpy spy(textInput, SIGNAL(inputMaskChanged(const QString &)));
textInput->setInputMask(unescapedMask);
QCOMPARE(spy.count(), 0);
// then either insert using insert() or keyboard
if (insert_text) {
textInput->insert(0, input);
} else {
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
simulateKey(&window, Qt::Key_Home);
for (int i = 0; i < input.length(); i++)
QTest::keyClick(&window, input.at(i).toLatin1());
}
QCOMPARE(textInput->text(), expectedText);
QCOMPARE(textInput->displayText(), expectedDisplay);
}
void tst_qquicktextinput::inputMask_data()
{
QTest::addColumn<QString>("mask");
QTest::addColumn<QString>("expectedMask");
// if no mask is set a nul string should be returned
QTest::newRow("nul 1") << QString("") << QString();
QTest::newRow("nul 2") << QString() << QString();
// try different masks
QTest::newRow("mask 1") << QString("000.000.000.000") << QString("000.000.000.000; ");
QTest::newRow("mask 2") << QString("000.000.000.000;#") << QString("000.000.000.000;#");
QTest::newRow("mask 3") << QString("AAA.aa.999.###;") << QString("AAA.aa.999.###; ");
QTest::newRow("mask 4") << QString(">abcdef<GHIJK") << QString(">abcdef<GHIJK; ");
// set an invalid input mask...
// the current behaviour is that this exact (faulty) string is returned.
QTest::newRow("invalid") << QString("ABCDEFGHIKLMNOP;") << QString("ABCDEFGHIKLMNOP; ");
// verify that we can unset the mask again
QTest::newRow("unset") << QString("") << QString();
}
void tst_qquicktextinput::inputMask()
{
QFETCH(QString, mask);
QFETCH(QString, expectedMask);
QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
QCOMPARE(textInput->inputMask(), expectedMask);
}
void tst_qquicktextinput::clearInputMask()
{
QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"000.000.000.000\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
QVERIFY(!textInput->inputMask().isEmpty());
textInput->setInputMask(QString());
QCOMPARE(textInput->inputMask(), QString());
}
void tst_qquicktextinput::keypress_inputMask_data()
{
QTest::addColumn<QString>("mask");
QTest::addColumn<KeyList>("keys");
QTest::addColumn<QString>("expectedText");
QTest::addColumn<QString>("expectedDisplayText");
{
KeyList keys;
// inserting 'A1.2B'
keys << Qt::Key_Home << "A1.2B";
QTest::newRow("jumping on period(separator)") << QString("000.000;_") << keys << QString("1.2") << QString("1__.2__");
}
{
KeyList keys;
// inserting '0!P3'
keys << Qt::Key_Home << "0!P3";
QTest::newRow("jumping on input") << QString("D0.AA.XX.AA.00;_") << keys << QString("0..!P..3") << QString("_0.__.!P.__.3_");
}
{
KeyList keys;
// pressing delete
keys << Qt::Key_Home
<< Qt::Key_Delete;
QTest::newRow("delete") << QString("000.000;_") << keys << QString(".") << QString("___.___");
}
{
KeyList keys;
// selecting all and delete
keys << Qt::Key_Home
<< Key(Qt::ShiftModifier, Qt::Key_End)
<< Qt::Key_Delete;
QTest::newRow("deleting all") << QString("000.000;_") << keys << QString(".") << QString("___.___");
}
{
KeyList keys;
// inserting '12.12' then two backspaces
keys << Qt::Key_Home << "12.12" << Qt::Key_Backspace << Qt::Key_Backspace;
QTest::newRow("backspace") << QString("000.000;_") << keys << QString("12.") << QString("12_.___");
}
{
KeyList keys;
// inserting '12ab'
keys << Qt::Key_Home << "12ab";
QTest::newRow("uppercase") << QString("9999 >AA;_") << keys << QString("12 AB") << QString("12__ AB");
}
{
KeyList keys;
// inserting '12ab'
keys << Qt::Key_Right << Qt::Key_Right << "1";
QTest::newRow("Move in mask") << QString("#0:00;*") << keys << QString(":1") << QString("**:1*");
}
}
void tst_qquicktextinput::keypress_inputMask()
{
QFETCH(QString, mask);
QFETCH(KeyList, keys);
QFETCH(QString, expectedText);
QFETCH(QString, expectedDisplayText);
QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
simulateKeys(&window, keys);
QCOMPARE(textInput->text(), expectedText);
QCOMPARE(textInput->displayText(), expectedDisplayText);
}
void tst_qquicktextinput::keypress_inputMethod_inputMask()
{
// Similar to the keypress_inputMask test, but this is done solely via
// input methods
QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"AA.AA.AA\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
QQuickWindow window;
textInput->setParentItem(window.contentItem());
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QVERIFY(textInput->hasActiveFocus());
{
QList<QInputMethodEvent::Attribute> attributes;
QInputMethodEvent event("", attributes);
event.setCommitString("EE");
QGuiApplication::sendEvent(textInput, &event);
}
QCOMPARE(textInput->cursorPosition(), 3);
QCOMPARE(textInput->text(), QStringLiteral("EE.."));
{
QList<QInputMethodEvent::Attribute> attributes;
QInputMethodEvent event("", attributes);
event.setCommitString("EE");
QGuiApplication::sendEvent(textInput, &event);
}
QCOMPARE(textInput->cursorPosition(), 6);
QCOMPARE(textInput->text(), QStringLiteral("EE.EE."));
{
QList<QInputMethodEvent::Attribute> attributes;
QInputMethodEvent event("", attributes);
event.setCommitString("EE");
QGuiApplication::sendEvent(textInput, &event);
}
QCOMPARE(textInput->cursorPosition(), 8);
QCOMPARE(textInput->text(), QStringLiteral("EE.EE.EE"));
}
void tst_qquicktextinput::hasAcceptableInputMask_data()
{
QTest::addColumn<QString>("optionalMask");
QTest::addColumn<QString>("requiredMask");
QTest::addColumn<QString>("invalid");
QTest::addColumn<QString>("valid");
QTest::newRow("Alphabetic optional and required")
<< QString("aaaa") << QString("AAAA") << QString("ab") << QString("abcd");
QTest::newRow("Alphanumeric optional and require")
<< QString("nnnn") << QString("NNNN") << QString("R2") << QString("R2D2");
QTest::newRow("Any optional and required")
<< QString("xxxx") << QString("XXXX") << QString("+-") << QString("+-*/");
QTest::newRow("Numeric (0-9) required")
<< QString("0000") << QString("9999") << QString("11") << QString("1138");
QTest::newRow("Numeric (1-9) optional and required")
<< QString("dddd") << QString("DDDD") << QString("12") << QString("1234");
}
void tst_qquicktextinput::hasAcceptableInputMask()
{
QFETCH(QString, optionalMask);
QFETCH(QString, requiredMask);
QFETCH(QString, invalid);
QFETCH(QString, valid);
QString componentStr = "import QtQuick 2.0\nTextInput { inputMask: \"" + optionalMask + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
// test that invalid input (for required) work for optionalMask
textInput->setText(invalid);
QVERIFY(textInput->hasAcceptableInput());
// test requiredMask
textInput->setInputMask(requiredMask);
textInput->setText(invalid);
// invalid text gets the input mask applied when setting, text becomes acceptable.
QVERIFY(textInput->hasAcceptableInput());
textInput->setText(valid);
QVERIFY(textInput->hasAcceptableInput());
}
void tst_qquicktextinput::maskCharacter_data()
{
QTest::addColumn<QString>("mask");
QTest::addColumn<QString>("input");
QTest::addColumn<bool>("expectedValid");
QTest::newRow("Hex") << QString("H")
<< QString("0123456789abcdefABCDEF") << true;
QTest::newRow("hex") << QString("h")
<< QString("0123456789abcdefABCDEF") << true;
QTest::newRow("HexInvalid") << QString("H")
<< QString("ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ")
<< false;
QTest::newRow("hexInvalid") << QString("h")
<< QString("ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ")
<< false;
QTest::newRow("Bin") << QString("B")
<< QString("01") << true;
QTest::newRow("bin") << QString("b")
<< QString("01") << true;
QTest::newRow("BinInvalid") << QString("B")
<< QString("23456789qwertyuiopasdfghjklzxcvbnm")
<< false;
QTest::newRow("binInvalid") << QString("b")
<< QString("23456789qwertyuiopasdfghjklzxcvbnm")
<< false;
}
void tst_qquicktextinput::maskCharacter()
{
QFETCH(QString, mask);
QFETCH(QString, input);
QFETCH(bool, expectedValid);
QString componentStr = "import QtQuick 2.0\nTextInput { inputMask: \"" + mask + "\" }";
QQmlComponent textInputComponent(&engine);
textInputComponent.setData(componentStr.toLatin1(), QUrl());
QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
QVERIFY(textInput != nullptr);
for (int i = 0; i < input.size(); ++i) {
QString in = QString(input.at(i));
QString expected = expectedValid ? in : QString();
textInput->setText(QString(input.at(i)));
QCOMPARE(textInput->text(), expected);
}
}
class TestValidator : public QValidator
{
public:
TestValidator(QObject *parent = nullptr) : QValidator(parent) { }
State validate(QString &input, int &) const { return input == QStringLiteral("ok") ? Acceptable : Intermediate; }
void fixup(QString &input) const { input = QStringLiteral("ok"); }
};
void tst_qquicktextinput::fixup()
{
QQuickWindow window;
window.show();
window.requestActivate();
QVERIFY(QTest::qWaitForWindowActive(&window));
QQuickTextInput *input = new QQuickTextInput(window.contentItem());
input->setValidator(new TestValidator(input));
// fixup() on accept
input->setFocus(true);
QVERIFY(input->hasActiveFocus());
QTest::keyClick(&window, Qt::Key_Enter);
QCOMPARE(input->text(), QStringLiteral("ok"));
// fixup() on defocus
input->setText(QString());
input->setFocus(false);
QVERIFY(!input->hasActiveFocus());
QCOMPARE(input->text(), QStringLiteral("ok"));
}
typedef qreal (*ExpectedBaseline)(QQuickTextInput *item);
Q_DECLARE_METATYPE(ExpectedBaseline)
static qreal expectedBaselineTop(QQuickTextInput *item)
{
QFontMetricsF fm(item->font());
return fm.ascent() + item->topPadding();
}
static qreal expectedBaselineBottom(QQuickTextInput *item)
{
QFontMetricsF fm(item->font());
return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent();
}
static qreal expectedBaselineCenter(QQuickTextInput *item)
{
QFontMetricsF fm(item->font());
return ((item->height() - item->contentHeight() - item->topPadding() - item->bottomPadding()) / 2) + fm.ascent() + item->topPadding();
}
static qreal expectedBaselineMultilineBottom(QQuickTextInput *item)
{
QFontMetricsF fm(item->font());
return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent();
}
void tst_qquicktextinput::baselineOffset_data()
{
QTest::addColumn<QString>("text");
QTest::addColumn<QByteArray>("bindings");
QTest::addColumn<qreal>("setHeight");
QTest::addColumn<ExpectedBaseline>("expectedBaseline");
QTest::addColumn<ExpectedBaseline>("expectedBaselineEmpty");
QTest::newRow("normal")
<< "Typography"
<< QByteArray()
<< -1.
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("top align")
<< "Typography"
<< QByteArray("height: 200; verticalAlignment: Text.AlignTop")
<< -1.
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("bottom align")
<< "Typography"
<< QByteArray("height: 200; verticalAlignment: Text.AlignBottom")
<< 100.
<< &expectedBaselineBottom
<< &expectedBaselineBottom;
QTest::newRow("center align")
<< "Typography"
<< QByteArray("height: 200; verticalAlignment: Text.AlignVCenter")
<< 100.
<< &expectedBaselineCenter
<< &expectedBaselineCenter;
QTest::newRow("multiline bottom aligned")
<< "The quick brown fox jumps over the lazy dog"
<< QByteArray("height: 200; width: 30; verticalAlignment: Text.AlignBottom; wrapMode: TextInput.WordWrap")
<< -1.
<< &expectedBaselineMultilineBottom
<< &expectedBaselineBottom;
QTest::newRow("padding")
<< "Typography"
<< QByteArray("topPadding: 10; bottomPadding: 20")
<< -1.
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("top align with padding")
<< "Typography"
<< QByteArray("height: 200; verticalAlignment: Text.AlignTop; topPadding: 10; bottomPadding: 20")
<< -1.
<< &expectedBaselineTop
<< &expectedBaselineTop;
QTest::newRow("bottom align with padding")
<< "Typography"
<< QByteArray("height: 200; verticalAlignment: Text.AlignBottom; topPadding: 10; bottomPadding: 20")
<< 100.
<< &expectedBaselineBottom
<< &expectedBaselineBottom;
QTest::newRow("center align with padding")
<< "Typography"
<< QByteArray("height: 200; verticalAlignment: Text.AlignVCenter; topPadding: 10; bottomPadding: 20")
<< 100.
<< &expectedBaselineCenter
<< &expectedBaselineCenter;
QTest::newRow("multiline bottom aligned with padding")
<< "The quick brown fox jumps over the lazy dog"
<< QByteArray("height: 200; width: 30; verticalAlignment: Text.AlignBottom; wrapMode: TextInput.WordWrap; topPadding: 10; bottomPadding: 20")
<< -1.
<< &expectedBaselineMultilineBottom
<< &expectedBaselineBottom;
}
void tst_qquicktextinput::baselineOffset()
{
QFETCH(QString, text);
QFETCH(QByteArray, bindings);
QFETCH(qreal, setHeight);
QFETCH(ExpectedBaseline, expectedBaseline);
QFETCH(ExpectedBaseline, expectedBaselineEmpty);
QQmlComponent component(&engine);
component.setData(
"import QtQuick 2.6\n"
"TextInput {\n"
+ bindings + "\n"
"}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickTextInput *item = qobject_cast<QQuickTextInput *>(object.data());
int passes = setHeight >= 0 ? 2 : 1;
while (passes--) {
QVERIFY(item);
QCOMPARE(item->baselineOffset(), expectedBaselineEmpty(item));
item->setText(text);
QCOMPARE(item->baselineOffset(), expectedBaseline(item));
item->setText(QString());
QCOMPARE(item->baselineOffset(), expectedBaselineEmpty(item));
if (setHeight >= 0)
item->setHeight(setHeight);
}
}
void tst_qquicktextinput::ensureVisible()
{
QQmlComponent component(&engine);
component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
QScopedPointer<QObject> object(component.create());
QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
QVERIFY(input);
input->setWidth(QFontMetrics(input->font()).averageCharWidth() * 3);
input->setText("Hello World");
QTextLayout layout;
layout.setText(input->text());
layout.setFont(input->font());
if (!qmlDisableDistanceField()) {
QTextOption option;
option.setUseDesignMetrics(true);
layout.setTextOption(option);
}
layout.beginLayout();
QTextLine line = layout.createLine();
layout.endLayout();
input->ensureVisible(0);
QCOMPARE(input->boundingRect().x(), qreal(0));
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
QCOMPARE(input->boundingRect().height(), line.height());
QSignalSpy cursorSpy(input, SIGNAL(cursorRectangleChanged()));
QVERIFY(cursorSpy.isValid());
input->ensureVisible(input->length());
QCOMPARE(cursorSpy.count(), 1);
QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
QCOMPARE(input->boundingRect().y(), qreal(0));
QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
QCOMPARE(input->boundingRect().height(), line.height());
}
void tst_qquicktextinput::padding()
{
QScopedPointer<QQuickView> window(new QQuickView);
window->setSource(testFileUrl("padding.qml"));
QTRY_COMPARE(window->status(), QQuickView::Ready);
window->show();
QVERIFY(QTest::qWaitForWindowExposed(window.data()));
QQuickItem *root = window->rootObject();
QVERIFY(root);
QQuickTextInput *obj = qobject_cast<QQuickTextInput*>(root);
QVERIFY(obj != nullptr);
qreal cw = obj->contentWidth();
qreal ch = obj->contentHeight();
QVERIFY(cw > 0);
QVERIFY(ch > 0);
QCOMPARE(obj->padding(), 10.0);
QCOMPARE(obj->topPadding(), 20.0);
QCOMPARE(obj->leftPadding(), 30.0);
QCOMPARE(obj->rightPadding(), 40.0);
QCOMPARE(obj->bottomPadding(), 50.0);
QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
obj->setTopPadding(2.25);
QCOMPARE(obj->topPadding(), 2.25);
QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
obj->setLeftPadding(3.75);
QCOMPARE(obj->leftPadding(), 3.75);
QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
obj->setRightPadding(4.4);
QCOMPARE(obj->rightPadding(), 4.4);
QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
obj->setBottomPadding(1.11);
QCOMPARE(obj->bottomPadding(), 1.11);
QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
obj->setText("Qt");
QVERIFY(obj->contentWidth() < cw);
QCOMPARE(obj->contentHeight(), ch);
cw = obj->contentWidth();
QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
obj->setFont(QFont("Courier", 96));
QVERIFY(obj->contentWidth() > cw);
QVERIFY(obj->contentHeight() > ch);
cw = obj->contentWidth();
ch = obj->contentHeight();
QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
obj->resetTopPadding();
QCOMPARE(obj->topPadding(), 10.0);
obj->resetLeftPadding();
QCOMPARE(obj->leftPadding(), 10.0);
obj->resetRightPadding();
QCOMPARE(obj->rightPadding(), 10.0);
obj->resetBottomPadding();
QCOMPARE(obj->bottomPadding(), 10.0);
obj->resetPadding();
QCOMPARE(obj->padding(), 0.0);
QCOMPARE(obj->topPadding(), 0.0);
QCOMPARE(obj->leftPadding(), 0.0);
QCOMPARE(obj->rightPadding(), 0.0);
QCOMPARE(obj->bottomPadding(), 0.0);
delete root;
}
void tst_qquicktextinput::QTBUG_51115_readOnlyResetsSelection()
{
QQuickView view;
view.setSource(testFileUrl("qtbug51115.qml"));
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
QQuickTextInput *obj = qobject_cast<QQuickTextInput*>(view.rootObject());
QCOMPARE(obj->selectedText(), QString());
}
void tst_qquicktextinput::QTBUG_77814_InsertRemoveNoSelection()
{
QQuickView view;
view.setSource(testFileUrl("qtbug77841.qml"));
view.show();
QVERIFY(QTest::qWaitForWindowExposed(&view));
QQuickTextInput *textInput = view.rootObject()->findChild<QQuickTextInput*>("qwe");
QVERIFY(textInput);
QCOMPARE(textInput->selectedText(), QString());
}
QTEST_MAIN(tst_qquicktextinput)
#include "tst_qquicktextinput.moc"