// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#include <qtest.h>
#include <QtTest/QSignalSpy>
#include <QtQuickTest/quicktest.h>
#include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtQuickTestUtils/private/testhttpserver_p.h>
#include <QtQuickTestUtils/private/viewtestutils_p.h>
#include <QtQuickTestUtils/private/visualtestutils_p.h>
#include <private/qinputmethod_p.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlexpression.h>
#include <QFile>
#include <QtQuick/qquickview.h>
#include <QtGui/qguiapplication.h>
#include <QtGui/qstylehints.h>
#include <QtGui/qvalidator.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qpointingdevice_p.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>

#include "qplatformdefs.h"
#include <QtQuickTestUtils/private/platformquirks_p.h>
#include <QtQuickTestUtils/private/platforminputcontext_p.h>

using namespace Qt::StringLiterals;

Q_DECLARE_METATYPE(QQuickTextInput::SelectionMode)
Q_DECLARE_METATYPE(QQuickTextInput::EchoMode)
Q_DECLARE_METATYPE(Qt::Key)

DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)

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; }

struct Key {
    QKeyCombination keyCombination;
    QChar character = {}; // the character generated by keyCombination (if any)
};

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 inputMethodQueryEnterKeyType();

    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 testCtrlBackspaceClearsSelectedText();
    void ensureVisible();
    void padding();

    void QTBUG_51115_readOnlyResetsSelection();
    void QTBUG_77814_InsertRemoveNoSelection();

    void checkCursorDelegateWhenPaddingChanged();

    void focusReason();

    void touchscreenDoesNotSelect_data();
    void touchscreenDoesNotSelect();
    void touchscreenSetsFocusAndMovesCursor();

    void imeSelectionWithCommitMovesCursor_NoMask();
    void imeSelectionWithCommitDoesNotOverride_WithMask();

private:
    void simulateKeys(QWindow *window, const QList<Key> &keys);
#if QT_CONFIG(shortcut)
    void simulateKeys(QWindow *window, const QKeySequence &sequence);
#endif

    QQmlEngine engine;
    const QStringList standard = {
        u"the quick brown fox jumped over the lazy dog"_s,
        u"It's supercalifragisiticexpialidocious!"_s,
        u"Hello, world!"_s,
        u"!dlrow ,olleH"_s,
        u" spacey   text "_s,
    };
    QStringList colorStrings;
    std::unique_ptr<QPointingDevice> touchscreen{QTest::createTouchDevice()};
};

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.size(); ++i) {
        const Qt::KeyboardModifiers modifiers = keys.at(i).keyCombination.keyboardModifiers();
        const QChar character = keys.at(i).character;
        if (!character.isNull())
            QTest::keyClick(window, character.toLatin1(), modifiers);
        else
            QTest::keyClick(window, keys.at(i).keyCombination.key(), modifiers);
    }
}

#if QT_CONFIG(shortcut)

void tst_qquicktextinput::simulateKeys(QWindow *window, const QKeySequence &sequence)
{
    for (int i = 0; i < sequence.count(); ++i)
        QTest::keyClick(window, sequence[i].key(), sequence[i].keyboardModifiers());
}

QList<Key> &operator <<(QList<Key> &keys, const QKeySequence &sequence)
{
    for (int i = 0; i < sequence.count(); ++i)
        keys << Key{sequence[i]};
    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)
        keys << Key{{}, QLatin1Char(characters[i])};
    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()
    : QQmlDataTest(QT_QMLTEST_DATADIR)
{
    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).size());

        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.size(), 1);

        textInputObject->setSelectionColor(QColor("black"));
        QCOMPARE(textInputObject->selectionColor(), QColor("black"));
        QCOMPARE(selectionColorSpy.size(), 1);

        textInputObject->setSelectedTextColor(QColor("blue"));
        QCOMPARE(textInputObject->selectedTextColor(), QColor("blue"));
        QCOMPARE(selectedTextColorSpy.size(), 1);

        textInputObject->setColor(QColor("white"));
        QCOMPARE(colorSpy.size(), 1);

        textInputObject->setSelectionColor(QColor("black"));
        QCOMPARE(selectionColorSpy.size(), 1);

        textInputObject->setSelectedTextColor(QColor("blue"));
        QCOMPARE(selectedTextColorSpy.size(), 1);

        textInputObject->setColor(QColor("black"));
        QCOMPARE(textInputObject->color(), QColor("black"));
        QCOMPARE(colorSpy.size(), 2);

        textInputObject->setSelectionColor(QColor("blue"));
        QCOMPARE(textInputObject->selectionColor(), QColor("blue"));
        QCOMPARE(selectionColorSpy.size(), 2);

        textInputObject->setSelectedTextColor(QColor("white"));
        QCOMPARE(textInputObject->selectedTextColor(), QColor("white"));
        QCOMPARE(selectedTextColorSpy.size(), 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.size(); 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.size(), 1);

        input->setWrapMode(QQuickTextInput::Wrap);
        QCOMPARE(spy.size(), 1);

        input->setWrapMode(QQuickTextInput::NoWrap);
        QCOMPARE(input->wrapMode(), QQuickTextInput::NoWrap);
        QCOMPARE(spy.size(), 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().size() + 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.size(), 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_ACTIVE_FOCUS(input);

    QSignalSpy spy(input, SIGNAL(persistentSelectionChanged()));

    QCOMPARE(input->persistentSelection(), false);

    input->setPersistentSelection(false);
    QCOMPARE(input->persistentSelection(), false);
    QCOMPARE(spy.size(), 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.size(), 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_ACTIVE_FOCUS(textInput);

    textInput->setOverwriteMode(true);
    QCOMPARE(spy.size(), 1);
    QCOMPARE(true, textInput->overwriteMode());
    textInput->setOverwriteMode(false);
    QCOMPARE(spy.size(), 2);
    QCOMPARE(false, textInput->overwriteMode());

    QVERIFY(!textInput->overwriteMode());
    QString insertString = "Some first text";
    for (int j = 0; j < insertString.size(); j++)
        QTest::keyClick(&window, insertString.at(j).toLatin1());

    QCOMPARE(textInput->text(), QString("Some first text"));

    textInput->setOverwriteMode(true);
    QCOMPARE(spy.size(), 3);
    textInput->setCursorPosition(5);

    insertString = "shiny";
    for (int j = 0; j < insertString.size(); 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 char16_t arabic_str[] = { 0x0638, 0x0643, 0x0646, 0x0647, 0x0633, 0x0638, 0x0643, 0x0646, 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.size()-2, text.size()-1), text.mid(text.size()-2, text.size()-1).isRightToLeft());
    QCOMPARE(textInput.isRightToLeft(text.size()/2, text.size()/2 + 1), text.mid(text.size()/2, text.size()/2 + 1).isRightToLeft());
    QCOMPARE(textInput.isRightToLeft(0,text.size()/4), text.mid(0,text.size()/4).isRightToLeft());
    QCOMPARE(textInput.isRightToLeft(text.size()/4,3*text.size()/4), text.mid(text.size()/4,3*text.size()/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.size()/4,text.size()-1), text.mid(3*text.size()/4,text.size()-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.size()-2, text.size()-1), lastCharacter);
    QCOMPARE(textInput.isRightToLeft(text.size()/2, text.size()/2 + 1), middleCharacter);
    QCOMPARE(textInput.isRightToLeft(0,text.size()/4), startString);
    QCOMPARE(textInput.isRightToLeft(text.size()/4,3*text.size()/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.size()/4,text.size()-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("mouseselectionmode_default.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);
    textInputObject->setSelectByMouse(true);

    // 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()).size() > 3);
    QTRY_VERIFY(str1.size() > 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.size() > 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();
    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().size() > 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.size(), 1);

    input->setMouseSelectionMode(QQuickTextInput::SelectWords);
    QCOMPARE(spy.size(), 1);

    input->setMouseSelectionMode(QQuickTextInput::SelectCharacters);
    QCOMPARE(input->mouseSelectionMode(), QQuickTextInput::SelectCharacters);
    QCOMPARE(spy.size(), 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.size(), 1);
    QCOMPARE(spy.at(0).at(0).toBool(), true);

    input->setSelectByMouse(true);
    QCOMPARE(spy.size(), 1);

    input->setSelectByMouse(false);
    QCOMPARE(input->selectByMouse(), false);
    QCOMPARE(spy.size(), 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.size(), 1);

    input->setRenderType(QQuickTextInput::NativeRendering);
    QCOMPARE(spy.size(), 1);

    input->setRenderType(QQuickTextInput::QtRendering);
    QCOMPARE(input->renderType(), QQuickTextInput::QtRendering);
    QCOMPARE(spy.size(), 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_ACTIVE_FOCUS(textInput);

    // 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.size(), 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);
    for (const QString &str : standard) {
        QVERIFY(textinputObject->text().size() <= 10);
        textinputObject->setText(str);
        QVERIFY(textinputObject->text().size() <= 10);
    }

    textinputObject->setText("");
    QTRY_VERIFY_ACTIVE_FOCUS(textinputObject);
    for (int i=0; i<20; i++) {
        QTRY_COMPARE(textinputObject->text().size(), qMin(i,10));
        QTest::keyClick(&window, Qt::Key_A);
    }
}

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_ACTIVE_FOCUS(textinputObject);
    QCOMPARE(textinputObject->text().size(), 0);
    QCOMPARE(textinputObject->inputMask(), QString("HHHHhhhh; "));
    QCOMPARE(textinputObject->length(), 8);
    for (int i=0; i<10; i++) {
        QTRY_COMPARE(qMin(i,8), textinputObject->text().size());
        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());
        QTest::keyClick(&window, Qt::Key_A);
    }
}

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.

    auto restoreLocale = qScopeGuard([original = QLocale()] {
        QLocale::setDefault(original);
    });
    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_ACTIVE_FOCUS(intInput);
    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.size(), 0);
    QCOMPARE(intInput->hasAcceptableInput(), false);
    QCOMPARE(intInput->property("acceptable").toBool(), false);
    QCOMPARE(intSpy.size(), 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.size(), 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.size(), 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_ACTIVE_FOCUS(dblInput);
    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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 4 + extraSignals);
    intValidator->setTop(12);
    QCOMPARE(dblInput->property("acceptable").toBool(), true);
    QCOMPARE(dblInput->hasAcceptableInput(), true);
    QCOMPARE(dblSpy.size(), 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_ACTIVE_FOCUS(strInput);
    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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 1);

    QQuickTextInput *unvalidatedInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(window.rootObject()->property("unvalidatedInput")));
    QVERIFY(unvalidatedInput);
    QSignalSpy unvalidatedSpy(unvalidatedInput, SIGNAL(acceptableInputChanged()));
    unvalidatedInput->setFocus(true);
    QVERIFY_ACTIVE_FOCUS(unvalidatedInput);
    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.size(), 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.size(), 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.size(), 1);
    input->setInputMethodHints(Qt::ImhUppercaseOnly);
    QCOMPARE(inputMethodHintSpy.size(), 1);

    // default value
    QQuickTextInput plainInput;
    QCOMPARE(plainInput.inputMethodHints(), Qt::ImhNone);

    input->setFocus(true);
    QVERIFY_ACTIVE_FOCUS(input);
    // 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().size());
    event.setCommitString("replacement", -input->text().size(), input->text().size());
    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::inputMethodQueryEnterKeyType()
{
    QQuickView view(testFileUrl("focusReason.qml"));

    QQuickTextInput *first = view.rootObject()->findChild<QQuickTextInput *>("first");
    QQuickTextInput *second = view.rootObject()->findChild<QQuickTextInput *>("second");
    QQuickTextInput *third = view.rootObject()->findChild<QQuickTextInput *>("third");
    QVERIFY(first && second && third);

    first->setActiveFocusOnTab(true);
    second->setActiveFocusOnTab(true);
    third->setActiveFocusOnTab(true);

    view.show();

    QVariant enterTypeFirst = first->inputMethodQuery(Qt::ImEnterKeyType);
    QVariant enterTypeSecond = second->inputMethodQuery(Qt::ImEnterKeyType);
    QVariant enterTypeThird = third->inputMethodQuery(Qt::ImEnterKeyType);
#ifdef Q_OS_ANDROID
    // QTBUG-61652
    // EnterKey is changed to EnterKeyNext if the focus can be moved to item below
    QCOMPARE(enterTypeFirst.value<Qt::EnterKeyType>(), Qt::EnterKeyNext);
    QCOMPARE(enterTypeSecond.value<Qt::EnterKeyType>(), Qt::EnterKeyNext);
#else
    QCOMPARE(enterTypeFirst.value<Qt::EnterKeyType>(), Qt::EnterKeyDefault);
    QCOMPARE(enterTypeSecond.value<Qt::EnterKeyType>(), Qt::EnterKeyDefault);
#endif
    QCOMPARE(enterTypeThird.value<Qt::EnterKeyType>(), Qt::EnterKeyDefault);
}

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_ACTIVE_FOCUS(input);
    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.size(), 0);

    QTest::keyPress(&window, Qt::Key_Enter);
    QTest::keyRelease(&window, Qt::Key_Enter, Qt::NoModifier);
    QTRY_COMPARE(acceptedSpy.size(), 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.size(), 1);

    QTest::keyPress(&window, Qt::Key_Enter);
    QTest::keyRelease(&window, Qt::Key_Enter, Qt::NoModifier);
    QTRY_COMPARE(acceptedSpy.size(), 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_ACTIVE_FOCUS(input1);
    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.size(), 0);

    QTest::keyPress(&window, Qt::Key_Enter);
    QTest::keyRelease(&window, Qt::Key_Enter, Qt::NoModifier);
    QTRY_COMPARE(editingFinished1Spy.size(), 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.size(), 1);

    QTest::keyPress(&window, Qt::Key_Enter);
    QTest::keyRelease(&window, Qt::Key_Enter, Qt::NoModifier);
    QTRY_COMPARE(editingFinished1Spy.size(), 1);
    QTRY_COMPARE(input1Spy.size(), 1);

    QSignalSpy editingFinished2Spy(input2, SIGNAL(editingFinished()));
    QSignalSpy input2Spy(input2, SIGNAL(acceptableInputChanged()));

    input2->setFocus(true);
    QTRY_VERIFY(!input1->hasActiveFocus());
    QTRY_VERIFY_ACTIVE_FOCUS(input2);

    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.size(), 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.size(), 1);

    input1->setFocus(true);
    QTRY_VERIFY_ACTIVE_FOCUS(input1);
    QTRY_VERIFY(!input2->hasActiveFocus());
    QTRY_COMPARE(editingFinished2Spy.size(), 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_ACTIVE_FOCUS(input);

    int textChanges = 0;
    int textEdits = 0;

    QTest::keyClick(&window, Qt::Key_A);
    QCOMPARE(textChangedSpy.size(), ++textChanges);
    QCOMPARE(textEditedSpy.size(), ++textEdits);

    QTest::keyClick(&window, Qt::Key_B);
    QCOMPARE(textChangedSpy.size(), ++textChanges);
    QCOMPARE(textEditedSpy.size(), ++textEdits);

    QTest::keyClick(&window, Qt::Key_C);
    QCOMPARE(textChangedSpy.size(), ++textChanges);
    QCOMPARE(textEditedSpy.size(), ++textEdits);

    QTest::keyClick(&window, Qt::Key_Space);
    QCOMPARE(textChangedSpy.size(), ++textChanges);
    QCOMPARE(textEditedSpy.size(), ++textEdits);

    QTest::keyClick(&window, Qt::Key_Backspace);
    QCOMPARE(textChangedSpy.size(), ++textChanges);
    QCOMPARE(textEditedSpy.size(), ++textEdits);

    input->clear();
    QCOMPARE(textChangedSpy.size(), ++textChanges);
    QCOMPARE(textEditedSpy.size(), textEdits);

    input->setText("TextInput");
    QCOMPARE(textChangedSpy.size(), ++textChanges);
    QCOMPARE(textEditedSpy.size(), 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_ACTIVE_FOCUS(input);
    QTest::keyClick(&window, Qt::Key_Left);
    QVERIFY(!input->hasActiveFocus());
    QTest::keyClick(&window, Qt::Key_Right);
    QVERIFY_ACTIVE_FOCUS(input);
    //QT-2944: If text is selected, ensure we deselect upon cursor motion
    input->setCursorPosition(input->text().size());
    input->select(0,input->text().size());
    QVERIFY(input->selectionStart() != input->selectionEnd());
    QTest::keyClick(&window, Qt::Key_Right);
    QCOMPARE(input->selectionStart(), input->selectionEnd());
    QCOMPARE(input->selectionStart(), input->text().size());
    QVERIFY_ACTIVE_FOCUS(input);
    QTest::keyClick(&window, Qt::Key_Right);
    QVERIFY(!input->hasActiveFocus());
    QTest::keyClick(&window, Qt::Key_Left);
    QVERIFY_ACTIVE_FOCUS(input);

    // Up and Down should NOT do Home/End, even on OS X (QTBUG-10438).
    input->setCursorPosition(2);
    QCOMPARE(input->cursorPosition(),2);
    QTest::keyClick(&window, Qt::Key_Up);
    QCOMPARE(input->cursorPosition(),2);
    QTest::keyClick(&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);
    QTest::keyClick(&window, Qt::Key_Right);
    QCOMPARE(input->hasActiveFocus(), false);
    QTest::keyClick(&window, Qt::Key_Left);
    QCOMPARE(input->hasActiveFocus(), true);
    QTest::keyClick(&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 char16_t arabic_str[] = { 0x0638, 0x0643, 0x0646, 0x0647, 0x0633, 0x0638, 0x0643, 0x0646, 0x0647, 0x0633, 0x0647};
    input->setText(QString::fromUtf16(arabic_str, 11));

    input->setCursorPosition(0);
    QTRY_VERIFY_ACTIVE_FOCUS(input);

    // move off
    QTest::keyClick(&window, Qt::Key_Right);
    QVERIFY(!input->hasActiveFocus());

    // move back
    QTest::keyClick(&window, Qt::Key_Left);
    QVERIFY_ACTIVE_FOCUS(input);

    input->setCursorPosition(input->text().size());
    QVERIFY_ACTIVE_FOCUS(input);

    // move off
    QTest::keyClick(&window, Qt::Key_Left);
    QVERIFY(!input->hasActiveFocus());

    // move back
    QTest::keyClick(&window, Qt::Key_Right);
    QVERIFY_ACTIVE_FOCUS(input);
}

#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().size(), 12);
    textInput->select(0, textInput->text().size());
    textInput->copy();
    QCOMPARE(textInput->selectedText(), QString("Hello world!"));
    QCOMPARE(textInput->selectedText().size(), 12);
    textInput->setCursorPosition(0);
    QTRY_VERIFY(textInput->canPaste());
    textInput->paste();
    QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
    QCOMPARE(textInput->text().size(), 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().size(), 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().size(), 0);
    textInput->paste();
    QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
    QCOMPARE(textInput->text().size(), 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().size());
        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_ACTIVE_FOCUS(textInput);
    QCOMPARE(textInput->text().size(), 12);
    textInput->select(0, textInput->text().size());
    simulateKeys(&window, QKeySequence::Copy);
    QCOMPARE(textInput->selectedText(), QString("Hello world!"));
    QCOMPARE(textInput->selectedText().size(), 12);
    textInput->setCursorPosition(0);
    QVERIFY(textInput->canPaste());
    simulateKeys(&window, QKeySequence::Paste);
    QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
    QCOMPARE(textInput->text().size(), 24);

    // select all and cut
    simulateKeys(&window, QKeySequence::SelectAll);
    simulateKeys(&window, QKeySequence::Cut);
    QCOMPARE(textInput->text().size(), 0);
    simulateKeys(&window, QKeySequence::Paste);
    QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
    QCOMPARE(textInput->text().size(), 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().size());
        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()
{
    if (!PlatformQuirks::isClipboardAvailable())
        QSKIP("This machine has no clipboard support.");

    QGuiApplication::clipboard()->clear();

    const 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);

    const bool cp = !textInput->isReadOnly() && QGuiApplication::clipboard()->text().size() != 0;
    QCOMPARE(textInput->canPaste(), cp);
}
#endif

#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
void tst_qquicktextinput::canPaste()
{
    if (!PlatformQuirks::isClipboardAvailable())
        QSKIP("This machine has no clipboard support.");

    QGuiApplication::clipboard()->setText("Some text");

    const 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);

    const bool cp = !textInput->isReadOnly() && QGuiApplication::clipboard()->text().size() != 0;
    QCOMPARE(textInput->canPaste(), cp);
}
#endif

#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
void tst_qquicktextinput::middleClickPaste()
{
    if (!PlatformQuirks::isClipboardAvailable())
        QSKIP("This machine has no clipboard support.");

    QQuickView window(testFileUrl("mouseselectionmode_default.qml"));

    window.show();
    window.requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(&window));

    QVERIFY(window.rootObject() != nullptr);
    QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(window.rootObject());
    QVERIFY(textInputObject != nullptr);
    textInputObject->setSelectByMouse(true);

    textInputObject->setFocus(true);

    const QString originalText = textInputObject->text();
    const 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.size()), 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().size(); 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(10).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(15).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, textInputObject->mapToGlobal(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(10).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(10).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.size(), 1);

    input.setCursorVisible(false);
    QCOMPARE(input.isCursorVisible(), false);
    QCOMPARE(spy.size(), 2);

    input.setFocus(true);
    QCOMPARE(input.isCursorVisible(), false);
    QCOMPARE(spy.size(), 2);

    input.setParentItem(view.rootObject());
    QCOMPARE(input.isCursorVisible(), true);
    QCOMPARE(spy.size(), 3);

    input.setFocus(false);
    QCOMPARE(input.isCursorVisible(), false);
    QCOMPARE(spy.size(), 4);

    input.setFocus(true);
    QCOMPARE(input.isCursorVisible(), true);
    QCOMPARE(spy.size(), 5);

    QQuickView alternateView;
    alternateView.show();
    alternateView.requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(&alternateView));

    QCOMPARE(input.isCursorVisible(), false);
    QCOMPARE(spy.size(), 6);

    view.requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(&view));
    QCOMPARE(input.isCursorVisible(), true);
    QCOMPARE(spy.size(), 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.size(), 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.size(), 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.size(), 10);

    input.setText("something");
    QCOMPARE(input.isCursorVisible(), true);
    QCOMPARE(spy.size(), 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.size(), 12);

    input.setCursorPosition(5);
    QCOMPARE(input.isCursorVisible(), true);
    QCOMPARE(spy.size(), 13);
}

void tst_qquicktextinput::cursorRectangle_data()
{
    const char16_t arabic_str[] = { 0x0638, 0x0643, 0x0646, 0x0647, 0x0633, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0638, 0x0643, 0x0646, 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.size(); ++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.size() - 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.size(); ++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.size(); ++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.size() - 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.size(), 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_ACTIVE_FOCUS(input);
    QVERIFY(input->isReadOnly());
    QVERIFY(!input->isCursorVisible());
    QString initial = input->text();
    for (int k = Qt::Key_0; k <= Qt::Key_Z; k++)
        QTest::keyClick(&window, Qt::Key(k));
    QTest::keyClick(&window, Qt::Key_Return);
    QTest::keyClick(&window, Qt::Key_Space);
    QTest::keyClick(&window, Qt::Key_Escape);
    QCOMPARE(input->text(), initial);

    input->setCursorPosition(3);
    input->setReadOnly(false);
    QCOMPARE(input->isReadOnly(), false);
    QCOMPARE(input->cursorPosition(), input->text().size());
    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_ACTIVE_FOCUS(input);
    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_ACTIVE_FOCUS(input);
    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_ACTIVE_FOCUS(input);

    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.size(), 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::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.size(), 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.size(), 1);
    QCOMPARE(activeFocusSpy.size(), 1);
    QCOMPARE(textInputObject->selectedText(), QString());
    QTest::mouseRelease(&window, Qt::LeftButton, noModifiers);

    textInputObject->setFocusOnPress(false);
    QCOMPARE(textInputObject->focusOnPress(), false);
    QCOMPARE(activeFocusOnPressSpy.size(), 1);

    textInputObject->setFocus(false);
    QCOMPARE(textInputObject->hasFocus(), false);
    QCOMPARE(textInputObject->hasActiveFocus(), false);
    QCOMPARE(focusSpy.size(), 2);
    QCOMPARE(activeFocusSpy.size(), 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.size(), 2);
    QCOMPARE(activeFocusSpy.size(), 2);
    QTest::mouseRelease(&window, Qt::LeftButton, noModifiers);

    textInputObject->setFocusOnPress(true);
    QCOMPARE(textInputObject->focusOnPress(), true);
    QCOMPARE(activeFocusOnPressSpy.size(), 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.size(), 3);
    QCOMPARE(activeFocusSpy.size(), 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_ACTIVE_FOCUS(second);

    // 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 = {};
    QTest::mousePress(&window, Qt::LeftButton, noModifiers, QPoint(10, 10));

    // make sure the press is processed.
    QGuiApplication::processEvents();

    QVERIFY_ACTIVE_FOCUS(second); // 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 = {};
    QTest::mousePress(&view, Qt::LeftButton, noModifiers);
    QGuiApplication::processEvents();
    QVERIFY_ACTIVE_FOCUS(input);
    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_ACTIVE_FOCUS(input);
    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.size(), 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;
    }
    QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data) override
    {
       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"));

    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.size(), 1);

    textObject->setWrapMode(QQuickTextInput::WordWrap);
    QVERIFY(textObject->contentWidth() <= textObject->width());
    QVERIFY(textObject->contentHeight() > textObject->height());
    QCOMPARE(spy.size(), 2);

    textObject->setText("The quickredfoxjumpedoverthe lazy brown dog");

    QVERIFY(textObject->contentWidth() > textObject->width());
    QVERIFY(textObject->contentHeight() > textObject->height());
    QCOMPARE(spy.size(), 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.size(), 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_ACTIVE_FOCUS(input);

    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.size(), ++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.size(), ++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.size(), ++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.size(), ++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.size() - 3);
    QCOMPARE(cursorRectangleSpy.size(), ++cursorRectangleChanges);
    x = input->positionToRectangle(0).x();
    for (int i = 2; i >= 0; --i) {
        sendPreeditText(input, preeditText, preeditText.size() - i);
        QCOMPARE(input->positionToRectangle(0).x(), x);
        QCOMPARE(cursorRectangleSpy.size(), ++cursorRectangleChanges);
    }
    for (int i = 1; i <  3; ++i) {
        sendPreeditText(input, preeditText, preeditText.size() - i);
        QCOMPARE(input->positionToRectangle(0).x(), x);
        QCOMPARE(cursorRectangleSpy.size(), ++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_ACTIVE_FOCUS(input);

    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.size() > 0); inputSpy.clear();
        QVERIFY(panelSpy.size() > 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.size(), 1);
    QCOMPARE(panelSpy.size(), 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.size(), 1);
    QCOMPARE(panelSpy.size(), 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_ACTIVE_FOCUS(input);
    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.size(), 1);

    {
        QInputMethodEvent event(text.mid(12), QList<QInputMethodEvent::Attribute>());
        QGuiApplication::sendEvent(input, &event);
    }
    QCOMPARE(spy.size(), 1);

    {
        QInputMethodEvent event;
        QGuiApplication::sendEvent(input, &event);
    }
    QCOMPARE(input->isInputMethodComposing(), false);
    QCOMPARE(spy.size(), 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.size(), 2);

    {
        QInputMethodEvent event(text.mid(16), QList<QInputMethodEvent::Attribute>());
        QGuiApplication::sendEvent(input, &event);
    }
    QCOMPARE(input->isInputMethodComposing(), true);
    QCOMPARE(spy.size(), 3);

    // Changing the text while composing cancels composition.
    input->setText(text.mid(0, 12));
    QCOMPARE(input->isInputMethodComposing(), false);
    QCOMPARE(spy.size(), 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.size(), 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.size(), 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.size(), 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.size(), 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.size(), 7);

    {
        QInputMethodEvent event;
        QGuiApplication::sendEvent(input, &event);
    }
    QCOMPARE(input->isInputMethodComposing(), false);
    QCOMPARE(spy.size(), 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_ACTIVE_FOCUS(input);

    // 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_ACTIVE_FOCUS(textInput);

    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).size()
            << 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).size() + 8
            << standard.at(0).mid(23);

    QTest::newRow("plain text cropped beginning and end")
            << standard.at(0)
            << QString()
            << -9 << standard.at(0).size() + 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).size() << standard.at(0).size() << standard.at(0).size()
            << QString("Hello")
            << standard.at(0) + QString("Hello")
            << standard.at(0).size() + 5 << standard.at(0).size() + 5 << standard.at(0).size() + 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).size() << standard.at(0).size() << 18
            << QString("Hello")
            << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
            << standard.at(0).size() + 5 << standard.at(0).size() + 5 << standard.at(0).size() + 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).size()
            << 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).size()
            << 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).size()
            << 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).size() + 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.size() << inputMask.size() << inputMask.size()
            << QString("8")
            << ip
            << inputMask.size() << inputMask.size() << inputMask.size()
            << 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.size() << inputMask.size() << 6
            << QString("75.2")
            << QString("192.167.5.24")
            << inputMask.size() << inputMask.size() << inputMask.size()
            << 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.size() + 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.size() : inputMask.size());

    QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
    QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
    QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);

    if (selectionStart > selectionEnd)
        qSwap(selectionStart, selectionEnd);

    QCOMPARE(selectionSpy.size() > 0, selectionChanged);
    QCOMPARE(selectionStartSpy.size() > 0, selectionStart != expectedSelectionStart);
    QCOMPARE(selectionEndSpy.size() > 0, selectionEnd != expectedSelectionEnd);
    QCOMPARE(textSpy.size() > 0, text != expectedText);
    QCOMPARE(cursorPositionSpy.size() > 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).size() << standard.at(0).size()
            << standard.at(0).size() << standard.at(0).size() - 5
            << standard.at(0).mid(0, standard.at(0).size() - 5)
            << standard.at(0).size() - 5 << standard.at(0).size() - 5 << standard.at(0).size() - 5
            << false << true;

    QTest::newRow("to cursor position (end)")
            << standard.at(0)
            << QString()
            << standard.at(0).size() << standard.at(0).size()
            << standard.at(0).size() - 5 << standard.at(0).size()
            << standard.at(0).mid(0, standard.at(0).size() - 5)
            << standard.at(0).size() - 5 << standard.at(0).size() - 5 << standard.at(0).size() - 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).size() << standard.at(0).size()
            << 18 << 23
            << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
            << standard.at(0).size() - 5 << standard.at(0).size() - 5 << standard.at(0).size() - 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).size() - 5 << standard.at(0).size()
            << standard.at(0).mid(0, standard.at(0).size() - 5)
            << 14 << 19 << 19
            << false << false;

    QTest::newRow("after reversed selection")
            << standard.at(0)
            << QString()
            << 19 << 14
            << standard.at(0).size() - 5 << standard.at(0).size()
            << standard.at(0).mid(0, standard.at(0).size() - 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).size() + 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).size() + 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.size() : inputMask.size());

    if (selectionStart > selectionEnd)  //
        qSwap(selectionStart, selectionEnd);

    QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
    QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
    QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);

    QCOMPARE(selectionSpy.size() > 0, selectionChanged);
    QCOMPARE(selectionStartSpy.size() > 0, selectionStart != expectedSelectionStart);
    QCOMPARE(selectionEndSpy.size() > 0, selectionEnd != expectedSelectionEnd);
    QCOMPARE(textSpy.size() > 0, text != expectedText);

    if (cursorPositionChanged)  //
        QVERIFY(cursorPositionSpy.size() > 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_ACTIVE_FOCUS(textInput);

    QTest::keyClick(&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_ACTIVE_FOCUS(textInput);

    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).size(); j++)
            QTest::keyClick(&window, insertString.at(i).at(j).toLatin1());
    }

    QCOMPARE(spy.size(), 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.size(), 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_ACTIVE_FOCUS(textInput);
    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).size(); j++)
            QTest::keyClick(&window, insertString.at(i).at(j).toLatin1());
        QVERIFY(textInput->canUndo());
        QVERIFY(!textInput->canRedo());
    }

    QCOMPARE(spy.size(), 0);

    // undo everything
    while (!textInput->text().isEmpty()) {
        QVERIFY(textInput->canUndo());
        textInput->undo();
        QVERIFY(textInput->canRedo());
    }

    QCOMPARE(spy.size(), 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.size(), 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_ACTIVE_FOCUS(textInput);

    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_ACTIVE_FOCUS(textInput);
    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.size(), 1);

    // checks that clears can be undone
    textInput->undo();
    QVERIFY(!textInput->canUndo());
    QCOMPARE(spy.size(), 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());
    QVERIFY2(textInput->preeditText().isEmpty(), "Pre-edit text must be empty after clear");

    QCOMPARE(spy.size(), 3);

    // checks that clears can be undone
    textInput->undo();
    QVERIFY(!textInput->canUndo());
    QCOMPARE(spy.size(), 4);
    QCOMPARE(textInput->text(), QString("I am Legend"));
    QVERIFY2(textInput->preeditText().isEmpty(), "Pre-edit text must be empty after undo");
}

void tst_qquicktextinput::backspaceSurrogatePairs()
{
    // Test backspace, and delete remove both characters in a surrogate pair.
    static const char16_t 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.size());

    QQuickWindow window;
    textInput->setParentItem(window.contentItem());
    window.show();
    window.requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(&window));
    QCOMPARE(QGuiApplication::focusWindow(), &window);

    for (int i = text.size(); 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.size(); 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_ACTIVE_FOCUS(input);

    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 QRegularExpression";
    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_ACTIVE_FOCUS(input);

    window.rootObject()->setProperty("regexvalue", QRegularExpression("abc"));
    QCOMPARE(window.rootObject()->property("regexvalue").toRegularExpression(), QRegularExpression("abc"));
    QCOMPARE(window.rootObject()->property("text").toString(), QString("abc"));
    QVERIFY(window.rootObject()->property("acceptableInput").toBool());

    window.rootObject()->setProperty("regexvalue", QRegularExpression("abcd"));
    QCOMPARE(window.rootObject()->property("regexvalue").toRegularExpression(), QRegularExpression("abcd"));
    QVERIFY(!window.rootObject()->property("acceptableInput").toBool());

    window.rootObject()->setProperty("regexvalue", QRegularExpression("abc"));
    QCOMPARE(window.rootObject()->property("regexvalue").toRegularExpression(), QRegularExpression("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.14\nTextInput { focus: true; inputMask: \"" + mask + "\"\n";
    if (!validatorRegExp.isEmpty())
        componentStr += "validator: RegularExpressionValidator { regularExpression: " + 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_ACTIVE_FOCUS(textInput);

    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(QString)));
    textInput->setInputMask(unescapedMask);
    QCOMPARE(spy.size(), 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_ACTIVE_FOCUS(textInput);

        textInput->setCursorPosition(0);
        for (int i = 0; i < input.size(); 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{QKeyCombination(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_ACTIVE_FOCUS(textInput);

    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_ACTIVE_FOCUS(textInput);

    {
        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 override { return input == QStringLiteral("ok") ? Acceptable : Intermediate; }
    void fixup(QString &input) const override { 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_ACTIVE_FOCUS(input);
    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::testCtrlBackspaceClearsSelectedText()
{
    QQuickView window;
    QVERIFY(QQuickTest::showView(window, testFileUrl("openInputPanel.qml")));

    QQuickTextInput *textInput = qmlobject_cast<QQuickTextInput *>(window.rootObject());
    QVERIFY(textInput != nullptr);

    textInput->forceActiveFocus();
    QVERIFY(textInput->hasActiveFocus());

    textInput->selectAll();
    QCOMPARE(textInput->selectedText(), QString("Hello world"));

    QTest::keySequence(&window, QKeySequence::DeleteStartOfWord);

    QCOMPARE(textInput->text(), QString(""));
}

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.size(), 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());
}

void tst_qquicktextinput::checkCursorDelegateWhenPaddingChanged()
{
    QQuickView view;
    view.setSource(testFileUrl("checkCursorDelegateWhenPaddingChanged.qml"));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QQuickTextInput *textInput = view.rootObject()->findChild<QQuickTextInput *>("textInput");
    QVERIFY(textInput);

    QTRY_VERIFY(textInput->findChild<QQuickItem *>("cursorDelegate"));
    QQuickItem *cursorDelegate = textInput->findChild<QQuickItem *>("cursorDelegate");

    QCOMPARE(cursorDelegate->x(), textInput->leftPadding());
    QCOMPARE(cursorDelegate->y(), textInput->topPadding());

    textInput->setPadding(5);
    QCOMPARE(cursorDelegate->x(), textInput->leftPadding());
    QCOMPARE(cursorDelegate->y(), textInput->topPadding());

    textInput->setTopPadding(10);
    QCOMPARE(cursorDelegate->x(), textInput->leftPadding());
    QCOMPARE(cursorDelegate->y(), textInput->topPadding());

    textInput->setLeftPadding(10);
    QCOMPARE(cursorDelegate->x(), textInput->leftPadding());
    QCOMPARE(cursorDelegate->y(), textInput->topPadding());
}

/*!
    Verifies that TextInput items get focus in/out events with the
    correct focus reason set.

    Up and Down keys translates to Backtab and Tab focus reasons.

    See QTBUG-75862.
*/
void tst_qquicktextinput::focusReason()
{
    QQuickView view;
    view.setSource(testFileUrl("focusReason.qml"));

    QQuickTextInput *first = view.rootObject()->findChild<QQuickTextInput *>("first");
    QQuickTextInput *second = view.rootObject()->findChild<QQuickTextInput *>("second");
    QQuickTextInput *third = view.rootObject()->findChild<QQuickTextInput *>("third");
    QVERIFY(first && second && third);

    class FocusEventFilter : public QObject
    {
    public:
        using QObject::QObject;

        QHash<QObject*, Qt::FocusReason> lastFocusReason;
    protected:
        bool eventFilter(QObject *o, QEvent *e) override
        {
            if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) {
                QFocusEvent *fe = static_cast<QFocusEvent*>(e);
                lastFocusReason[o] = fe->reason();
            }
            return QObject::eventFilter(o, e);
        }
    } eventFilter;
    first->installEventFilter(&eventFilter);
    second->installEventFilter(&eventFilter);
    third->installEventFilter(&eventFilter);

    view.show();
    QVERIFY(QTest::qWaitForWindowActive(&view));

    QCOMPARE(qApp->focusObject(), first);
    // on some platforms we don't get ActiveWindowFocusReason; tolerate this,
    // it's not what we are testing in this test
    if (eventFilter.lastFocusReason[first] != Qt::ActiveWindowFocusReason) {
        QEXPECT_FAIL("", qPrintable(QString("No window activation event on the %1 platform")
                                            .arg(QGuiApplication::platformName())),
                     Continue);
    }
    QCOMPARE(eventFilter.lastFocusReason[first], Qt::ActiveWindowFocusReason);

    QTest::mouseClick(&view, Qt::LeftButton, {},
                      (second->boundingRect().center() + second->position()).toPoint());
    QTRY_COMPARE(qApp->focusObject(), second);
    QCOMPARE(eventFilter.lastFocusReason[first], Qt::MouseFocusReason);
    QCOMPARE(eventFilter.lastFocusReason[second], Qt::MouseFocusReason);

    QTest::keyClick(&view, Qt::Key_Tab);
    QCOMPARE(qApp->focusObject(), third);
    QCOMPARE(eventFilter.lastFocusReason[second], Qt::TabFocusReason);
    QCOMPARE(eventFilter.lastFocusReason[third], Qt::TabFocusReason);

    QTest::keyClick(&view, Qt::Key_Backtab);
    QCOMPARE(qApp->focusObject(), second);
    QCOMPARE(eventFilter.lastFocusReason[third], Qt::BacktabFocusReason);
    QCOMPARE(eventFilter.lastFocusReason[second], Qt::BacktabFocusReason);

    QTest::keyClick(&view, Qt::Key_Up);
    QCOMPARE(qApp->focusObject(), first);
    QCOMPARE(eventFilter.lastFocusReason[second], Qt::BacktabFocusReason);
    QCOMPARE(eventFilter.lastFocusReason[first], Qt::BacktabFocusReason);

    QTest::keyClick(&view, Qt::Key_Down);
    QCOMPARE(qApp->focusObject(), second);
    QCOMPARE(eventFilter.lastFocusReason[second], Qt::TabFocusReason);
    QCOMPARE(eventFilter.lastFocusReason[first], Qt::TabFocusReason);
}

void tst_qquicktextinput::touchscreenDoesNotSelect_data()
{
    QTest::addColumn<QUrl>("src");
    QTest::addColumn<bool>("expectDefaultSelectByMouse");
    QTest::addColumn<bool>("overrideSelectByMouseFalse");
    QTest::newRow("new default")  << testFileUrl("mouseselectionmode_default.qml") << true << false;
    QTest::newRow("new override") << testFileUrl("mouseselectionmode_default.qml") << true << true;
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
    QTest::newRow("old default")  << testFileUrl("mouseselection_old_default.qml") << false << false;
#endif
}

void tst_qquicktextinput::touchscreenDoesNotSelect()
{
    QFETCH(QUrl, src);
    QFETCH(bool, expectDefaultSelectByMouse);
    QFETCH(bool, overrideSelectByMouseFalse);

    QQuickView window;
    QVERIFY(QQuickTest::showView(window, src));

    QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(window.rootObject());
    QVERIFY(textInputObject);
    QCOMPARE(textInputObject->selectByMouse(), expectDefaultSelectByMouse);
    if (overrideSelectByMouseFalse)
        textInputObject->setSelectByMouse(overrideSelectByMouseFalse);

    // press-drag-and-release from x1 to x2
    int x1 = 10;
    int x2 = 70;
    int y = textInputObject->height() / 2;
    QTest::touchEvent(&window, touchscreen.get()).press(0, QPoint(x1,y), &window);
    QTest::touchEvent(&window, touchscreen.get()).move(0, QPoint(x2,y), &window);
    QTest::touchEvent(&window, touchscreen.get()).release(0, QPoint(x2,y), &window);
    QQuickTouchUtils::flush(&window);
    QVERIFY(textInputObject->selectedText().isEmpty());

    // select all text (which moves the cursor to the end), then tap:
    // with old API, it deselects, and moves the cursor (QTBUG-116606)
    // with new API, it remains selected, and the cursor remains at the end
    textInputObject->selectAll();
    const int cursorPos = textInputObject->cursorPosition();
    QTest::touchEvent(&window, touchscreen.get()).press(0, QPoint(x2,y), &window);
    QTest::touchEvent(&window, touchscreen.get()).release(0, QPoint(x2,y), &window);
    QQuickTouchUtils::flush(&window);
    QCOMPARE(textInputObject->selectedText().isEmpty(), true);
    QCOMPARE_NE(textInputObject->cursorPosition(), cursorPos);
    QVERIFY(textInputObject->selectedText().isEmpty());
}

void tst_qquicktextinput::touchscreenSetsFocusAndMovesCursor()
{
    SKIP_IF_NO_WINDOW_ACTIVATION;

    QQuickView window;
    QVERIFY(QQuickTest::showView(window, testFileUrl("twoInAColumn.qml")));
    window.requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(&window));

    QQuickTextInput *top = window.rootObject()->findChild<QQuickTextInput*>("top");
    QVERIFY(top);
    QQuickTextInput *bottom = window.rootObject()->findChild<QQuickTextInput*>("bottom");
    QVERIFY(bottom);

    // tap the bottom field
    int x1 = 10;
    int y = bottom->position().y() + bottom->height() / 2;
    QTest::touchEvent(&window, touchscreen.get()).press(0, QPoint(x1,y), &window);
    QQuickTouchUtils::flush(&window);
    QCOMPARE(qApp->focusObject(), bottom);
    // text cursor is at the end by default, on press
    const auto len = bottom->text().size();
    QCOMPARE(bottom->cursorPosition(), len);
    // so typing a character appends it
    QVERIFY(!bottom->text().endsWith('q'));
    QTest::keyClick(&window, Qt::Key_Q);
    QVERIFY(bottom->text().endsWith('q'));
    QCOMPARE(bottom->text().size(), len + 1);
    QTest::touchEvent(&window, touchscreen.get()).release(0, QPoint(x1,y), &window);
    QQuickTouchUtils::flush(&window);
    // the cursor gets moved on release, as long as TextInput's grab wasn't stolen (e.g. by Flickable)
    QVERIFY(bottom->cursorPosition() < 5);

    // press-drag-and-release from x1 to x2 on the top field
    int x2 = 70;
    y = top->position().y() + top->height() / 2;
    QTest::touchEvent(&window, touchscreen.get()).press(0, QPoint(x1,y), &window);
    QTest::touchEvent(&window, touchscreen.get()).move(0, QPoint(x2,y), &window);
    QTest::touchEvent(&window, touchscreen.get()).release(0, QPoint(x2,y), &window);
    QQuickTouchUtils::flush(&window);
    QCOMPARE(qApp->focusObject(), top);
    QVERIFY(top->selectedText().isEmpty());
}

void tst_qquicktextinput::imeSelectionWithCommitMovesCursor_NoMask()
{
    // QTBUG-140400: Selection (len 0) must move cursor even when commit is present.
    QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; text: \"abcdef\" }";
    QQmlComponent comp(&engine);
    comp.setData(componentStr.toLatin1(), QUrl());
    QQuickTextInput *ti = qobject_cast<QQuickTextInput*>(comp.create());
    QVERIFY(ti);

    QQuickWindow win;
    ti->setParentItem(win.contentItem());
    win.show();
    QVERIFY(QTest::qWaitForWindowActive(&win));
    QVERIFY_ACTIVE_FOCUS(ti);

    ti->setCursorPosition(3);
    QCOMPARE(ti->cursorPosition(), 3);

    // Commit + Selection(start=4, length=0) in the same IME event.
    {
        QList<QInputMethodEvent::Attribute> attrs;
        attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 4, 0, QVariant());
        QInputMethodEvent ev(QString(), attrs);
        ev.setCommitString("HELLO");
        QGuiApplication::sendEvent(ti, &ev);
    }

    // Commit happened at old cursor (-> abcHELLOdef), then Selection moved cursor to 4.
    QCOMPARE(ti->text(), QStringLiteral("abcHELLOdef"));
    QCOMPARE(ti->selectionStart(), ti->selectionEnd());
    QCOMPARE(ti->cursorPosition(), 4);
}

void tst_qquicktextinput::imeSelectionWithCommitDoesNotOverride_WithMask()
{
    // Preserve QTBUG-94253 behavior: with input mask, Selection must not override
    // cursor set by internalInsert() when a commit is present.
    QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"AA.AA.AA\" }";
    QQmlComponent comp(&engine);
    comp.setData(componentStr.toLatin1(), QUrl());
    QQuickTextInput *ti = qobject_cast<QQuickTextInput*>(comp.create());
    QVERIFY(ti);

    QQuickWindow win;
    ti->setParentItem(win.contentItem());
    win.show();
    QVERIFY(QTest::qWaitForWindowActive(&win));
    QVERIFY_ACTIVE_FOCUS(ti);

    // One IME event: commit + Selection(start: 4, length: 0). Selection start must NOT override the cursor.
    {
        QList<QInputMethodEvent::Attribute> attrs;
        attrs << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 4, 0, QVariant());
        QInputMethodEvent ev(QString(), attrs);
        ev.setCommitString("HELLO");
        QGuiApplication::sendEvent(ti, &ev);
    }

    // Matches existing mask expectations (cursor at the end after commit string)
    QCOMPARE(ti->text(), QStringLiteral("HE.LL.O"));
    QCOMPARE(ti->cursorPosition(), ti->text().size());
}

QTEST_MAIN(tst_qquicktextinput)

#include "tst_qquicktextinput.moc"
