1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the test suite of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include <QtTest/QSignalSpy>
43 #include "../../shared/util.h"
44 #include <private/qinputmethod_p.h>
45 #include <QtQml/qqmlengine.h>
46 #include <QtQml/qqmlexpression.h>
48 #include <QtQuick/qquickview.h>
49 #include <QtGui/qguiapplication.h>
50 #include <QtGui/qstylehints.h>
51 #include <QInputMethod>
52 #include <private/qquicktextinput_p.h>
53 #include <private/qquicktextinput_p_p.h>
59 #include <Carbon/Carbon.h>
62 #include "qplatformdefs.h"
63 #include "../../shared/platforminputcontext.h"
65 Q_DECLARE_METATYPE(QQuickTextInput::SelectionMode)
66 Q_DECLARE_METATYPE(QQuickTextInput::EchoMode)
67 Q_DECLARE_METATYPE(Qt::Key)
69 DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
71 QString createExpectedFileIfNotFound(const QString& filebasename, const QImage& actual)
73 // XXX This will be replaced by some clever persistent platform image store.
74 QString persistent_dir = QQmlDataTest::instance()->dataDirectory();
75 QString arch = "unknown-architecture"; // QTest needs to help with this.
77 QString expectfile = persistent_dir + QDir::separator() + filebasename + "-" + arch + ".png";
79 if (!QFile::exists(expectfile)) {
80 actual.save(expectfile);
81 qWarning() << "created" << expectfile;
87 template <typename T> static T evaluate(QObject *scope, const QString &expression)
89 QQmlExpression expr(qmlContext(scope), scope, expression);
90 T result = expr.evaluate().value<T>();
92 qWarning() << expr.error().toString();
96 template<typename T, int N> int lengthOf(const T (&)[N]) { return N; }
98 typedef QPair<int, QChar> Key;
100 class tst_qquicktextinput : public QQmlDataTest
105 tst_qquicktextinput();
115 void persistentSelection();
116 void isRightToLeft_data();
117 void isRightToLeft();
118 void moveCursorSelection_data();
119 void moveCursorSelection();
120 void moveCursorSelectionSequence_data();
121 void moveCursorSelectionSequence();
122 void dragMouseSelection();
123 void mouseSelectionMode_data();
124 void mouseSelectionMode();
125 void tripleClickSelectsAll();
127 void horizontalAlignment_data();
128 void horizontalAlignment();
129 void horizontalAlignment_RightToLeft();
130 void verticalAlignment();
142 void passwordCharacter();
143 void cursorDelegate_data();
144 void cursorDelegate();
145 void cursorVisible();
146 void cursorRectangle_data();
147 void cursorRectangle();
149 void navigation_RTL();
151 void copyAndPasteKeySequence();
152 void canPasteEmpty();
157 void openInputPanel();
158 void setHAlignClearCache();
159 void focusOutClearSelection();
162 void passwordEchoDelay();
163 void geometrySignals();
166 void preeditAutoScroll();
167 void preeditCursorRectangle();
168 void inputContextMouseHandler();
169 void inputMethodComposing();
170 void inputMethodUpdate();
171 void cursorRectangleSize();
180 void keySequence_data();
187 void undo_keypressevents_data();
188 void undo_keypressevents();
191 void QTBUG_19956_data();
192 void QTBUG_19956_regexp();
194 void implicitSize_data();
196 void implicitSizeBinding_data();
197 void implicitSizeBinding();
199 void negativeDimensions();
202 void setInputMask_data();
204 void inputMask_data();
206 void clearInputMask();
207 void keypress_inputMask_data();
208 void keypress_inputMask();
209 void hasAcceptableInputMask_data();
210 void hasAcceptableInputMask();
211 void maskCharacter_data();
212 void maskCharacter();
215 void simulateKey(QWindow *, int key);
217 void simulateKeys(QWindow *window, const QList<Key> &keys);
218 void simulateKeys(QWindow *window, const QKeySequence &sequence);
221 QStringList standard;
222 QStringList colorStrings;
225 typedef QList<int> IntList;
226 Q_DECLARE_METATYPE(IntList)
228 typedef QList<Key> KeyList;
229 Q_DECLARE_METATYPE(KeyList)
231 void tst_qquicktextinput::simulateKeys(QWindow *window, const QList<Key> &keys)
233 for (int i = 0; i < keys.count(); ++i) {
234 const int key = keys.at(i).first;
235 const int modifiers = key & Qt::KeyboardModifierMask;
236 const QString text = !keys.at(i).second.isNull() ? QString(keys.at(i).second) : QString();
238 QKeyEvent press(QEvent::KeyPress, Qt::Key(key), Qt::KeyboardModifiers(modifiers), text);
239 QKeyEvent release(QEvent::KeyRelease, Qt::Key(key), Qt::KeyboardModifiers(modifiers), text);
241 QGuiApplication::sendEvent(window, &press);
242 QGuiApplication::sendEvent(window, &release);
246 void tst_qquicktextinput::simulateKeys(QWindow *window, const QKeySequence &sequence)
248 for (int i = 0; i < sequence.count(); ++i) {
249 const int key = sequence[i];
250 const int modifiers = key & Qt::KeyboardModifierMask;
252 QTest::keyClick(window, Qt::Key(key & ~modifiers), Qt::KeyboardModifiers(modifiers));
256 QList<Key> &operator <<(QList<Key> &keys, const QKeySequence &sequence)
258 for (int i = 0; i < sequence.count(); ++i)
259 keys << Key(sequence[i], QChar());
263 template <int N> QList<Key> &operator <<(QList<Key> &keys, const char (&characters)[N])
265 for (int i = 0; i < N - 1; ++i) {
266 int key = QTest::asciiToKey(characters[i]);
267 QChar character = QLatin1Char(characters[i]);
268 keys << Key(key, character);
273 QList<Key> &operator <<(QList<Key> &keys, Qt::Key key)
275 keys << Key(key, QChar());
279 void tst_qquicktextinput::cleanup()
281 // ensure not even skipped tests with custom input context leave it dangling
282 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
283 inputMethodPrivate->testContext = 0;
286 tst_qquicktextinput::tst_qquicktextinput()
288 standard << "the quick brown fox jumped over the lazy dog"
289 << "It's supercalifragisiticexpialidocious!"
294 colorStrings << "aliceblue"
308 void tst_qquicktextinput::text()
311 QQmlComponent textinputComponent(&engine);
312 textinputComponent.setData("import QtQuick 2.0\nTextInput { text: \"\" }", QUrl());
313 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
315 QVERIFY(textinputObject != 0);
316 QCOMPARE(textinputObject->text(), QString(""));
317 QCOMPARE(textinputObject->length(), 0);
319 delete textinputObject;
322 for (int i = 0; i < standard.size(); i++)
324 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
325 QQmlComponent textinputComponent(&engine);
326 textinputComponent.setData(componentStr.toLatin1(), QUrl());
327 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
329 QVERIFY(textinputObject != 0);
330 QCOMPARE(textinputObject->text(), standard.at(i));
331 QCOMPARE(textinputObject->length(), standard.at(i).length());
333 delete textinputObject;
338 void tst_qquicktextinput::width()
340 // uses Font metrics to find the width for standard
342 QQmlComponent textinputComponent(&engine);
343 textinputComponent.setData("import QtQuick 2.0\nTextInput { text: \"\" }", QUrl());
344 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
346 QVERIFY(textinputObject != 0);
347 QCOMPARE(textinputObject->width(), 0.0);
349 delete textinputObject;
352 bool requiresUnhintedMetrics = !qmlDisableDistanceField();
354 for (int i = 0; i < standard.size(); i++)
356 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
357 QQmlComponent textinputComponent(&engine);
358 textinputComponent.setData(componentStr.toLatin1(), QUrl());
359 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
361 QString s = standard.at(i);
362 s.replace(QLatin1Char('\n'), QChar::LineSeparator);
364 QTextLayout layout(s);
365 layout.setFont(textinputObject->font());
366 layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
367 if (requiresUnhintedMetrics) {
369 option.setUseDesignMetrics(true);
370 layout.setTextOption(option);
373 layout.beginLayout();
375 QTextLine line = layout.createLine();
382 qreal metricWidth = ceil(layout.boundingRect().width());
384 QVERIFY(textinputObject != 0);
385 int delta = abs(int(int(textinputObject->width()) - metricWidth));
386 QVERIFY(delta <= 3.0); // As best as we can hope for cross-platform.
388 delete textinputObject;
392 void tst_qquicktextinput::font()
394 //test size, then bold, then italic, then family
396 QString componentStr = "import QtQuick 2.0\nTextInput { font.pointSize: 40; text: \"Hello World\" }";
397 QQmlComponent textinputComponent(&engine);
398 textinputComponent.setData(componentStr.toLatin1(), QUrl());
399 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
401 QVERIFY(textinputObject != 0);
402 QCOMPARE(textinputObject->font().pointSize(), 40);
403 QCOMPARE(textinputObject->font().bold(), false);
404 QCOMPARE(textinputObject->font().italic(), false);
406 delete textinputObject;
410 QString componentStr = "import QtQuick 2.0\nTextInput { font.bold: true; text: \"Hello World\" }";
411 QQmlComponent textinputComponent(&engine);
412 textinputComponent.setData(componentStr.toLatin1(), QUrl());
413 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
415 QVERIFY(textinputObject != 0);
416 QCOMPARE(textinputObject->font().bold(), true);
417 QCOMPARE(textinputObject->font().italic(), false);
419 delete textinputObject;
423 QString componentStr = "import QtQuick 2.0\nTextInput { font.italic: true; text: \"Hello World\" }";
424 QQmlComponent textinputComponent(&engine);
425 textinputComponent.setData(componentStr.toLatin1(), QUrl());
426 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
428 QVERIFY(textinputObject != 0);
429 QCOMPARE(textinputObject->font().italic(), true);
430 QCOMPARE(textinputObject->font().bold(), false);
432 delete textinputObject;
436 QString componentStr = "import QtQuick 2.0\nTextInput { font.family: \"Helvetica\"; text: \"Hello World\" }";
437 QQmlComponent textinputComponent(&engine);
438 textinputComponent.setData(componentStr.toLatin1(), QUrl());
439 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
441 QVERIFY(textinputObject != 0);
442 QCOMPARE(textinputObject->font().family(), QString("Helvetica"));
443 QCOMPARE(textinputObject->font().bold(), false);
444 QCOMPARE(textinputObject->font().italic(), false);
446 delete textinputObject;
450 QString componentStr = "import QtQuick 2.0\nTextInput { font.family: \"\"; text: \"Hello World\" }";
451 QQmlComponent textinputComponent(&engine);
452 textinputComponent.setData(componentStr.toLatin1(), QUrl());
453 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
455 QVERIFY(textinputObject != 0);
456 QCOMPARE(textinputObject->font().family(), QString(""));
458 delete textinputObject;
462 void tst_qquicktextinput::color()
465 for (int i = 0; i < colorStrings.size(); i++)
467 QString componentStr = "import QtQuick 2.0\nTextInput { color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
468 QQmlComponent textinputComponent(&engine);
469 textinputComponent.setData(componentStr.toLatin1(), QUrl());
470 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
471 QVERIFY(textinputObject != 0);
472 QCOMPARE(textinputObject->color(), QColor(colorStrings.at(i)));
474 delete textinputObject;
477 //test selection color
478 for (int i = 0; i < colorStrings.size(); i++)
480 QString componentStr = "import QtQuick 2.0\nTextInput { selectionColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
481 QQmlComponent textinputComponent(&engine);
482 textinputComponent.setData(componentStr.toLatin1(), QUrl());
483 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
484 QVERIFY(textinputObject != 0);
485 QCOMPARE(textinputObject->selectionColor(), QColor(colorStrings.at(i)));
487 delete textinputObject;
490 //test selected text color
491 for (int i = 0; i < colorStrings.size(); i++)
493 QString componentStr = "import QtQuick 2.0\nTextInput { selectedTextColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
494 QQmlComponent textinputComponent(&engine);
495 textinputComponent.setData(componentStr.toLatin1(), QUrl());
496 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
497 QVERIFY(textinputObject != 0);
498 QCOMPARE(textinputObject->selectedTextColor(), QColor(colorStrings.at(i)));
500 delete textinputObject;
504 QString colorStr = "#AA001234";
505 QColor testColor("#001234");
506 testColor.setAlpha(170);
508 QString componentStr = "import QtQuick 2.0\nTextInput { color: \"" + colorStr + "\"; text: \"Hello World\" }";
509 QQmlComponent textinputComponent(&engine);
510 textinputComponent.setData(componentStr.toLatin1(), QUrl());
511 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
513 QVERIFY(textinputObject != 0);
514 QCOMPARE(textinputObject->color(), testColor);
516 delete textinputObject;
520 void tst_qquicktextinput::wrap()
523 // for specified width and wrap set true
525 QQmlComponent textComponent(&engine);
526 textComponent.setData("import QtQuick 2.0\nTextInput { text: \"Hello\"; wrapMode: Text.WrapAnywhere; width: 300 }", QUrl::fromLocalFile(""));
527 QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(textComponent.create());
528 textHeight = textObject->height();
530 QVERIFY(textObject != 0);
531 QVERIFY(textObject->wrapMode() == QQuickTextInput::WrapAnywhere);
532 QCOMPARE(textObject->width(), 300.);
537 for (int i = 0; i < standard.count(); i++) {
538 QString componentStr = "import QtQuick 2.0\nTextInput { wrapMode: Text.WrapAnywhere; width: 30; text: \"" + standard.at(i) + "\" }";
539 QQmlComponent textComponent(&engine);
540 textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
541 QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(textComponent.create());
543 QVERIFY(textObject != 0);
544 QCOMPARE(textObject->width(), 30.);
545 QVERIFY(textObject->height() > textHeight);
547 int oldHeight = textObject->height();
548 textObject->setWidth(100);
549 QVERIFY(textObject->height() < oldHeight);
555 void tst_qquicktextinput::selection()
557 QString testStr = standard[0];
558 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
559 QQmlComponent textinputComponent(&engine);
560 textinputComponent.setData(componentStr.toLatin1(), QUrl());
561 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
562 QVERIFY(textinputObject != 0);
565 //Test selection follows cursor
566 for (int i=0; i<= testStr.size(); i++) {
567 textinputObject->setCursorPosition(i);
568 QCOMPARE(textinputObject->cursorPosition(), i);
569 QCOMPARE(textinputObject->selectionStart(), i);
570 QCOMPARE(textinputObject->selectionEnd(), i);
571 QVERIFY(textinputObject->selectedText().isNull());
574 textinputObject->setCursorPosition(0);
575 QVERIFY(textinputObject->cursorPosition() == 0);
576 QVERIFY(textinputObject->selectionStart() == 0);
577 QVERIFY(textinputObject->selectionEnd() == 0);
578 QVERIFY(textinputObject->selectedText().isNull());
580 // Verify invalid positions are ignored.
581 textinputObject->setCursorPosition(-1);
582 QVERIFY(textinputObject->cursorPosition() == 0);
583 QVERIFY(textinputObject->selectionStart() == 0);
584 QVERIFY(textinputObject->selectionEnd() == 0);
585 QVERIFY(textinputObject->selectedText().isNull());
587 textinputObject->setCursorPosition(textinputObject->text().count()+1);
588 QVERIFY(textinputObject->cursorPosition() == 0);
589 QVERIFY(textinputObject->selectionStart() == 0);
590 QVERIFY(textinputObject->selectionEnd() == 0);
591 QVERIFY(textinputObject->selectedText().isNull());
594 for (int i=0; i<= testStr.size(); i++) {
595 textinputObject->select(0,i);
596 QCOMPARE(testStr.mid(0,i), textinputObject->selectedText());
598 for (int i=0; i<= testStr.size(); i++) {
599 textinputObject->select(i,testStr.size());
600 QCOMPARE(testStr.mid(i,testStr.size()-i), textinputObject->selectedText());
603 textinputObject->setCursorPosition(0);
604 QVERIFY(textinputObject->cursorPosition() == 0);
605 QVERIFY(textinputObject->selectionStart() == 0);
606 QVERIFY(textinputObject->selectionEnd() == 0);
607 QVERIFY(textinputObject->selectedText().isNull());
609 //Test Error Ignoring behaviour
610 textinputObject->setCursorPosition(0);
611 QVERIFY(textinputObject->selectedText().isNull());
612 textinputObject->select(-10,0);
613 QVERIFY(textinputObject->selectedText().isNull());
614 textinputObject->select(100,110);
615 QVERIFY(textinputObject->selectedText().isNull());
616 textinputObject->select(0,-10);
617 QVERIFY(textinputObject->selectedText().isNull());
618 textinputObject->select(0,100);
619 QVERIFY(textinputObject->selectedText().isNull());
620 textinputObject->select(0,10);
621 QVERIFY(textinputObject->selectedText().size() == 10);
622 textinputObject->select(-10,10);
623 QVERIFY(textinputObject->selectedText().size() == 10);
624 textinputObject->select(100,101);
625 QVERIFY(textinputObject->selectedText().size() == 10);
626 textinputObject->select(0,-10);
627 QVERIFY(textinputObject->selectedText().size() == 10);
628 textinputObject->select(0,100);
629 QVERIFY(textinputObject->selectedText().size() == 10);
631 textinputObject->deselect();
632 QVERIFY(textinputObject->selectedText().isNull());
633 textinputObject->select(0,10);
634 QVERIFY(textinputObject->selectedText().size() == 10);
635 textinputObject->deselect();
636 QVERIFY(textinputObject->selectedText().isNull());
638 // test input method selection
639 QSignalSpy selectionSpy(textinputObject, SIGNAL(selectedTextChanged()));
640 textinputObject->setFocus(true);
642 QList<QInputMethodEvent::Attribute> attributes;
643 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 12, 5, QVariant());
644 QInputMethodEvent event("", attributes);
645 QGuiApplication::sendEvent(textinputObject, &event);
647 QCOMPARE(selectionSpy.count(), 1);
648 QCOMPARE(textinputObject->selectionStart(), 12);
649 QCOMPARE(textinputObject->selectionEnd(), 17);
651 delete textinputObject;
654 void tst_qquicktextinput::persistentSelection()
656 QQuickView canvas(testFileUrl("persistentSelection.qml"));
658 canvas.requestActivateWindow();
659 QTest::qWaitForWindowShown(&canvas);
660 QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
661 canvas.requestActivateWindow();
663 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(canvas.rootObject());
665 QVERIFY(input->hasActiveFocus());
667 QSignalSpy spy(input, SIGNAL(persistentSelectionChanged()));
669 QCOMPARE(input->persistentSelection(), false);
671 input->setPersistentSelection(false);
672 QCOMPARE(input->persistentSelection(), false);
673 QCOMPARE(spy.count(), 0);
676 QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
678 input->setFocus(false);
679 QCOMPARE(input->property("selected").toString(), QString());
681 input->setFocus(true);
682 QCOMPARE(input->property("selected").toString(), QString());
684 input->setPersistentSelection(true);
685 QCOMPARE(input->persistentSelection(), true);
686 QCOMPARE(spy.count(), 1);
689 QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
691 input->setFocus(false);
692 QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
694 input->setFocus(true);
695 QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
698 void tst_qquicktextinput::isRightToLeft_data()
700 QTest::addColumn<QString>("text");
701 QTest::addColumn<bool>("emptyString");
702 QTest::addColumn<bool>("firstCharacter");
703 QTest::addColumn<bool>("lastCharacter");
704 QTest::addColumn<bool>("middleCharacter");
705 QTest::addColumn<bool>("startString");
706 QTest::addColumn<bool>("midString");
707 QTest::addColumn<bool>("endString");
709 const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
710 QTest::newRow("Empty") << "" << false << false << false << false << false << false << false;
711 QTest::newRow("Neutral") << "23244242" << false << false << false << false << false << false << false;
712 QTest::newRow("LTR") << "Hello world" << false << false << false << false << false << false << false;
713 QTest::newRow("RTL") << QString::fromUtf16(arabic_str, 11) << false << true << true << true << true << true << true;
714 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;
715 QTest::newRow("Bidi LTR + RTL + LTR") << QString("Hello world") + QString::fromUtf16(arabic_str, 11) + QString("Hello world") << false << false << false << true << false << false << false;
718 void tst_qquicktextinput::isRightToLeft()
720 QFETCH(QString, text);
721 QFETCH(bool, emptyString);
722 QFETCH(bool, firstCharacter);
723 QFETCH(bool, lastCharacter);
724 QFETCH(bool, middleCharacter);
725 QFETCH(bool, startString);
726 QFETCH(bool, midString);
727 QFETCH(bool, endString);
729 QQuickTextInput textInput;
730 textInput.setText(text);
732 // first test that the right string is delivered to the QString::isRightToLeft()
733 QCOMPARE(textInput.isRightToLeft(0,0), text.mid(0,0).isRightToLeft());
734 QCOMPARE(textInput.isRightToLeft(0,1), text.mid(0,1).isRightToLeft());
735 QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), text.mid(text.count()-2, text.count()-1).isRightToLeft());
736 QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), text.mid(text.count()/2, text.count()/2 + 1).isRightToLeft());
737 QCOMPARE(textInput.isRightToLeft(0,text.count()/4), text.mid(0,text.count()/4).isRightToLeft());
738 QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), text.mid(text.count()/4,3*text.count()/4).isRightToLeft());
740 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
741 QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), text.mid(3*text.count()/4,text.count()-1).isRightToLeft());
743 // then test that the feature actually works
744 QCOMPARE(textInput.isRightToLeft(0,0), emptyString);
745 QCOMPARE(textInput.isRightToLeft(0,1), firstCharacter);
746 QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), lastCharacter);
747 QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), middleCharacter);
748 QCOMPARE(textInput.isRightToLeft(0,text.count()/4), startString);
749 QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), midString);
751 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
752 QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), endString);
755 void tst_qquicktextinput::moveCursorSelection_data()
757 QTest::addColumn<QString>("testStr");
758 QTest::addColumn<int>("cursorPosition");
759 QTest::addColumn<int>("movePosition");
760 QTest::addColumn<QQuickTextInput::SelectionMode>("mode");
761 QTest::addColumn<int>("selectionStart");
762 QTest::addColumn<int>("selectionEnd");
763 QTest::addColumn<bool>("reversible");
765 // () contains the text selected by the cursor.
766 // <> contains the actual selection.
768 QTest::newRow("(t)he|characters")
769 << standard[0] << 0 << 1 << QQuickTextInput::SelectCharacters << 0 << 1 << true;
770 QTest::newRow("do(g)|characters")
771 << standard[0] << 43 << 44 << QQuickTextInput::SelectCharacters << 43 << 44 << true;
772 QTest::newRow("jum(p)ed|characters")
773 << standard[0] << 23 << 24 << QQuickTextInput::SelectCharacters << 23 << 24 << true;
774 QTest::newRow("jumped( )over|characters")
775 << standard[0] << 26 << 27 << QQuickTextInput::SelectCharacters << 26 << 27 << true;
776 QTest::newRow("(the )|characters")
777 << standard[0] << 0 << 4 << QQuickTextInput::SelectCharacters << 0 << 4 << true;
778 QTest::newRow("( dog)|characters")
779 << standard[0] << 40 << 44 << QQuickTextInput::SelectCharacters << 40 << 44 << true;
780 QTest::newRow("( jumped )|characters")
781 << standard[0] << 19 << 27 << QQuickTextInput::SelectCharacters << 19 << 27 << true;
782 QTest::newRow("th(e qu)ick|characters")
783 << standard[0] << 2 << 6 << QQuickTextInput::SelectCharacters << 2 << 6 << true;
784 QTest::newRow("la(zy d)og|characters")
785 << standard[0] << 38 << 42 << QQuickTextInput::SelectCharacters << 38 << 42 << true;
786 QTest::newRow("jum(ped ov)er|characters")
787 << standard[0] << 23 << 29 << QQuickTextInput::SelectCharacters << 23 << 29 << true;
788 QTest::newRow("()the|characters")
789 << standard[0] << 0 << 0 << QQuickTextInput::SelectCharacters << 0 << 0 << true;
790 QTest::newRow("dog()|characters")
791 << standard[0] << 44 << 44 << QQuickTextInput::SelectCharacters << 44 << 44 << true;
792 QTest::newRow("jum()ped|characters")
793 << standard[0] << 23 << 23 << QQuickTextInput::SelectCharacters << 23 << 23 << true;
795 QTest::newRow("<(t)he>|words")
796 << standard[0] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 3 << true;
797 QTest::newRow("<do(g)>|words")
798 << standard[0] << 43 << 44 << QQuickTextInput::SelectWords << 41 << 44 << true;
799 QTest::newRow("<jum(p)ed>|words")
800 << standard[0] << 23 << 24 << QQuickTextInput::SelectWords << 20 << 26 << true;
801 QTest::newRow("<jumped( )>over|words,ltr")
802 << standard[0] << 26 << 27 << QQuickTextInput::SelectWords << 20 << 27 << false;
803 QTest::newRow("jumped<( )over>|words,rtl")
804 << standard[0] << 27 << 26 << QQuickTextInput::SelectWords << 26 << 31 << false;
805 QTest::newRow("<(the )>quick|words,ltr")
806 << standard[0] << 0 << 4 << QQuickTextInput::SelectWords << 0 << 4 << false;
807 QTest::newRow("<(the )quick>|words,rtl")
808 << standard[0] << 4 << 0 << QQuickTextInput::SelectWords << 0 << 9 << false;
809 QTest::newRow("<lazy( dog)>|words,ltr")
810 << standard[0] << 40 << 44 << QQuickTextInput::SelectWords << 36 << 44 << false;
811 QTest::newRow("lazy<( dog)>|words,rtl")
812 << standard[0] << 44 << 40 << QQuickTextInput::SelectWords << 40 << 44 << false;
813 QTest::newRow("<fox( jumped )>over|words,ltr")
814 << standard[0] << 19 << 27 << QQuickTextInput::SelectWords << 16 << 27 << false;
815 QTest::newRow("fox<( jumped )over>|words,rtl")
816 << standard[0] << 27 << 19 << QQuickTextInput::SelectWords << 19 << 31 << false;
817 QTest::newRow("<th(e qu)ick>|words")
818 << standard[0] << 2 << 6 << QQuickTextInput::SelectWords << 0 << 9 << true;
819 QTest::newRow("<la(zy d)og|words>")
820 << standard[0] << 38 << 42 << QQuickTextInput::SelectWords << 36 << 44 << true;
821 QTest::newRow("<jum(ped ov)er>|words")
822 << standard[0] << 23 << 29 << QQuickTextInput::SelectWords << 20 << 31 << true;
823 QTest::newRow("<()>the|words")
824 << standard[0] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << true;
825 QTest::newRow("dog<()>|words")
826 << standard[0] << 44 << 44 << QQuickTextInput::SelectWords << 44 << 44 << true;
827 QTest::newRow("jum<()>ped|words")
828 << standard[0] << 23 << 23 << QQuickTextInput::SelectWords << 23 << 23 << true;
830 QTest::newRow("Hello<(,)> |words")
831 << standard[2] << 5 << 6 << QQuickTextInput::SelectWords << 5 << 6 << true;
832 QTest::newRow("Hello<(, )>world|words,ltr")
833 << standard[2] << 5 << 7 << QQuickTextInput::SelectWords << 5 << 7 << false;
834 QTest::newRow("Hello<(, )world>|words,rtl")
835 << standard[2] << 7 << 5 << QQuickTextInput::SelectWords << 5 << 12 << false;
836 QTest::newRow("<Hel(lo, )>world|words,ltr")
837 << standard[2] << 3 << 7 << QQuickTextInput::SelectWords << 0 << 7 << false;
838 QTest::newRow("<Hel(lo, )world>|words,rtl")
839 << standard[2] << 7 << 3 << QQuickTextInput::SelectWords << 0 << 12 << false;
840 QTest::newRow("<Hel(lo)>,|words")
841 << standard[2] << 3 << 5 << QQuickTextInput::SelectWords << 0 << 5 << true;
842 QTest::newRow("Hello<()>,|words")
843 << standard[2] << 5 << 5 << QQuickTextInput::SelectWords << 5 << 5 << true;
844 QTest::newRow("Hello,<()>|words")
845 << standard[2] << 6 << 6 << QQuickTextInput::SelectWords << 6 << 6 << true;
846 QTest::newRow("Hello<,( )>world|words,ltr")
847 << standard[2] << 6 << 7 << QQuickTextInput::SelectWords << 5 << 7 << false;
848 QTest::newRow("Hello,<( )world>|words,rtl")
849 << standard[2] << 7 << 6 << QQuickTextInput::SelectWords << 6 << 12 << false;
850 QTest::newRow("Hello<,( world)>|words,ltr")
851 << standard[2] << 6 << 12 << QQuickTextInput::SelectWords << 5 << 12 << false;
852 QTest::newRow("Hello,<( world)>|words,rtl")
853 << standard[2] << 12 << 6 << QQuickTextInput::SelectWords << 6 << 12 << false;
854 QTest::newRow("Hello<,( world!)>|words,ltr")
855 << standard[2] << 6 << 13 << QQuickTextInput::SelectWords << 5 << 13 << false;
856 QTest::newRow("Hello,<( world!)>|words,rtl")
857 << standard[2] << 13 << 6 << QQuickTextInput::SelectWords << 6 << 13 << false;
858 QTest::newRow("Hello<(, world!)>|words")
859 << standard[2] << 5 << 13 << QQuickTextInput::SelectWords << 5 << 13 << true;
860 // Fails due to an issue with QTextBoundaryFinder and punctuation at the end of strings.
862 // QTest::newRow("world<(!)>|words")
863 // << standard[2] << 12 << 13 << QQuickTextInput::SelectWords << 12 << 13 << true;
864 QTest::newRow("world!<()>)|words")
865 << standard[2] << 13 << 13 << QQuickTextInput::SelectWords << 13 << 13 << true;
866 QTest::newRow("world<()>!)|words")
867 << standard[2] << 12 << 12 << QQuickTextInput::SelectWords << 12 << 12 << true;
869 QTest::newRow("<(,)>olleH |words")
870 << standard[3] << 7 << 8 << QQuickTextInput::SelectWords << 7 << 8 << true;
871 QTest::newRow("<dlrow( ,)>olleH|words,ltr")
872 << standard[3] << 6 << 8 << QQuickTextInput::SelectWords << 1 << 8 << false;
873 QTest::newRow("dlrow<( ,)>olleH|words,rtl")
874 << standard[3] << 8 << 6 << QQuickTextInput::SelectWords << 6 << 8 << false;
875 QTest::newRow("<dlrow( ,ol)leH>|words,ltr")
876 << standard[3] << 6 << 10 << QQuickTextInput::SelectWords << 1 << 13 << false;
877 QTest::newRow("dlrow<( ,ol)leH>|words,rtl")
878 << standard[3] << 10 << 6 << QQuickTextInput::SelectWords << 6 << 13 << false;
879 QTest::newRow(",<(ol)leH>,|words")
880 << standard[3] << 8 << 10 << QQuickTextInput::SelectWords << 8 << 13 << true;
881 QTest::newRow(",<()>olleH|words")
882 << standard[3] << 8 << 8 << QQuickTextInput::SelectWords << 8 << 8 << true;
883 QTest::newRow("<()>,olleH|words")
884 << standard[3] << 7 << 7 << QQuickTextInput::SelectWords << 7 << 7 << true;
885 QTest::newRow("<dlrow( )>,olleH|words,ltr")
886 << standard[3] << 6 << 7 << QQuickTextInput::SelectWords << 1 << 7 << false;
887 QTest::newRow("dlrow<( ),>olleH|words,rtl")
888 << standard[3] << 7 << 6 << QQuickTextInput::SelectWords << 6 << 8 << false;
889 QTest::newRow("<(dlrow )>,olleH|words,ltr")
890 << standard[3] << 1 << 7 << QQuickTextInput::SelectWords << 1 << 7 << false;
891 QTest::newRow("<(dlrow ),>olleH|words,rtl")
892 << standard[3] << 7 << 1 << QQuickTextInput::SelectWords << 1 << 8 << false;
893 QTest::newRow("<(!dlrow )>,olleH|words,ltr")
894 << standard[3] << 0 << 7 << QQuickTextInput::SelectWords << 0 << 7 << false;
895 QTest::newRow("<(!dlrow ),>olleH|words,rtl")
896 << standard[3] << 7 << 0 << QQuickTextInput::SelectWords << 0 << 8 << false;
897 QTest::newRow("(!dlrow ,)olleH|words")
898 << standard[3] << 0 << 8 << QQuickTextInput::SelectWords << 0 << 8 << true;
899 QTest::newRow("<(!)>dlrow|words")
900 << standard[3] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 1 << true;
901 QTest::newRow("<()>!dlrow|words")
902 << standard[3] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << true;
903 QTest::newRow("!<()>dlrow|words")
904 << standard[3] << 1 << 1 << QQuickTextInput::SelectWords << 1 << 1 << true;
906 QTest::newRow(" <s(pac)ey> text |words")
907 << standard[4] << 1 << 4 << QQuickTextInput::SelectWords << 1 << 7 << true;
908 QTest::newRow(" spacey <t(ex)t> |words")
909 << standard[4] << 11 << 13 << QQuickTextInput::SelectWords << 10 << 14 << false; // Should be reversible. QTBUG-11365
910 QTest::newRow("<( )>spacey text |words|ltr")
911 << standard[4] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 1 << false;
912 QTest::newRow("<( )spacey> text |words|rtl")
913 << standard[4] << 1 << 0 << QQuickTextInput::SelectWords << 0 << 7 << false;
914 QTest::newRow("spacey <text( )>|words|ltr")
915 << standard[4] << 14 << 15 << QQuickTextInput::SelectWords << 10 << 15 << false;
917 // QTest::newRow("spacey text<( )>|words|rtl")
918 // << standard[4] << 15 << 14 << QQuickTextInput::SelectWords << 14 << 15 << false;
919 QTest::newRow("<()> spacey text |words")
920 << standard[4] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << false;
921 QTest::newRow(" spacey text <()>|words")
922 << standard[4] << 15 << 15 << QQuickTextInput::SelectWords << 15 << 15 << false;
925 void tst_qquicktextinput::moveCursorSelection()
927 QFETCH(QString, testStr);
928 QFETCH(int, cursorPosition);
929 QFETCH(int, movePosition);
930 QFETCH(QQuickTextInput::SelectionMode, mode);
931 QFETCH(int, selectionStart);
932 QFETCH(int, selectionEnd);
933 QFETCH(bool, reversible);
935 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
936 QQmlComponent textinputComponent(&engine);
937 textinputComponent.setData(componentStr.toLatin1(), QUrl());
938 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
939 QVERIFY(textinputObject != 0);
941 textinputObject->setCursorPosition(cursorPosition);
942 textinputObject->moveCursorSelection(movePosition, mode);
944 QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
945 QCOMPARE(textinputObject->selectionStart(), selectionStart);
946 QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
949 textinputObject->setCursorPosition(movePosition);
950 textinputObject->moveCursorSelection(cursorPosition, mode);
952 QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
953 QCOMPARE(textinputObject->selectionStart(), selectionStart);
954 QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
957 delete textinputObject;
960 void tst_qquicktextinput::moveCursorSelectionSequence_data()
962 QTest::addColumn<QString>("testStr");
963 QTest::addColumn<int>("cursorPosition");
964 QTest::addColumn<int>("movePosition1");
965 QTest::addColumn<int>("movePosition2");
966 QTest::addColumn<int>("selection1Start");
967 QTest::addColumn<int>("selection1End");
968 QTest::addColumn<int>("selection2Start");
969 QTest::addColumn<int>("selection2End");
971 // () contains the text selected by the cursor.
972 // <> contains the actual selection.
973 // ^ is the revised cursor position.
974 // {} contains the revised selection.
976 QTest::newRow("the {<quick( bro)wn> f^ox} jumped|ltr")
981 QTest::newRow("the quick<( {bro)wn> f^ox} jumped|rtl")
986 QTest::newRow("the {<quick( bro)wn> ^}fox jumped|ltr")
991 QTest::newRow("the quick<( {bro)wn> ^}fox jumped|rtl")
996 QTest::newRow("the {<quick( bro)wn^>} fox jumped|ltr")
1001 QTest::newRow("the quick<( {bro)wn^>} f^ox jumped|rtl")
1006 QTest::newRow("the {<quick() ^}bro)wn> fox|ltr")
1011 QTest::newRow("the quick<( {^bro)wn>} fox|rtl")
1016 QTest::newRow("the {<quick^}( bro)wn> fox|ltr")
1021 QTest::newRow("the quick{<(^ bro)wn>} fox|rtl")
1026 QTest::newRow("the {<qui^ck}( bro)wn> fox|ltr")
1031 QTest::newRow("the {<qui^ck}( bro)wn> fox|rtl")
1036 QTest::newRow("the {<^quick}( bro)wn> fox|ltr")
1041 QTest::newRow("the {<^quick}( bro)wn> fox|rtl")
1046 QTest::newRow("the{^ <quick}( bro)wn> fox|ltr")
1051 QTest::newRow("the{^ <quick}( bro)wn> fox|rtl")
1056 QTest::newRow("{t^he <quick}( bro)wn> fox|ltr")
1061 QTest::newRow("{t^he <quick}( bro)wn> fox|rtl")
1067 QTest::newRow("{<He(ll)o>, w^orld}!|ltr")
1072 QTest::newRow("{<He(ll)o>, w^orld}!|rtl")
1078 QTest::newRow("!{dlro^w ,<o(ll)eH>}|ltr")
1083 QTest::newRow("!{dlro^w ,<o(ll)eH>}|rtl")
1089 QTest::newRow("{<(^} sp)acey> text |ltr")
1094 QTest::newRow("{<( ^}sp)acey> text |ltr")
1099 QTest::newRow("<( {s^p)acey>} text |rtl")
1104 QTest::newRow("<( {^sp)acey>} text |rtl")
1110 QTest::newRow(" spacey <te(xt {^)>}|rtl")
1116 // QTest::newRow(" spacey <te(xt{^ )>}|rtl")
1118 // << 15 << 12 << 14
1121 QTest::newRow(" spacey {<te(x^t} )>|ltr")
1127 // QTest::newRow(" spacey {<te(xt^} )>|ltr")
1129 // << 12 << 15 << 14
1134 void tst_qquicktextinput::moveCursorSelectionSequence()
1136 QFETCH(QString, testStr);
1137 QFETCH(int, cursorPosition);
1138 QFETCH(int, movePosition1);
1139 QFETCH(int, movePosition2);
1140 QFETCH(int, selection1Start);
1141 QFETCH(int, selection1End);
1142 QFETCH(int, selection2Start);
1143 QFETCH(int, selection2End);
1145 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
1146 QQmlComponent textinputComponent(&engine);
1147 textinputComponent.setData(componentStr.toLatin1(), QUrl());
1148 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(textinputComponent.create());
1149 QVERIFY(textinputObject != 0);
1151 textinputObject->setCursorPosition(cursorPosition);
1153 textinputObject->moveCursorSelection(movePosition1, QQuickTextInput::SelectWords);
1154 QCOMPARE(textinputObject->selectedText(), testStr.mid(selection1Start, selection1End - selection1Start));
1155 QCOMPARE(textinputObject->selectionStart(), selection1Start);
1156 QCOMPARE(textinputObject->selectionEnd(), selection1End);
1158 textinputObject->moveCursorSelection(movePosition2, QQuickTextInput::SelectWords);
1159 QCOMPARE(textinputObject->selectedText(), testStr.mid(selection2Start, selection2End - selection2Start));
1160 QCOMPARE(textinputObject->selectionStart(), selection2Start);
1161 QCOMPARE(textinputObject->selectionEnd(), selection2End);
1163 delete textinputObject;
1166 void tst_qquicktextinput::dragMouseSelection()
1168 QString qmlfile = testFile("mouseselection_true.qml");
1170 QQuickView canvas(QUrl::fromLocalFile(qmlfile));
1173 canvas.requestActivateWindow();
1174 QTest::qWaitForWindowShown(&canvas);
1176 QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1178 QVERIFY(canvas.rootObject() != 0);
1179 QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(canvas.rootObject());
1180 QVERIFY(textInputObject != 0);
1182 // press-and-drag-and-release from x1 to x2
1185 int y = textInputObject->height()/2;
1186 QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
1187 QTest::mouseMove(&canvas, QPoint(x2, y));
1188 QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
1191 QVERIFY((str1 = textInputObject->selectedText()).length() > 3);
1192 QVERIFY(str1.length() > 3);
1194 // press and drag the current selection.
1197 QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
1198 QTest::mouseMove(&canvas, QPoint(x2, y));
1199 QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
1201 QString str2 = textInputObject->selectedText();
1202 QVERIFY(str2.length() > 3);
1204 QVERIFY(str1 != str2);
1207 void tst_qquicktextinput::mouseSelectionMode_data()
1209 QTest::addColumn<QString>("qmlfile");
1210 QTest::addColumn<bool>("selectWords");
1211 QTest::addColumn<bool>("focus");
1212 QTest::addColumn<bool>("focusOnPress");
1215 QTest::newRow("SelectWords focused") << testFile("mouseselectionmode_words.qml") << true << true << true;
1216 QTest::newRow("SelectCharacters focused") << testFile("mouseselectionmode_characters.qml") << false << true << true;
1217 QTest::newRow("default focused") << testFile("mouseselectionmode_default.qml") << false << true << true;
1218 QTest::newRow("SelectWords unfocused") << testFile("mouseselectionmode_words.qml") << true << false << false;
1219 QTest::newRow("SelectCharacters unfocused") << testFile("mouseselectionmode_characters.qml") << false << false << false;
1220 QTest::newRow("default unfocused") << testFile("mouseselectionmode_default.qml") << false << true << false;
1221 QTest::newRow("SelectWords focuss on press") << testFile("mouseselectionmode_words.qml") << true << false << true;
1222 QTest::newRow("SelectCharacters focus on press") << testFile("mouseselectionmode_characters.qml") << false << false << true;
1223 QTest::newRow("default focus on press") << testFile("mouseselectionmode_default.qml") << false << false << true;
1226 void tst_qquicktextinput::mouseSelectionMode()
1228 QFETCH(QString, qmlfile);
1229 QFETCH(bool, selectWords);
1230 QFETCH(bool, focus);
1231 QFETCH(bool, focusOnPress);
1233 QString text = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1235 QQuickView canvas(QUrl::fromLocalFile(qmlfile));
1238 canvas.requestActivateWindow();
1239 QTest::qWaitForWindowShown(&canvas);
1240 QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1242 QVERIFY(canvas.rootObject() != 0);
1243 QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(canvas.rootObject());
1244 QVERIFY(textInputObject != 0);
1246 textInputObject->setFocus(focus);
1247 textInputObject->setFocusOnPress(focusOnPress);
1249 // press-and-drag-and-release from x1 to x2
1252 int y = textInputObject->height()/2;
1253 QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
1254 QTest::mouseMove(&canvas, QPoint(x2,y)); // doesn't work
1255 QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
1258 QTRY_COMPARE(textInputObject->selectedText(), text);
1260 QTRY_VERIFY(textInputObject->selectedText().length() > 3);
1261 QVERIFY(textInputObject->selectedText() != text);
1265 void tst_qquicktextinput::horizontalAlignment_data()
1267 QTest::addColumn<int>("hAlign");
1268 QTest::addColumn<QString>("expectfile");
1270 QTest::newRow("L") << int(Qt::AlignLeft) << "halign_left";
1271 QTest::newRow("R") << int(Qt::AlignRight) << "halign_right";
1272 QTest::newRow("C") << int(Qt::AlignHCenter) << "halign_center";
1275 void tst_qquicktextinput::horizontalAlignment()
1277 QSKIP("Image comparison of text is almost guaranteed to fail during development");
1279 QFETCH(int, hAlign);
1280 QFETCH(QString, expectfile);
1282 QQuickView canvas(testFileUrl("horizontalAlignment.qml"));
1285 canvas.requestActivateWindow();
1286 QTest::qWaitForWindowShown(&canvas);
1287 QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1288 QObject *ob = canvas.rootObject();
1290 ob->setProperty("horizontalAlignment",hAlign);
1291 QImage actual = canvas.grabFrameBuffer();
1293 expectfile = createExpectedFileIfNotFound(expectfile, actual);
1295 QImage expect(expectfile);
1297 QCOMPARE(actual,expect);
1300 void tst_qquicktextinput::horizontalAlignment_RightToLeft()
1302 PlatformInputContext platformInputContext;
1303 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
1304 inputMethodPrivate->testContext = &platformInputContext;
1306 QQuickView canvas(testFileUrl("horizontalAlignment_RightToLeft.qml"));
1307 QQuickTextInput *textInput = canvas.rootObject()->findChild<QQuickTextInput*>("text");
1308 QVERIFY(textInput != 0);
1311 const QString rtlText = textInput->text();
1313 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1314 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1316 // implicit alignment should follow the reading direction of RTL text
1317 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1318 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1319 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1320 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1322 // explicitly left aligned
1323 textInput->setHAlign(QQuickTextInput::AlignLeft);
1324 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1325 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1326 QCOMPARE(textInput->boundingRect().left(), qreal(0));
1328 // explicitly right aligned
1329 textInput->setHAlign(QQuickTextInput::AlignRight);
1330 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1331 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1332 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1333 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1335 // explicitly center aligned
1336 textInput->setHAlign(QQuickTextInput::AlignHCenter);
1337 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1338 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignHCenter);
1339 QVERIFY(textInput->boundingRect().left() > 0);
1340 QVERIFY(textInput->boundingRect().right() < textInput->width());
1342 // reseted alignment should go back to following the text reading direction
1343 textInput->resetHAlign();
1344 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1345 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1346 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1347 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1349 // mirror the text item
1350 QQuickItemPrivate::get(textInput)->setLayoutMirror(true);
1352 // mirrored implicit alignment should continue to follow the reading direction of the text
1353 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1354 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1355 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1356 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1358 // explicitly right aligned behaves as left aligned
1359 textInput->setHAlign(QQuickTextInput::AlignRight);
1360 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1361 QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignLeft);
1362 QCOMPARE(textInput->boundingRect().left(), qreal(0));
1364 // mirrored explicitly left aligned behaves as right aligned
1365 textInput->setHAlign(QQuickTextInput::AlignLeft);
1366 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1367 QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignRight);
1368 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1369 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1371 // disable mirroring
1372 QQuickItemPrivate::get(textInput)->setLayoutMirror(false);
1373 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1374 textInput->resetHAlign();
1376 // English text should be implicitly left aligned
1377 textInput->setText("Hello world!");
1378 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1379 QCOMPARE(textInput->boundingRect().left(), qreal(0));
1381 canvas.requestActivateWindow();
1382 QTest::qWaitForWindowShown(&canvas);
1383 QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1385 // If there is no commited text, the preedit text should determine the alignment.
1386 textInput->setText(QString());
1387 { QInputMethodEvent ev(rtlText, QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(qGuiApp->focusObject(), &ev); }
1388 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1389 { QInputMethodEvent ev("Hello world!", QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(qGuiApp->focusObject(), &ev); }
1390 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1392 // Clear pre-edit text. TextInput should maybe do this itself on setText, but that may be
1393 // redundant as an actual input method may take care of it.
1394 { QInputMethodEvent ev; QGuiApplication::sendEvent(qGuiApp->focusObject(), &ev); }
1396 // empty text with implicit alignment follows the system locale-based
1397 // keyboard input direction from QInputMethod::inputDirection()
1398 textInput->setText("");
1399 platformInputContext.setInputDirection(Qt::LeftToRight);
1400 QVERIFY(qApp->inputMethod()->inputDirection() == Qt::LeftToRight);
1401 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1402 QCOMPARE(textInput->boundingRect().left(), qreal(0));
1404 QSignalSpy cursorRectangleSpy(textInput, SIGNAL(cursorRectangleChanged()));
1405 platformInputContext.setInputDirection(Qt::RightToLeft);
1406 QVERIFY(qApp->inputMethod()->inputDirection() == Qt::RightToLeft);
1407 QCOMPARE(cursorRectangleSpy.count(), 1);
1408 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1409 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1410 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1412 // set input direction while having content
1413 platformInputContext.setInputDirection(Qt::LeftToRight);
1414 textInput->setText("a");
1415 platformInputContext.setInputDirection(Qt::RightToLeft);
1416 QTest::keyClick(&canvas, Qt::Key_Backspace);
1417 QVERIFY(textInput->text().isEmpty());
1418 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1419 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1420 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1422 // input direction changed while not having focus
1423 platformInputContext.setInputDirection(Qt::LeftToRight);
1424 textInput->setFocus(false);
1425 platformInputContext.setInputDirection(Qt::RightToLeft);
1426 textInput->setFocus(true);
1427 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1428 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1429 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1431 textInput->setHAlign(QQuickTextInput::AlignRight);
1432 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1433 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1434 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1436 // neutral text should fall back to input direction
1437 textInput->setFocus(true);
1438 textInput->resetHAlign();
1439 textInput->setText(" ()((=<>");
1440 platformInputContext.setInputDirection(Qt::LeftToRight);
1441 QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignLeft);
1442 platformInputContext.setInputDirection(Qt::RightToLeft);
1443 QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignRight);
1445 // changing width keeps right aligned cursor on proper position
1446 textInput->setText("");
1447 textInput->setWidth(500);
1448 QVERIFY(textInput->positionToRectangle(0).x() > textInput->width() / 2);
1451 void tst_qquicktextinput::verticalAlignment()
1453 QQuickView canvas(testFileUrl("horizontalAlignment.qml"));
1454 QQuickTextInput *textInput = canvas.rootObject()->findChild<QQuickTextInput*>("text");
1455 QVERIFY(textInput != 0);
1458 QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignTop);
1459 QVERIFY(textInput->boundingRect().bottom() < canvas.height() / 2);
1460 QVERIFY(textInput->cursorRectangle().bottom() < canvas.height() / 2);
1461 QVERIFY(textInput->positionToRectangle(0).bottom() < canvas.height() / 2);
1464 textInput->setVAlign(QQuickTextInput::AlignBottom);
1465 QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignBottom);
1466 QVERIFY(textInput->boundingRect().top() > canvas.height() / 2);
1467 QVERIFY(textInput->cursorRectangle().top() > canvas.height() / 2);
1468 QVERIFY(textInput->positionToRectangle(0).top() > canvas.height() / 2);
1470 // explicitly center aligned
1471 textInput->setVAlign(QQuickTextInput::AlignVCenter);
1472 QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignVCenter);
1473 QVERIFY(textInput->boundingRect().top() < canvas.height() / 2);
1474 QVERIFY(textInput->boundingRect().bottom() > canvas.height() / 2);
1475 QVERIFY(textInput->cursorRectangle().top() < canvas.height() / 2);
1476 QVERIFY(textInput->cursorRectangle().bottom() > canvas.height() / 2);
1477 QVERIFY(textInput->positionToRectangle(0).top() < canvas.height() / 2);
1478 QVERIFY(textInput->positionToRectangle(0).bottom() > canvas.height() / 2);
1481 void tst_qquicktextinput::clipRect()
1483 QQmlComponent component(&engine);
1484 component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
1485 QScopedPointer<QObject> object(component.create());
1486 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
1489 QCOMPARE(input->clipRect().x(), qreal(0));
1490 QCOMPARE(input->clipRect().y(), qreal(0));
1491 QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
1492 QCOMPARE(input->clipRect().height(), input->height());
1494 input->setText("Hello World");
1495 QCOMPARE(input->clipRect().x(), qreal(0));
1496 QCOMPARE(input->clipRect().y(), qreal(0));
1497 QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
1498 QCOMPARE(input->clipRect().height(), input->height());
1500 // clip rect shouldn't exceed the size of the item, expect for the cursor width;
1501 input->setWidth(input->width() / 2);
1502 QCOMPARE(input->clipRect().x(), qreal(0));
1503 QCOMPARE(input->clipRect().y(), qreal(0));
1504 QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
1505 QCOMPARE(input->clipRect().height(), input->height());
1507 input->setHeight(input->height() * 2);
1508 QCOMPARE(input->clipRect().x(), qreal(0));
1509 QCOMPARE(input->clipRect().y(), qreal(0));
1510 QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
1511 QCOMPARE(input->clipRect().height(), input->height());
1513 QQmlComponent cursorComponent(&engine);
1514 cursorComponent.setData("import QtQuick 2.0\nRectangle { height: 20; width: 8 }", QUrl());
1516 input->setCursorDelegate(&cursorComponent);
1517 input->setCursorVisible(true);
1519 // If a cursor delegate is used it's size should determine the excess width.
1520 QCOMPARE(input->clipRect().x(), qreal(0));
1521 QCOMPARE(input->clipRect().y(), qreal(0));
1522 QCOMPARE(input->clipRect().width(), input->width() + 8);
1523 QCOMPARE(input->clipRect().height(), input->height());
1525 // Alignment, auto scroll, wrapping all don't affect the clip rect.
1526 input->setAutoScroll(false);
1527 QCOMPARE(input->clipRect().x(), qreal(0));
1528 QCOMPARE(input->clipRect().y(), qreal(0));
1529 QCOMPARE(input->clipRect().width(), input->width() + 8);
1530 QCOMPARE(input->clipRect().height(), input->height());
1532 input->setHAlign(QQuickTextInput::AlignRight);
1533 QCOMPARE(input->clipRect().x(), qreal(0));
1534 QCOMPARE(input->clipRect().y(), qreal(0));
1535 QCOMPARE(input->clipRect().width(), input->width() + 8);
1536 QCOMPARE(input->clipRect().height(), input->height());
1538 input->setWrapMode(QQuickTextInput::Wrap);
1539 QCOMPARE(input->clipRect().x(), qreal(0));
1540 QCOMPARE(input->clipRect().y(), qreal(0));
1541 QCOMPARE(input->clipRect().width(), input->width() + 8);
1542 QCOMPARE(input->clipRect().height(), input->height());
1544 input->setVAlign(QQuickTextInput::AlignBottom);
1545 QCOMPARE(input->clipRect().x(), qreal(0));
1546 QCOMPARE(input->clipRect().y(), qreal(0));
1547 QCOMPARE(input->clipRect().width(), input->width() + 8);
1548 QCOMPARE(input->clipRect().height(), input->height());
1551 void tst_qquicktextinput::boundingRect()
1553 QQmlComponent component(&engine);
1554 component.setData("import QtQuick 2.0\n TextInput {}", QUrl());
1555 QScopedPointer<QObject> object(component.create());
1556 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object.data());
1560 layout.setFont(input->font());
1562 if (!qmlDisableDistanceField()) {
1564 option.setUseDesignMetrics(true);
1565 layout.setTextOption(option);
1567 layout.beginLayout();
1568 QTextLine line = layout.createLine();
1571 QCOMPARE(input->boundingRect().x(), qreal(0));
1572 QCOMPARE(input->boundingRect().y(), qreal(0));
1573 QCOMPARE(input->boundingRect().width(), input->cursorRectangle().width());
1574 QCOMPARE(input->boundingRect().height(), line.height());
1576 input->setText("Hello World");
1578 layout.setText(input->text());
1579 layout.beginLayout();
1580 line = layout.createLine();
1583 QCOMPARE(input->boundingRect().x(), qreal(0));
1584 QCOMPARE(input->boundingRect().y(), qreal(0));
1585 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
1586 QCOMPARE(input->boundingRect().height(), line.height());
1588 // the size of the bounding rect shouldn't be bounded by the size of item.
1589 input->setWidth(input->width() / 2);
1590 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
1591 QCOMPARE(input->boundingRect().y(), qreal(0));
1592 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
1593 QCOMPARE(input->boundingRect().height(), line.height());
1595 input->setHeight(input->height() * 2);
1596 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
1597 QCOMPARE(input->boundingRect().y(), qreal(0));
1598 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
1599 QCOMPARE(input->boundingRect().height(), line.height());
1601 QQmlComponent cursorComponent(&engine);
1602 cursorComponent.setData("import QtQuick 2.0\nRectangle { height: 20; width: 8 }", QUrl());
1604 input->setCursorDelegate(&cursorComponent);
1605 input->setCursorVisible(true);
1607 // Don't include the size of a cursor delegate as it has its own bounding rect.
1608 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
1609 QCOMPARE(input->boundingRect().y(), qreal(0));
1610 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
1611 QCOMPARE(input->boundingRect().height(), line.height());
1613 // Bounding rect left aligned when auto scroll is disabled;
1614 input->setAutoScroll(false);
1615 QCOMPARE(input->boundingRect().x(), qreal(0));
1616 QCOMPARE(input->boundingRect().y(), qreal(0));
1617 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
1618 QCOMPARE(input->boundingRect().height(), line.height());
1620 input->setHAlign(QQuickTextInput::AlignRight);
1621 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
1622 QCOMPARE(input->boundingRect().y(), qreal(0));
1623 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
1624 QCOMPARE(input->boundingRect().height(), line.height());
1626 input->setWrapMode(QQuickTextInput::Wrap);
1627 QCOMPARE(input->boundingRect().right(), input->width());
1628 QCOMPARE(input->boundingRect().y(), qreal(0));
1629 QVERIFY(input->boundingRect().width() < line.naturalTextWidth());
1630 QVERIFY(input->boundingRect().height() > line.height());
1632 input->setVAlign(QQuickTextInput::AlignBottom);
1633 QCOMPARE(input->boundingRect().right(), input->width());
1634 QCOMPARE(input->boundingRect().bottom(), input->height());
1635 QVERIFY(input->boundingRect().width() < line.naturalTextWidth());
1636 QVERIFY(input->boundingRect().height() > line.height());
1639 void tst_qquicktextinput::positionAt()
1641 QQuickView canvas(testFileUrl("positionAt.qml"));
1642 QVERIFY(canvas.rootObject() != 0);
1644 canvas.requestActivateWindow();
1645 QTest::qWaitForWindowShown(&canvas);
1647 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(canvas.rootObject());
1648 QVERIFY(textinputObject != 0);
1650 // Check autoscrolled...
1652 int pos = evaluate<int>(textinputObject, QString("positionAt(%1)").arg(textinputObject->width()/2));
1654 QTextLayout layout(textinputObject->text());
1655 layout.setFont(textinputObject->font());
1657 if (!qmlDisableDistanceField()) {
1659 option.setUseDesignMetrics(true);
1660 layout.setTextOption(option);
1662 layout.beginLayout();
1663 QTextLine line = layout.createLine();
1666 int textLeftWidthBegin = floor(line.cursorToX(pos - 1));
1667 int textLeftWidthEnd = ceil(line.cursorToX(pos + 1));
1668 int textWidth = floor(line.horizontalAdvance());
1670 QVERIFY(textLeftWidthBegin <= textWidth - textinputObject->width() / 2);
1671 QVERIFY(textLeftWidthEnd >= textWidth - textinputObject->width() / 2);
1673 int x = textinputObject->positionToRectangle(pos + 1).x() - 1;
1674 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1);
1675 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos);
1677 // Check without autoscroll...
1678 textinputObject->setAutoScroll(false);
1679 pos = evaluate<int>(textinputObject, QString("positionAt(%1)").arg(textinputObject->width() / 2));
1681 textLeftWidthBegin = floor(line.cursorToX(pos - 1));
1682 textLeftWidthEnd = ceil(line.cursorToX(pos + 1));
1684 QVERIFY(textLeftWidthBegin <= textinputObject->width() / 2);
1685 QVERIFY(textLeftWidthEnd >= textinputObject->width() / 2);
1687 x = textinputObject->positionToRectangle(pos + 1).x() - 1;
1688 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1);
1689 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos);
1691 const qreal x0 = textinputObject->positionToRectangle(pos).x();
1692 const qreal x1 = textinputObject->positionToRectangle(pos + 1).x();
1694 QString preeditText = textinputObject->text().mid(0, pos);
1695 textinputObject->setText(textinputObject->text().mid(pos));
1696 textinputObject->setCursorPosition(0);
1698 { QInputMethodEvent inputEvent(preeditText, QList<QInputMethodEvent::Attribute>());
1699 QVERIFY(qGuiApp->focusObject());
1700 QGuiApplication::sendEvent(qGuiApp->focusObject(), &inputEvent); }
1702 // Check all points within the preedit text return the same position.
1703 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(0)), 0);
1704 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x0 / 2)), 0);
1705 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x0)), 0);
1707 // Verify positioning returns to normal after the preedit text.
1708 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x1)), 1);
1709 QCOMPARE(textinputObject->positionToRectangle(1).x(), x1);
1711 { QInputMethodEvent inputEvent;
1712 QVERIFY(qGuiApp->focusObject());
1713 QGuiApplication::sendEvent(qGuiApp->focusObject(), &inputEvent); }
1716 textinputObject->setWrapMode(QQuickTextInput::WrapAnywhere);
1718 const qreal y0 = line.height() / 2;
1719 const qreal y1 = line.height() * 3 / 2;
1721 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y0)), pos);
1722 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y0)), pos + 1);
1724 int newLinePos = evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y1));
1725 QVERIFY(newLinePos > pos);
1726 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y1)), newLinePos + 1);
1729 void tst_qquicktextinput::maxLength()
1731 QQuickView canvas(testFileUrl("maxLength.qml"));
1732 QVERIFY(canvas.rootObject() != 0);
1734 canvas.requestActivateWindow();
1735 QTest::qWaitForWindowShown(&canvas);
1737 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(canvas.rootObject());
1738 QVERIFY(textinputObject != 0);
1739 QVERIFY(textinputObject->text().isEmpty());
1740 QVERIFY(textinputObject->maxLength() == 10);
1741 foreach (const QString &str, standard) {
1742 QVERIFY(textinputObject->text().length() <= 10);
1743 textinputObject->setText(str);
1744 QVERIFY(textinputObject->text().length() <= 10);
1747 textinputObject->setText("");
1748 QTRY_VERIFY(textinputObject->hasActiveFocus() == true);
1749 for (int i=0; i<20; i++) {
1750 QTRY_COMPARE(textinputObject->text().length(), qMin(i,10));
1751 //simulateKey(&canvas, Qt::Key_A);
1752 QTest::keyPress(&canvas, Qt::Key_A);
1753 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1758 void tst_qquicktextinput::masks()
1760 //Not a comprehensive test of the possible masks, that's done elsewhere (QLineEdit)
1761 //QString componentStr = "import QtQuick 2.0\nTextInput { inputMask: 'HHHHhhhh'; }";
1762 QQuickView canvas(testFileUrl("masks.qml"));
1764 canvas.requestActivateWindow();
1765 QVERIFY(canvas.rootObject() != 0);
1766 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(canvas.rootObject());
1767 QVERIFY(textinputObject != 0);
1768 QTRY_VERIFY(textinputObject->hasActiveFocus() == true);
1769 QVERIFY(textinputObject->text().length() == 0);
1770 QCOMPARE(textinputObject->inputMask(), QString("HHHHhhhh; "));
1771 QCOMPARE(textinputObject->length(), 8);
1772 for (int i=0; i<10; i++) {
1773 QTRY_COMPARE(qMin(i,8), textinputObject->text().length());
1774 QCOMPARE(textinputObject->length(), 8);
1775 QCOMPARE(textinputObject->getText(0, qMin(i, 8)), QString(qMin(i, 8), 'a'));
1776 QCOMPARE(textinputObject->getText(qMin(i, 8), 8), QString(8 - qMin(i, 8), ' '));
1777 QCOMPARE(i>=4, textinputObject->hasAcceptableInput());
1778 //simulateKey(&canvas, Qt::Key_A);
1779 QTest::keyPress(&canvas, Qt::Key_A);
1780 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1785 void tst_qquicktextinput::validators()
1787 // Note that this test assumes that the validators are working properly
1788 // so you may need to run their tests first. All validators are checked
1789 // here to ensure that their exposure to QML is working.
1791 QLocale::setDefault(QLocale(QStringLiteral("C")));
1793 QQuickView canvas(testFileUrl("validators.qml"));
1795 canvas.requestActivateWindow();
1797 QVERIFY(canvas.rootObject() != 0);
1799 QLocale defaultLocale;
1800 QLocale enLocale("en");
1801 QLocale deLocale("de_DE");
1803 QQuickTextInput *intInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("intInput")));
1805 QSignalSpy intSpy(intInput, SIGNAL(acceptableInputChanged()));
1807 QQuickIntValidator *intValidator = qobject_cast<QQuickIntValidator *>(intInput->validator());
1808 QVERIFY(intValidator);
1809 QCOMPARE(intValidator->localeName(), defaultLocale.name());
1810 QCOMPARE(intInput->validator()->locale(), defaultLocale);
1811 intValidator->setLocaleName(enLocale.name());
1812 QCOMPARE(intValidator->localeName(), enLocale.name());
1813 QCOMPARE(intInput->validator()->locale(), enLocale);
1814 intValidator->resetLocaleName();
1815 QCOMPARE(intValidator->localeName(), defaultLocale.name());
1816 QCOMPARE(intInput->validator()->locale(), defaultLocale);
1818 intInput->setFocus(true);
1819 QTRY_VERIFY(intInput->hasActiveFocus());
1820 QCOMPARE(intInput->hasAcceptableInput(), false);
1821 QCOMPARE(intInput->property("acceptable").toBool(), false);
1822 QTest::keyPress(&canvas, Qt::Key_1);
1823 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1825 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
1826 QCOMPARE(intInput->hasAcceptableInput(), false);
1827 QCOMPARE(intInput->property("acceptable").toBool(), false);
1828 QCOMPARE(intSpy.count(), 0);
1829 QTest::keyPress(&canvas, Qt::Key_2);
1830 QTest::keyRelease(&canvas, Qt::Key_2, Qt::NoModifier ,10);
1832 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
1833 QCOMPARE(intInput->hasAcceptableInput(), false);
1834 QCOMPARE(intInput->property("acceptable").toBool(), false);
1835 QCOMPARE(intSpy.count(), 0);
1836 QTest::keyPress(&canvas, Qt::Key_Period);
1837 QTest::keyRelease(&canvas, Qt::Key_Period, Qt::NoModifier ,10);
1839 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
1840 QCOMPARE(intInput->hasAcceptableInput(), false);
1841 QTest::keyPress(&canvas, Qt::Key_Comma);
1842 QTest::keyRelease(&canvas, Qt::Key_Comma, Qt::NoModifier ,10);
1844 QTRY_COMPARE(intInput->text(), QLatin1String("1,"));
1845 QCOMPARE(intInput->hasAcceptableInput(), false);
1846 QTest::keyPress(&canvas, Qt::Key_Backspace);
1847 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
1849 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
1850 QCOMPARE(intInput->hasAcceptableInput(), false);
1851 intValidator->setLocaleName(deLocale.name());
1852 QTest::keyPress(&canvas, Qt::Key_Period);
1853 QTest::keyRelease(&canvas, Qt::Key_Period, Qt::NoModifier ,10);
1855 QTRY_COMPARE(intInput->text(), QLatin1String("1."));
1856 QCOMPARE(intInput->hasAcceptableInput(), false);
1857 QTest::keyPress(&canvas, Qt::Key_Backspace);
1858 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
1860 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
1861 QCOMPARE(intInput->hasAcceptableInput(), false);
1862 intValidator->resetLocaleName();
1863 QTest::keyPress(&canvas, Qt::Key_1);
1864 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1866 QCOMPARE(intInput->text(), QLatin1String("11"));
1867 QCOMPARE(intInput->hasAcceptableInput(), true);
1868 QCOMPARE(intInput->property("acceptable").toBool(), true);
1869 QCOMPARE(intSpy.count(), 1);
1870 QTest::keyPress(&canvas, Qt::Key_0);
1871 QTest::keyRelease(&canvas, Qt::Key_0, Qt::NoModifier ,10);
1873 QCOMPARE(intInput->text(), QLatin1String("11"));
1874 QCOMPARE(intInput->hasAcceptableInput(), true);
1875 QCOMPARE(intInput->property("acceptable").toBool(), true);
1876 QCOMPARE(intSpy.count(), 1);
1878 QQuickTextInput *dblInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("dblInput")));
1880 QSignalSpy dblSpy(dblInput, SIGNAL(acceptableInputChanged()));
1882 QQuickDoubleValidator *dblValidator = qobject_cast<QQuickDoubleValidator *>(dblInput->validator());
1883 QVERIFY(dblValidator);
1884 QCOMPARE(dblValidator->localeName(), defaultLocale.name());
1885 QCOMPARE(dblInput->validator()->locale(), defaultLocale);
1886 dblValidator->setLocaleName(enLocale.name());
1887 QCOMPARE(dblValidator->localeName(), enLocale.name());
1888 QCOMPARE(dblInput->validator()->locale(), enLocale);
1889 dblValidator->resetLocaleName();
1890 QCOMPARE(dblValidator->localeName(), defaultLocale.name());
1891 QCOMPARE(dblInput->validator()->locale(), defaultLocale);
1893 dblInput->setFocus(true);
1894 QVERIFY(dblInput->hasActiveFocus() == true);
1895 QCOMPARE(dblInput->hasAcceptableInput(), false);
1896 QCOMPARE(dblInput->property("acceptable").toBool(), false);
1897 QTest::keyPress(&canvas, Qt::Key_1);
1898 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1900 QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
1901 QCOMPARE(dblInput->hasAcceptableInput(), false);
1902 QCOMPARE(dblInput->property("acceptable").toBool(), false);
1903 QCOMPARE(dblSpy.count(), 0);
1904 QTest::keyPress(&canvas, Qt::Key_2);
1905 QTest::keyRelease(&canvas, Qt::Key_2, Qt::NoModifier ,10);
1907 QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
1908 QCOMPARE(dblInput->hasAcceptableInput(), true);
1909 QCOMPARE(dblInput->property("acceptable").toBool(), true);
1910 QCOMPARE(dblSpy.count(), 1);
1911 QTest::keyPress(&canvas, Qt::Key_Comma);
1912 QTest::keyRelease(&canvas, Qt::Key_Comma, Qt::NoModifier ,10);
1914 QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
1915 QCOMPARE(dblInput->hasAcceptableInput(), true);
1916 QTest::keyPress(&canvas, Qt::Key_1);
1917 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1919 QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
1920 QCOMPARE(dblInput->hasAcceptableInput(), true);
1921 dblValidator->setLocaleName(deLocale.name());
1922 QCOMPARE(dblInput->hasAcceptableInput(), true);
1923 QTest::keyPress(&canvas, Qt::Key_1);
1924 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1926 QTRY_COMPARE(dblInput->text(), QLatin1String("12,1"));
1927 QCOMPARE(dblInput->hasAcceptableInput(), true);
1928 QTest::keyPress(&canvas, Qt::Key_1);
1929 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1931 QTRY_COMPARE(dblInput->text(), QLatin1String("12,11"));
1932 QCOMPARE(dblInput->hasAcceptableInput(), true);
1933 QTest::keyPress(&canvas, Qt::Key_Backspace);
1934 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
1936 QTRY_COMPARE(dblInput->text(), QLatin1String("12,1"));
1937 QCOMPARE(dblInput->hasAcceptableInput(), true);
1938 QTest::keyPress(&canvas, Qt::Key_Backspace);
1939 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
1941 QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
1942 QCOMPARE(dblInput->hasAcceptableInput(), true);
1943 QTest::keyPress(&canvas, Qt::Key_Backspace);
1944 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
1946 QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
1947 QCOMPARE(dblInput->hasAcceptableInput(), true);
1948 dblValidator->resetLocaleName();
1949 QTest::keyPress(&canvas, Qt::Key_Period);
1950 QTest::keyRelease(&canvas, Qt::Key_Period, Qt::NoModifier ,10);
1952 QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
1953 QCOMPARE(dblInput->hasAcceptableInput(), true);
1954 QCOMPARE(dblInput->property("acceptable").toBool(), true);
1955 QCOMPARE(dblSpy.count(), 1);
1956 QTest::keyPress(&canvas, Qt::Key_1);
1957 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1959 QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
1960 QCOMPARE(dblInput->hasAcceptableInput(), true);
1961 QCOMPARE(dblInput->property("acceptable").toBool(), true);
1962 QCOMPARE(dblSpy.count(), 1);
1963 QTest::keyPress(&canvas, Qt::Key_1);
1964 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1966 QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
1967 QCOMPARE(dblInput->hasAcceptableInput(), true);
1968 QCOMPARE(dblInput->property("acceptable").toBool(), true);
1969 QCOMPARE(dblSpy.count(), 1);
1970 QTest::keyPress(&canvas, Qt::Key_1);
1971 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1973 QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
1974 QCOMPARE(dblInput->hasAcceptableInput(), true);
1975 QCOMPARE(dblInput->property("acceptable").toBool(), true);
1976 QCOMPARE(dblSpy.count(), 1);
1978 // Ensure the validator doesn't prevent characters being removed.
1979 dblInput->setValidator(intInput->validator());
1980 QCOMPARE(dblInput->text(), QLatin1String("12.11"));
1981 QCOMPARE(dblInput->hasAcceptableInput(), false);
1982 QCOMPARE(dblInput->property("acceptable").toBool(), false);
1983 QCOMPARE(dblSpy.count(), 2);
1984 QTest::keyPress(&canvas, Qt::Key_Backspace);
1985 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
1987 QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
1988 QCOMPARE(dblInput->hasAcceptableInput(), false);
1989 QCOMPARE(dblInput->property("acceptable").toBool(), false);
1990 QCOMPARE(dblSpy.count(), 2);
1991 // Once unacceptable input is in anything goes until it reaches an acceptable state again.
1992 QTest::keyPress(&canvas, Qt::Key_1);
1993 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1995 QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
1996 QCOMPARE(dblInput->hasAcceptableInput(), false);
1997 QCOMPARE(dblSpy.count(), 2);
1998 QTest::keyPress(&canvas, Qt::Key_Backspace);
1999 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
2001 QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
2002 QCOMPARE(dblInput->hasAcceptableInput(), false);
2003 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2004 QCOMPARE(dblSpy.count(), 2);
2005 QTest::keyPress(&canvas, Qt::Key_Backspace);
2006 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
2008 QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
2009 QCOMPARE(dblInput->hasAcceptableInput(), false);
2010 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2011 QCOMPARE(dblSpy.count(), 2);
2012 QTest::keyPress(&canvas, Qt::Key_Backspace);
2013 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
2015 QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
2016 QCOMPARE(dblInput->hasAcceptableInput(), false);
2017 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2018 QCOMPARE(dblSpy.count(), 2);
2019 QTest::keyPress(&canvas, Qt::Key_Backspace);
2020 QTest::keyRelease(&canvas, Qt::Key_Backspace, Qt::NoModifier ,10);
2022 QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
2023 QCOMPARE(dblInput->hasAcceptableInput(), false);
2024 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2025 QCOMPARE(dblSpy.count(), 2);
2026 QTest::keyPress(&canvas, Qt::Key_1);
2027 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
2029 QCOMPARE(dblInput->text(), QLatin1String("11"));
2030 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2031 QCOMPARE(dblInput->hasAcceptableInput(), true);
2032 QCOMPARE(dblSpy.count(), 3);
2034 // Changing the validator properties will re-evaluate whether the input is acceptable.
2035 intValidator->setTop(10);
2036 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2037 QCOMPARE(dblInput->hasAcceptableInput(), false);
2038 QCOMPARE(dblSpy.count(), 4);
2039 intValidator->setTop(12);
2040 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2041 QCOMPARE(dblInput->hasAcceptableInput(), true);
2042 QCOMPARE(dblSpy.count(), 5);
2044 QQuickTextInput *strInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("strInput")));
2046 QSignalSpy strSpy(strInput, SIGNAL(acceptableInputChanged()));
2047 strInput->setFocus(true);
2048 QVERIFY(strInput->hasActiveFocus() == true);
2049 QCOMPARE(strInput->hasAcceptableInput(), false);
2050 QCOMPARE(strInput->property("acceptable").toBool(), false);
2051 QTest::keyPress(&canvas, Qt::Key_1);
2052 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
2054 QTRY_COMPARE(strInput->text(), QLatin1String(""));
2055 QCOMPARE(strInput->hasAcceptableInput(), false);
2056 QCOMPARE(strInput->property("acceptable").toBool(), false);
2057 QCOMPARE(strSpy.count(), 0);
2058 QTest::keyPress(&canvas, Qt::Key_A);
2059 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
2061 QTRY_COMPARE(strInput->text(), QLatin1String("a"));
2062 QCOMPARE(strInput->hasAcceptableInput(), false);
2063 QCOMPARE(strInput->property("acceptable").toBool(), false);
2064 QCOMPARE(strSpy.count(), 0);
2065 QTest::keyPress(&canvas, Qt::Key_A);
2066 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
2068 QTRY_COMPARE(strInput->text(), QLatin1String("aa"));
2069 QCOMPARE(strInput->hasAcceptableInput(), true);
2070 QCOMPARE(strInput->property("acceptable").toBool(), true);
2071 QCOMPARE(strSpy.count(), 1);
2072 QTest::keyPress(&canvas, Qt::Key_A);
2073 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
2075 QTRY_COMPARE(strInput->text(), QLatin1String("aaa"));
2076 QCOMPARE(strInput->hasAcceptableInput(), true);
2077 QCOMPARE(strInput->property("acceptable").toBool(), true);
2078 QCOMPARE(strSpy.count(), 1);
2079 QTest::keyPress(&canvas, Qt::Key_A);
2080 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
2082 QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
2083 QCOMPARE(strInput->hasAcceptableInput(), true);
2084 QCOMPARE(strInput->property("acceptable").toBool(), true);
2085 QCOMPARE(strSpy.count(), 1);
2086 QTest::keyPress(&canvas, Qt::Key_A);
2087 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
2089 QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
2090 QCOMPARE(strInput->hasAcceptableInput(), true);
2091 QCOMPARE(strInput->property("acceptable").toBool(), true);
2092 QCOMPARE(strSpy.count(), 1);
2094 QQuickTextInput *unvalidatedInput = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("unvalidatedInput")));
2095 QVERIFY(unvalidatedInput);
2096 QSignalSpy unvalidatedSpy(unvalidatedInput, SIGNAL(acceptableInputChanged()));
2097 unvalidatedInput->setFocus(true);
2098 QVERIFY(unvalidatedInput->hasActiveFocus() == true);
2099 QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
2100 QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
2101 QTest::keyPress(&canvas, Qt::Key_1);
2102 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
2104 QTRY_COMPARE(unvalidatedInput->text(), QLatin1String("1"));
2105 QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
2106 QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
2107 QCOMPARE(unvalidatedSpy.count(), 0);
2108 QTest::keyPress(&canvas, Qt::Key_A);
2109 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
2111 QTRY_COMPARE(unvalidatedInput->text(), QLatin1String("1a"));
2112 QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
2113 QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
2114 QCOMPARE(unvalidatedSpy.count(), 0);
2117 void tst_qquicktextinput::inputMethods()
2119 QQuickView canvas(testFileUrl("inputmethods.qml"));
2121 canvas.requestActivateWindow();
2122 QTest::qWaitForWindowShown(&canvas);
2124 // test input method hints
2125 QVERIFY(canvas.rootObject() != 0);
2126 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(canvas.rootObject());
2127 QVERIFY(input != 0);
2128 QVERIFY(input->inputMethodHints() & Qt::ImhNoPredictiveText);
2129 QSignalSpy inputMethodHintSpy(input, SIGNAL(inputMethodHintsChanged()));
2130 input->setInputMethodHints(Qt::ImhUppercaseOnly);
2131 QVERIFY(input->inputMethodHints() & Qt::ImhUppercaseOnly);
2132 QCOMPARE(inputMethodHintSpy.count(), 1);
2133 input->setInputMethodHints(Qt::ImhUppercaseOnly);
2134 QCOMPARE(inputMethodHintSpy.count(), 1);
2137 QQuickTextInput plainInput;
2138 QCOMPARE(plainInput.inputMethodHints(), Qt::ImhNone);
2140 input->setFocus(true);
2141 QVERIFY(input->hasActiveFocus() == true);
2142 // test that input method event is committed
2143 QInputMethodEvent event;
2144 event.setCommitString( "My ", -12, 0);
2145 QTRY_COMPARE(qGuiApp->focusObject(), input);
2146 QGuiApplication::sendEvent(qGuiApp->focusObject(), &event);
2147 QCOMPARE(input->text(), QString("My Hello world!"));
2149 input->setCursorPosition(2);
2150 event.setCommitString("Your", -2, 2);
2151 QGuiApplication::sendEvent(qGuiApp->focusObject(), &event);
2152 QCOMPARE(input->text(), QString("Your Hello world!"));
2153 QCOMPARE(input->cursorPosition(), 4);
2155 input->setCursorPosition(7);
2156 event.setCommitString("Goodbye", -2, 5);
2157 QGuiApplication::sendEvent(qGuiApp->focusObject(), &event);
2158 QCOMPARE(input->text(), QString("Your Goodbye world!"));
2159 QCOMPARE(input->cursorPosition(), 12);
2161 input->setCursorPosition(8);
2162 event.setCommitString("Our", -8, 4);
2163 QGuiApplication::sendEvent(qGuiApp->focusObject(), &event);
2164 QCOMPARE(input->text(), QString("Our Goodbye world!"));
2165 QCOMPARE(input->cursorPosition(), 7);
2167 // input should reset selection even if replacement parameters are out of bounds
2168 input->setText("text");
2169 input->setCursorPosition(0);
2170 input->moveCursorSelection(input->text().length());
2171 event.setCommitString("replacement", -input->text().length(), input->text().length());
2172 QGuiApplication::sendEvent(qGuiApp->focusObject(), &event);
2173 QCOMPARE(input->selectionStart(), input->selectionEnd());
2175 QInputMethodQueryEvent enabledQueryEvent(Qt::ImEnabled);
2176 QGuiApplication::sendEvent(input, &enabledQueryEvent);
2177 QCOMPARE(enabledQueryEvent.value(Qt::ImEnabled).toBool(), true);
2179 input->setReadOnly(true);
2180 QGuiApplication::sendEvent(input, &enabledQueryEvent);
2181 QCOMPARE(enabledQueryEvent.value(Qt::ImEnabled).toBool(), false);
2185 TextInput element should only handle left/right keys until the cursor reaches
2186 the extent of the text, then they should ignore the keys.
2189 void tst_qquicktextinput::navigation()
2191 QQuickView canvas(testFileUrl("navigation.qml"));
2193 canvas.requestActivateWindow();
2195 QVERIFY(canvas.rootObject() != 0);
2197 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
2199 QVERIFY(input != 0);
2200 input->setCursorPosition(0);
2201 QTRY_VERIFY(input->hasActiveFocus() == true);
2202 simulateKey(&canvas, Qt::Key_Left);
2203 QVERIFY(input->hasActiveFocus() == false);
2204 simulateKey(&canvas, Qt::Key_Right);
2205 QVERIFY(input->hasActiveFocus() == true);
2206 //QT-2944: If text is selected, ensure we deselect upon cursor motion
2207 input->setCursorPosition(input->text().length());
2208 input->select(0,input->text().length());
2209 QVERIFY(input->selectionStart() != input->selectionEnd());
2210 simulateKey(&canvas, Qt::Key_Right);
2211 QVERIFY(input->selectionStart() == input->selectionEnd());
2212 QVERIFY(input->selectionStart() == input->text().length());
2213 QVERIFY(input->hasActiveFocus() == true);
2214 simulateKey(&canvas, Qt::Key_Right);
2215 QVERIFY(input->hasActiveFocus() == false);
2216 simulateKey(&canvas, Qt::Key_Left);
2217 QVERIFY(input->hasActiveFocus() == true);
2219 // Up and Down should NOT do Home/End, even on Mac OS X (QTBUG-10438).
2220 input->setCursorPosition(2);
2221 QCOMPARE(input->cursorPosition(),2);
2222 simulateKey(&canvas, Qt::Key_Up);
2223 QCOMPARE(input->cursorPosition(),2);
2224 simulateKey(&canvas, Qt::Key_Down);
2225 QCOMPARE(input->cursorPosition(),2);
2227 // Test left and right navigation works if the TextInput is empty (QTBUG-25447).
2228 input->setText(QString());
2229 QCOMPARE(input->cursorPosition(), 0);
2230 simulateKey(&canvas, Qt::Key_Right);
2231 QCOMPARE(input->hasActiveFocus(), false);
2232 simulateKey(&canvas, Qt::Key_Left);
2233 QCOMPARE(input->hasActiveFocus(), true);
2234 simulateKey(&canvas, Qt::Key_Left);
2235 QCOMPARE(input->hasActiveFocus(), false);
2238 void tst_qquicktextinput::navigation_RTL()
2240 QQuickView canvas(testFileUrl("navigation.qml"));
2242 canvas.requestActivateWindow();
2244 QVERIFY(canvas.rootObject() != 0);
2246 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
2248 QVERIFY(input != 0);
2249 const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
2250 input->setText(QString::fromUtf16(arabic_str, 11));
2252 input->setCursorPosition(0);
2253 QTRY_VERIFY(input->hasActiveFocus() == true);
2256 simulateKey(&canvas, Qt::Key_Right);
2257 QVERIFY(input->hasActiveFocus() == false);
2260 simulateKey(&canvas, Qt::Key_Left);
2261 QVERIFY(input->hasActiveFocus() == true);
2263 input->setCursorPosition(input->text().length());
2264 QVERIFY(input->hasActiveFocus() == true);
2267 simulateKey(&canvas, Qt::Key_Left);
2268 QVERIFY(input->hasActiveFocus() == false);
2271 simulateKey(&canvas, Qt::Key_Right);
2272 QVERIFY(input->hasActiveFocus() == true);
2275 void tst_qquicktextinput::copyAndPaste() {
2276 #ifndef QT_NO_CLIPBOARD
2280 PasteboardRef pasteboard;
2281 OSStatus status = PasteboardCreate(0, &pasteboard);
2282 if (status == noErr)
2283 CFRelease(pasteboard);
2285 QSKIP("This machine doesn't support the clipboard");
2289 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
2290 QQmlComponent textInputComponent(&engine);
2291 textInputComponent.setData(componentStr.toLatin1(), QUrl());
2292 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
2293 QVERIFY(textInput != 0);
2296 QCOMPARE(textInput->text().length(), 12);
2297 textInput->select(0, textInput->text().length());
2299 QCOMPARE(textInput->selectedText(), QString("Hello world!"));
2300 QCOMPARE(textInput->selectedText().length(), 12);
2301 textInput->setCursorPosition(0);
2302 QVERIFY(textInput->canPaste());
2304 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2305 QCOMPARE(textInput->text().length(), 24);
2308 QVERIFY(textInput->canPaste());
2309 textInput->setReadOnly(true);
2310 QVERIFY(!textInput->canPaste());
2311 textInput->setReadOnly(false);
2312 QVERIFY(textInput->canPaste());
2315 textInput->setCursorPosition(0);
2316 textInput->selectWord();
2317 QCOMPARE(textInput->selectedText(), QString("Hello"));
2319 // select all and cut
2320 textInput->selectAll();
2322 QCOMPARE(textInput->text().length(), 0);
2324 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2325 QCOMPARE(textInput->text().length(), 24);
2327 // clear copy buffer
2328 QClipboard *clipboard = QGuiApplication::clipboard();
2331 QVERIFY(!textInput->canPaste());
2333 // test that copy functionality is disabled
2334 // when echo mode is set to hide text/password mode
2337 QQuickTextInput::EchoMode echoMode = QQuickTextInput::EchoMode(index);
2338 textInput->setEchoMode(echoMode);
2339 textInput->setText("My password");
2340 textInput->select(0, textInput->text().length());
2342 if (echoMode == QQuickTextInput::Normal) {
2343 QVERIFY(!clipboard->text().isEmpty());
2344 QCOMPARE(clipboard->text(), QString("My password"));
2347 QVERIFY(clipboard->text().isEmpty());
2356 void tst_qquicktextinput::copyAndPasteKeySequence() {
2357 #ifndef QT_NO_CLIPBOARD
2361 PasteboardRef pasteboard;
2362 OSStatus status = PasteboardCreate(0, &pasteboard);
2363 if (status == noErr)
2364 CFRelease(pasteboard);
2366 QSKIP("This machine doesn't support the clipboard");
2370 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\"; focus: true }";
2371 QQmlComponent textInputComponent(&engine);
2372 textInputComponent.setData(componentStr.toLatin1(), QUrl());
2373 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
2374 QVERIFY(textInput != 0);
2376 QQuickCanvas canvas;
2377 textInput->setParentItem(canvas.rootItem());
2379 canvas.requestActivateWindow();
2380 QTest::qWaitForWindowShown(&canvas);
2381 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
2384 QVERIFY(textInput->hasActiveFocus());
2385 QCOMPARE(textInput->text().length(), 12);
2386 textInput->select(0, textInput->text().length());
2387 simulateKeys(&canvas, QKeySequence::Copy);
2388 QCOMPARE(textInput->selectedText(), QString("Hello world!"));
2389 QCOMPARE(textInput->selectedText().length(), 12);
2390 textInput->setCursorPosition(0);
2391 QVERIFY(textInput->canPaste());
2392 simulateKeys(&canvas, QKeySequence::Paste);
2393 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2394 QCOMPARE(textInput->text().length(), 24);
2396 // select all and cut
2397 simulateKeys(&canvas, QKeySequence::SelectAll);
2398 simulateKeys(&canvas, QKeySequence::Cut);
2399 QCOMPARE(textInput->text().length(), 0);
2400 simulateKeys(&canvas, QKeySequence::Paste);
2401 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2402 QCOMPARE(textInput->text().length(), 24);
2404 // clear copy buffer
2405 QClipboard *clipboard = QGuiApplication::clipboard();
2408 QVERIFY(!textInput->canPaste());
2410 // test that copy functionality is disabled
2411 // when echo mode is set to hide text/password mode
2414 QQuickTextInput::EchoMode echoMode = QQuickTextInput::EchoMode(index);
2415 textInput->setEchoMode(echoMode);
2416 textInput->setText("My password");
2417 textInput->select(0, textInput->text().length());
2418 simulateKeys(&canvas, QKeySequence::Copy);
2419 if (echoMode == QQuickTextInput::Normal) {
2420 QVERIFY(!clipboard->text().isEmpty());
2421 QCOMPARE(clipboard->text(), QString("My password"));
2424 QVERIFY(clipboard->text().isEmpty());
2433 void tst_qquicktextinput::canPasteEmpty() {
2434 #ifndef QT_NO_CLIPBOARD
2436 QGuiApplication::clipboard()->clear();
2438 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
2439 QQmlComponent textInputComponent(&engine);
2440 textInputComponent.setData(componentStr.toLatin1(), QUrl());
2441 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
2442 QVERIFY(textInput != 0);
2444 bool cp = !textInput->isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
2445 QCOMPARE(textInput->canPaste(), cp);
2450 void tst_qquicktextinput::canPaste() {
2451 #ifndef QT_NO_CLIPBOARD
2453 QGuiApplication::clipboard()->setText("Some text");
2455 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
2456 QQmlComponent textInputComponent(&engine);
2457 textInputComponent.setData(componentStr.toLatin1(), QUrl());
2458 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
2459 QVERIFY(textInput != 0);
2461 bool cp = !textInput->isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
2462 QCOMPARE(textInput->canPaste(), cp);
2467 void tst_qquicktextinput::passwordCharacter()
2469 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\"; font.family: \"Helvetica\"; echoMode: TextInput.Password }";
2470 QQmlComponent textInputComponent(&engine);
2471 textInputComponent.setData(componentStr.toLatin1(), QUrl());
2472 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
2473 QVERIFY(textInput != 0);
2475 textInput->setPasswordCharacter("X");
2476 qreal implicitWidth = textInput->implicitWidth();
2477 textInput->setPasswordCharacter(".");
2479 // QTBUG-12383 content is updated and redrawn
2480 QVERIFY(textInput->implicitWidth() < implicitWidth);
2485 void tst_qquicktextinput::cursorDelegate_data()
2487 QTest::addColumn<QUrl>("source");
2488 QTest::newRow("out of line") << testFileUrl("cursorTest.qml");
2489 QTest::newRow("in line") << testFileUrl("cursorTestInline.qml");
2490 QTest::newRow("external") << testFileUrl("cursorTestExternal.qml");
2493 void tst_qquicktextinput::cursorDelegate()
2495 QFETCH(QUrl, source);
2496 QQuickView view(source);
2498 view.requestActivateWindow();
2499 QQuickTextInput *textInputObject = view.rootObject()->findChild<QQuickTextInput*>("textInputObject");
2500 QVERIFY(textInputObject != 0);
2501 // Delegate is created on demand, and so won't be available immediately. Focus in or
2502 // setCursorVisible(true) will trigger creation.
2503 QTRY_VERIFY(!textInputObject->findChild<QQuickItem*>("cursorInstance"));
2504 QVERIFY(!textInputObject->isCursorVisible());
2505 //Test Delegate gets created
2506 textInputObject->setFocus(true);
2507 QVERIFY(textInputObject->isCursorVisible());
2508 QQuickItem* delegateObject = textInputObject->findChild<QQuickItem*>("cursorInstance");
2509 QVERIFY(delegateObject);
2510 QCOMPARE(delegateObject->property("localProperty").toString(), QString("Hello"));
2511 //Test Delegate gets moved
2512 for (int i=0; i<= textInputObject->text().length(); i++) {
2513 textInputObject->setCursorPosition(i);
2514 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2515 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2517 textInputObject->setCursorPosition(0);
2518 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2519 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2521 // Test delegate gets moved on mouse press.
2522 textInputObject->setSelectByMouse(true);
2523 textInputObject->setCursorPosition(0);
2524 const QPoint point1 = textInputObject->positionToRectangle(5).center().toPoint();
2525 QTest::qWait(400); //ensure this isn't treated as a double-click
2526 QTest::mouseClick(&view, Qt::LeftButton, 0, point1);
2528 QTRY_VERIFY(textInputObject->cursorPosition() != 0);
2529 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2530 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2532 // Test delegate gets moved on mouse drag
2533 textInputObject->setCursorPosition(0);
2534 const QPoint point2 = textInputObject->positionToRectangle(10).center().toPoint();
2535 QTest::qWait(400); //ensure this isn't treated as a double-click
2536 QTest::mousePress(&view, Qt::LeftButton, 0, point1);
2537 QMouseEvent mv(QEvent::MouseMove, point2, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
2538 QGuiApplication::sendEvent(&view, &mv);
2539 QTest::mouseRelease(&view, Qt::LeftButton, 0, point2);
2541 QTRY_COMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2542 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2544 textInputObject->setReadOnly(true);
2545 textInputObject->setCursorPosition(0);
2546 QTest::qWait(400); //ensure this isn't treated as a double-click
2547 QTest::mouseClick(&view, Qt::LeftButton, 0, textInputObject->positionToRectangle(5).center().toPoint());
2549 QTRY_VERIFY(textInputObject->cursorPosition() != 0);
2550 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2551 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2553 textInputObject->setCursorPosition(0);
2554 QTest::qWait(400); //ensure this isn't treated as a double-click
2555 QTest::mouseClick(&view, Qt::LeftButton, 0, textInputObject->positionToRectangle(5).center().toPoint());
2557 QTRY_VERIFY(textInputObject->cursorPosition() != 0);
2558 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2559 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2561 textInputObject->setCursorPosition(0);
2562 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2563 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2565 textInputObject->setReadOnly(false);
2567 // Delegate moved when text is entered
2568 textInputObject->setText(QString());
2569 for (int i = 0; i < 20; ++i) {
2570 QTest::keyClick(&view, Qt::Key_A);
2571 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2572 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2575 // Delegate moved when text is entered by im.
2576 textInputObject->setText(QString());
2577 for (int i = 0; i < 20; ++i) {
2578 QInputMethodEvent event;
2579 event.setCommitString("w");
2580 QGuiApplication::sendEvent(textInputObject, &event);
2581 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2582 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2584 // Delegate moved when text is removed by im.
2585 for (int i = 19; i > 1; --i) {
2586 QInputMethodEvent event;
2587 event.setCommitString(QString(), -1, 1);
2588 QGuiApplication::sendEvent(textInputObject, &event);
2589 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2590 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2592 { // Delegate moved the text is changed in place by im.
2593 QInputMethodEvent event;
2594 event.setCommitString("i", -1, 1);
2595 QGuiApplication::sendEvent(textInputObject, &event);
2596 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2597 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2600 //Test Delegate gets deleted
2601 textInputObject->setCursorDelegate(0);
2602 QVERIFY(!textInputObject->findChild<QQuickItem*>("cursorInstance"));
2605 void tst_qquicktextinput::cursorVisible()
2607 QQuickTextInput input;
2608 input.componentComplete();
2609 QSignalSpy spy(&input, SIGNAL(cursorVisibleChanged(bool)));
2611 QQuickView view(testFileUrl("cursorVisible.qml"));
2613 view.requestActivateWindow();
2614 QTest::qWaitForWindowShown(&view);
2615 QTRY_COMPARE(&view, qGuiApp->focusWindow());
2617 QCOMPARE(input.isCursorVisible(), false);
2619 input.setCursorVisible(true);
2620 QCOMPARE(input.isCursorVisible(), true);
2621 QCOMPARE(spy.count(), 1);
2623 input.setCursorVisible(false);
2624 QCOMPARE(input.isCursorVisible(), false);
2625 QCOMPARE(spy.count(), 2);
2627 input.setFocus(true);
2628 QCOMPARE(input.isCursorVisible(), false);
2629 QCOMPARE(spy.count(), 2);
2631 input.setParentItem(view.rootObject());
2632 QCOMPARE(input.isCursorVisible(), true);
2633 QCOMPARE(spy.count(), 3);
2635 input.setFocus(false);
2636 QCOMPARE(input.isCursorVisible(), false);
2637 QCOMPARE(spy.count(), 4);
2639 input.setFocus(true);
2640 QCOMPARE(input.isCursorVisible(), true);
2641 QCOMPARE(spy.count(), 5);
2643 QWindow alternateView;
2644 alternateView.show();
2645 alternateView.requestActivateWindow();
2646 QTest::qWaitForWindowShown(&alternateView);
2648 QCOMPARE(input.isCursorVisible(), false);
2649 QCOMPARE(spy.count(), 6);
2651 view.requestActivateWindow();
2652 QTest::qWaitForWindowShown(&view);
2653 QCOMPARE(input.isCursorVisible(), true);
2654 QCOMPARE(spy.count(), 7);
2656 { // Cursor attribute with 0 length hides cursor.
2657 QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
2658 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
2659 QCoreApplication::sendEvent(&input, &ev);
2661 QCOMPARE(input.isCursorVisible(), false);
2662 QCOMPARE(spy.count(), 8);
2664 { // Cursor attribute with non zero length shows cursor.
2665 QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
2666 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant()));
2667 QCoreApplication::sendEvent(&input, &ev);
2669 QCOMPARE(input.isCursorVisible(), true);
2670 QCOMPARE(spy.count(), 9);
2672 { // If the cursor is hidden by the input method and the text is changed it should be visible again.
2673 QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
2674 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
2675 QCoreApplication::sendEvent(&input, &ev);
2677 QCOMPARE(input.isCursorVisible(), false);
2678 QCOMPARE(spy.count(), 10);
2680 input.setText("something");
2681 QCOMPARE(input.isCursorVisible(), true);
2682 QCOMPARE(spy.count(), 11);
2684 { // If the cursor is hidden by the input method and the cursor position is changed it should be visible again.
2685 QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
2686 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
2687 QCoreApplication::sendEvent(&input, &ev);
2689 QCOMPARE(input.isCursorVisible(), false);
2690 QCOMPARE(spy.count(), 12);
2692 input.setCursorPosition(5);
2693 QCOMPARE(input.isCursorVisible(), true);
2694 QCOMPARE(spy.count(), 13);
2697 void tst_qquicktextinput::cursorRectangle_data()
2699 const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
2701 QTest::addColumn<QString>("text");
2702 QTest::addColumn<int>("positionAtWidth");
2703 QTest::addColumn<int>("wrapPosition");
2704 QTest::addColumn<QString>("shortText");
2705 QTest::addColumn<bool>("leftToRight");
2707 QTest::newRow("left to right")
2708 << "Hello World!" << 5 << 11
2711 QTest::newRow("right to left")
2712 << QString::fromUtf16(arabic_str, lengthOf(arabic_str)) << 5 << 11
2713 << QString::fromUtf16(arabic_str, 3)
2717 void tst_qquicktextinput::cursorRectangle()
2719 QFETCH(QString, text);
2720 QFETCH(int, positionAtWidth);
2721 QFETCH(int, wrapPosition);
2722 QFETCH(QString, shortText);
2723 QFETCH(bool, leftToRight);
2725 QQuickTextInput input;
2726 input.setText(text);
2727 input.componentComplete();
2729 QTextLayout layout(text);
2730 layout.setFont(input.font());
2731 if (!qmlDisableDistanceField()) {
2733 option.setUseDesignMetrics(true);
2734 layout.setTextOption(option);
2736 layout.beginLayout();
2737 QTextLine line = layout.createLine();
2742 input.setWidth(line.cursorToX(positionAtWidth, QTextLine::Leading));
2744 input.setWidth(line.horizontalAdvance() - line.cursorToX(positionAtWidth, QTextLine::Leading));
2745 offset = line.horizontalAdvance() - input.width();
2747 input.setHeight(qCeil(line.height() * 3 / 2));
2751 for (int i = 0; i <= positionAtWidth; ++i) {
2752 input.setCursorPosition(i);
2753 r = input.cursorRectangle();
2755 QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
2756 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2757 QCOMPARE(input.positionToRectangle(i), r);
2760 // Check the cursor rectangle remains within the input bounding rect when auto scrolling.
2761 QCOMPARE(r.left(), leftToRight ? input.width() : 0);
2763 for (int i = positionAtWidth + 1; i < text.length(); ++i) {
2764 input.setCursorPosition(i);
2765 QCOMPARE(r, input.cursorRectangle());
2766 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2767 QCOMPARE(input.positionToRectangle(i), r);
2770 for (int i = text.length() - 2; i >= 0; --i) {
2771 input.setCursorPosition(i);
2772 r = input.cursorRectangle();
2773 QCOMPARE(r.top(), 0.);
2775 QVERIFY(r.right() >= 0);
2777 QVERIFY(r.left() <= input.width());
2779 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2780 QCOMPARE(input.positionToRectangle(i), r);
2783 // Check position with word wrap.
2784 input.setWrapMode(QQuickTextInput::WordWrap);
2785 input.setAutoScroll(false);
2786 for (int i = 0; i < wrapPosition; ++i) {
2787 input.setCursorPosition(i);
2788 r = input.cursorRectangle();
2790 QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
2791 QCOMPARE(r.top(), 0.);
2792 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2793 QCOMPARE(input.positionToRectangle(i), r);
2796 input.setCursorPosition(wrapPosition);
2797 r = input.cursorRectangle();
2799 QCOMPARE(r.left(), 0.);
2801 QCOMPARE(r.left(), input.width());
2803 QVERIFY(r.top() >= line.height() - 1);
2804 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2805 QCOMPARE(input.positionToRectangle(11), r);
2807 for (int i = wrapPosition + 1; i < text.length(); ++i) {
2808 input.setCursorPosition(i);
2809 r = input.cursorRectangle();
2810 QVERIFY(r.top() >= line.height() - 1);
2811 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2812 QCOMPARE(input.positionToRectangle(i), r);
2815 // Check vertical scrolling with word wrap.
2816 input.setAutoScroll(true);
2817 for (int i = 0; i <= positionAtWidth; ++i) {
2818 input.setCursorPosition(i);
2819 r = input.cursorRectangle();
2821 QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
2822 QCOMPARE(r.top(), 0.);
2823 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2824 QCOMPARE(input.positionToRectangle(i), r);
2827 // Whitespace doesn't wrap, so scroll horizontally until the until the cursor
2828 // reaches the next non-whitespace character.
2829 QCOMPARE(r.left(), leftToRight ? input.width() : 0);
2830 for (int i = positionAtWidth + 1; i < wrapPosition && leftToRight; ++i) {
2831 input.setCursorPosition(i);
2832 QCOMPARE(r, input.cursorRectangle());
2833 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2834 QCOMPARE(input.positionToRectangle(i), r);
2837 input.setCursorPosition(wrapPosition);
2838 r = input.cursorRectangle();
2840 QCOMPARE(r.left(), 0.);
2842 QCOMPARE(r.left(), input.width());
2844 QVERIFY(r.bottom() >= input.height());
2845 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2846 QCOMPARE(input.positionToRectangle(11), r);
2848 for (int i = wrapPosition + 1; i < text.length(); ++i) {
2849 input.setCursorPosition(i);
2850 r = input.cursorRectangle();
2851 QVERIFY(r.bottom() >= input.height());
2852 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2853 QCOMPARE(input.positionToRectangle(i), r);
2856 for (int i = text.length() - 2; i >= wrapPosition; --i) {
2857 input.setCursorPosition(i);
2858 r = input.cursorRectangle();
2859 QVERIFY(r.bottom() >= input.height());
2860 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2861 QCOMPARE(input.positionToRectangle(i), r);
2864 input.setCursorPosition(wrapPosition - 1);
2865 r = input.cursorRectangle();
2866 QCOMPARE(r.top(), 0.);
2867 QCOMPARE(r.left(), leftToRight ? input.width() : 0);
2868 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2869 QCOMPARE(input.positionToRectangle(10), r);
2871 for (int i = wrapPosition - 2; i >= positionAtWidth + 1; --i) {
2872 input.setCursorPosition(i);
2873 QCOMPARE(r, input.cursorRectangle());
2874 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2875 QCOMPARE(input.positionToRectangle(i), r);
2878 for (int i = positionAtWidth; i >= 0; --i) {
2879 input.setCursorPosition(i);
2880 r = input.cursorRectangle();
2881 QCOMPARE(r.top(), 0.);
2882 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRectF(), r);
2883 QCOMPARE(input.positionToRectangle(i), r);
2886 input.setText(shortText);
2887 input.setHAlign(leftToRight ? QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft);
2888 r = input.cursorRectangle();
2889 QCOMPARE(r.left(), leftToRight ? input.width() : 0);
2892 void tst_qquicktextinput::readOnly()
2894 QQuickView canvas(testFileUrl("readOnly.qml"));
2896 canvas.requestActivateWindow();
2898 QVERIFY(canvas.rootObject() != 0);
2900 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
2902 QVERIFY(input != 0);
2903 QTRY_VERIFY(input->hasActiveFocus() == true);
2904 QVERIFY(input->isReadOnly() == true);
2905 QString initial = input->text();
2906 for (int k=Qt::Key_0; k<=Qt::Key_Z; k++)
2907 simulateKey(&canvas, k);
2908 simulateKey(&canvas, Qt::Key_Return);
2909 simulateKey(&canvas, Qt::Key_Space);
2910 simulateKey(&canvas, Qt::Key_Escape);
2911 QCOMPARE(input->text(), initial);
2913 input->setCursorPosition(3);
2914 input->setReadOnly(false);
2915 QCOMPARE(input->isReadOnly(), false);
2916 QCOMPARE(input->cursorPosition(), input->text().length());
2919 void tst_qquicktextinput::echoMode()
2921 QQuickView canvas(testFileUrl("echoMode.qml"));
2923 canvas.requestActivateWindow();
2924 QTest::qWaitForWindowShown(&canvas);
2925 QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
2927 QVERIFY(canvas.rootObject() != 0);
2929 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
2931 QVERIFY(input != 0);
2932 QTRY_VERIFY(input->hasActiveFocus() == true);
2933 QString initial = input->text();
2934 Qt::InputMethodHints ref;
2935 QCOMPARE(initial, QLatin1String("ABCDefgh"));
2936 QCOMPARE(input->echoMode(), QQuickTextInput::Normal);
2937 QCOMPARE(input->displayText(), input->text());
2939 ref &= ~Qt::ImhHiddenText;
2940 ref &= ~(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
2941 QCOMPARE((Qt::InputMethodHints) input->inputMethodQuery(Qt::ImHints).toInt(), ref);
2942 input->setEchoMode(QQuickTextInput::NoEcho);
2943 QCOMPARE(input->text(), initial);
2944 QCOMPARE(input->displayText(), QLatin1String(""));
2945 QCOMPARE(input->passwordCharacter(), QLatin1String("*"));
2947 ref |= Qt::ImhHiddenText;
2948 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
2949 QCOMPARE((Qt::InputMethodHints) input->inputMethodQuery(Qt::ImHints).toInt(), ref);
2950 input->setEchoMode(QQuickTextInput::Password);
2952 ref |= Qt::ImhHiddenText;
2953 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
2954 QCOMPARE(input->text(), initial);
2955 QCOMPARE(input->displayText(), QLatin1String("********"));
2956 QCOMPARE((Qt::InputMethodHints) input->inputMethodQuery(Qt::ImHints).toInt(), ref);
2957 // clearing input hints do not clear bits set by echo mode
2958 input->setInputMethodHints(Qt::ImhNone);
2959 QCOMPARE((Qt::InputMethodHints) input->inputMethodQuery(Qt::ImHints).toInt(), ref);
2960 input->setPasswordCharacter(QChar('Q'));
2961 QCOMPARE(input->passwordCharacter(), QLatin1String("Q"));
2962 QCOMPARE(input->text(), initial);
2963 QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
2964 input->setEchoMode(QQuickTextInput::PasswordEchoOnEdit);
2965 //PasswordEchoOnEdit
2966 ref &= ~Qt::ImhHiddenText;
2967 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
2968 QCOMPARE((Qt::InputMethodHints) input->inputMethodQuery(Qt::ImHints).toInt(), ref);
2969 QCOMPARE(input->text(), initial);
2970 QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
2971 QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("QQQQQQQQ"));
2972 QTest::keyPress(&canvas, Qt::Key_A);//Clearing previous entry is part of PasswordEchoOnEdit
2973 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
2974 QCOMPARE(input->text(), QLatin1String("a"));
2975 QCOMPARE(input->displayText(), QLatin1String("a"));
2976 QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("a"));
2977 input->setFocus(false);
2978 QVERIFY(input->hasActiveFocus() == false);
2979 QCOMPARE(input->displayText(), QLatin1String("Q"));
2980 QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("Q"));
2981 input->setFocus(true);
2982 QVERIFY(input->hasActiveFocus());
2983 QInputMethodEvent inputEvent;
2984 inputEvent.setCommitString(initial);
2985 QGuiApplication::sendEvent(input, &inputEvent);
2986 QCOMPARE(input->text(), initial);
2987 QCOMPARE(input->displayText(), initial);
2988 QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), initial);
2991 void tst_qquicktextinput::passwordEchoDelay()
2993 int maskDelay = qGuiApp->styleHints()->passwordMaskDelay();
2995 QSKIP("No mask delay in use");
2996 QQuickView canvas(testFileUrl("echoMode.qml"));
2998 canvas.requestActivateWindow();
2999 QTest::qWaitForWindowShown(&canvas);
3000 QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
3002 QVERIFY(canvas.rootObject() != 0);
3004 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
3007 QQuickItem *cursor = input->findChild<QQuickItem *>("cursor");
3010 QChar fillChar = QLatin1Char('*');
3012 input->setEchoMode(QQuickTextInput::Password);
3013 QCOMPARE(input->displayText(), QString(8, fillChar));
3014 input->setText(QString());
3015 QCOMPARE(input->displayText(), QString());
3017 QTest::keyPress(&canvas, '0');
3018 QTest::keyPress(&canvas, '1');
3019 QTest::keyPress(&canvas, '2');
3020 QCOMPARE(input->displayText(), QString(2, fillChar) + QLatin1Char('2'));
3021 QTest::keyPress(&canvas, '3');
3022 QTest::keyPress(&canvas, '4');
3023 QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
3024 QTest::keyPress(&canvas, Qt::Key_Backspace);
3025 QCOMPARE(input->displayText(), QString(4, fillChar));
3026 QTest::keyPress(&canvas, '4');
3027 QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
3028 QCOMPARE(input->cursorRectangle().topLeft(), cursor->pos());
3030 // Verify the last character entered is replaced by the fill character after a delay.
3031 // Also check the cursor position is updated to accomdate a size difference between
3032 // the fill character and the replaced character.
3033 QSignalSpy cursorSpy(input, SIGNAL(cursorRectangleChanged()));
3034 QTest::qWait(maskDelay);
3035 QTRY_COMPARE(input->displayText(), QString(5, fillChar));
3036 QCOMPARE(cursorSpy.count(), 1);
3037 QCOMPARE(input->cursorRectangle().topLeft(), cursor->pos());
3039 QTest::keyPress(&canvas, '5');
3040 QCOMPARE(input->displayText(), QString(5, fillChar) + QLatin1Char('5'));
3041 input->setFocus(false);
3042 QVERIFY(!input->hasFocus());
3043 QCOMPARE(input->displayText(), QString(6, fillChar));
3044 input->setFocus(true);
3045 QTRY_VERIFY(input->hasFocus());
3046 QCOMPARE(input->displayText(), QString(6, fillChar));
3047 QTest::keyPress(&canvas, '6');
3048 QCOMPARE(input->displayText(), QString(6, fillChar) + QLatin1Char('6'));
3050 QInputMethodEvent ev;
3051 ev.setCommitString(QLatin1String("7"));
3052 QGuiApplication::sendEvent(qGuiApp->focusObject(), &ev);
3053 QCOMPARE(input->displayText(), QString(7, fillChar) + QLatin1Char('7'));
3055 input->setCursorPosition(3);
3056 QCOMPARE(input->displayText(), QString(7, fillChar) + QLatin1Char('7'));
3057 QTest::keyPress(&canvas, 'a');
3058 QCOMPARE(input->displayText(), QString(3, fillChar) + QLatin1Char('a') + QString(5, fillChar));
3059 QTest::keyPress(&canvas, Qt::Key_Backspace);
3060 QCOMPARE(input->displayText(), QString(8, fillChar));
3064 void tst_qquicktextinput::simulateKey(QWindow *view, int key)
3066 QKeyEvent press(QKeyEvent::KeyPress, key, 0);
3067 QKeyEvent release(QKeyEvent::KeyRelease, key, 0);
3069 QGuiApplication::sendEvent(view, &press);
3070 QGuiApplication::sendEvent(view, &release);
3074 void tst_qquicktextinput::focusOnPress()
3076 QString componentStr =
3077 "import QtQuick 2.0\n"
3079 "property bool selectOnFocus: false\n"
3080 "width: 100; height: 50\n"
3081 "activeFocusOnPress: true\n"
3082 "text: \"Hello World\"\n"
3083 "onFocusChanged: { if (focus && selectOnFocus) selectAll() }"
3085 QQmlComponent texteditComponent(&engine);
3086 texteditComponent.setData(componentStr.toLatin1(), QUrl());
3087 QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput*>(texteditComponent.create());
3088 QVERIFY(textInputObject != 0);
3089 QCOMPARE(textInputObject->focusOnPress(), true);
3090 QCOMPARE(textInputObject->hasFocus(), false);
3092 QSignalSpy focusSpy(textInputObject, SIGNAL(focusChanged(bool)));
3093 QSignalSpy activeFocusSpy(textInputObject, SIGNAL(focusChanged(bool)));
3094 QSignalSpy activeFocusOnPressSpy(textInputObject, SIGNAL(activeFocusOnPressChanged(bool)));
3096 textInputObject->setFocusOnPress(true);
3097 QCOMPARE(textInputObject->focusOnPress(), true);
3098 QCOMPARE(activeFocusOnPressSpy.count(), 0);
3100 QQuickCanvas canvas;
3101 canvas.resize(100, 50);
3102 textInputObject->setParentItem(canvas.rootItem());
3104 canvas.requestActivateWindow();
3105 QTest::qWaitForWindowShown(&canvas);
3106 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
3108 QCOMPARE(textInputObject->hasFocus(), false);
3109 QCOMPARE(textInputObject->hasActiveFocus(), false);
3111 QPoint centerPoint(canvas.width()/2, canvas.height()/2);
3112 Qt::KeyboardModifiers noModifiers = 0;
3113 QTest::mousePress(&canvas, Qt::LeftButton, noModifiers, centerPoint);
3114 QGuiApplication::processEvents();
3115 QCOMPARE(textInputObject->hasFocus(), true);
3116 QCOMPARE(textInputObject->hasActiveFocus(), true);
3117 QCOMPARE(focusSpy.count(), 1);
3118 QCOMPARE(activeFocusSpy.count(), 1);
3119 QCOMPARE(textInputObject->selectedText(), QString());
3120 QTest::mouseRelease(&canvas, Qt::LeftButton, noModifiers, centerPoint);
3122 textInputObject->setFocusOnPress(false);
3123 QCOMPARE(textInputObject->focusOnPress(), false);
3124 QCOMPARE(activeFocusOnPressSpy.count(), 1);
3126 textInputObject->setFocus(false);
3127 QCOMPARE(textInputObject->hasFocus(), false);
3128 QCOMPARE(textInputObject->hasActiveFocus(), false);
3129 QCOMPARE(focusSpy.count(), 2);
3130 QCOMPARE(activeFocusSpy.count(), 2);
3132 // Wait for double click timeout to expire before clicking again.
3134 QTest::mousePress(&canvas, Qt::LeftButton, noModifiers, centerPoint);
3135 QGuiApplication::processEvents();
3136 QCOMPARE(textInputObject->hasFocus(), false);
3137 QCOMPARE(textInputObject->hasActiveFocus(), false);
3138 QCOMPARE(focusSpy.count(), 2);
3139 QCOMPARE(activeFocusSpy.count(), 2);
3140 QTest::mouseRelease(&canvas, Qt::LeftButton, noModifiers, centerPoint);
3142 textInputObject->setFocusOnPress(true);
3143 QCOMPARE(textInputObject->focusOnPress(), true);
3144 QCOMPARE(activeFocusOnPressSpy.count(), 2);
3146 // Test a selection made in the on(Active)FocusChanged handler isn't overwritten.
3147 textInputObject->setProperty("selectOnFocus", true);
3150 QTest::mousePress(&canvas, Qt::LeftButton, noModifiers, centerPoint);
3151 QGuiApplication::processEvents();
3152 QCOMPARE(textInputObject->hasFocus(), true);
3153 QCOMPARE(textInputObject->hasActiveFocus(), true);
3154 QCOMPARE(focusSpy.count(), 3);
3155 QCOMPARE(activeFocusSpy.count(), 3);
3156 QCOMPARE(textInputObject->selectedText(), textInputObject->text());
3157 QTest::mouseRelease(&canvas, Qt::LeftButton, noModifiers, centerPoint);
3160 void tst_qquicktextinput::openInputPanel()
3162 PlatformInputContext platformInputContext;
3163 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
3164 inputMethodPrivate->testContext = &platformInputContext;
3166 QQuickView view(testFileUrl("openInputPanel.qml"));
3168 view.requestActivateWindow();
3169 QTest::qWaitForWindowShown(&view);
3170 QTRY_COMPARE(&view, qGuiApp->focusWindow());
3172 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
3175 // check default values
3176 QVERIFY(input->focusOnPress());
3177 QVERIFY(!input->hasActiveFocus());
3178 QVERIFY(qApp->focusObject() != input);
3179 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3181 // input panel should open on focus
3182 QPoint centerPoint(view.width()/2, view.height()/2);
3183 Qt::KeyboardModifiers noModifiers = 0;
3184 QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
3185 QGuiApplication::processEvents();
3186 QVERIFY(input->hasActiveFocus());
3187 QCOMPARE(qApp->focusObject(), input);
3188 QCOMPARE(qApp->inputMethod()->isVisible(), true);
3189 QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
3191 // input panel should be re-opened when pressing already focused TextInput
3192 qApp->inputMethod()->hide();
3193 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3194 QVERIFY(input->hasActiveFocus());
3195 QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
3196 QGuiApplication::processEvents();
3197 QCOMPARE(qApp->inputMethod()->isVisible(), true);
3198 QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
3200 // input panel should stay visible if focus is lost to another text inputor
3201 QSignalSpy inputPanelVisibilitySpy(qApp->inputMethod(), SIGNAL(visibleChanged()));
3202 QQuickTextInput anotherInput;
3203 anotherInput.componentComplete();
3204 anotherInput.setParentItem(view.rootObject());
3205 anotherInput.setFocus(true);
3206 QCOMPARE(qApp->inputMethod()->isVisible(), true);
3207 QCOMPARE(qApp->focusObject(), qobject_cast<QObject*>(&anotherInput));
3208 QCOMPARE(inputPanelVisibilitySpy.count(), 0);
3210 anotherInput.setFocus(false);
3211 QVERIFY(qApp->focusObject() != &anotherInput);
3212 QCOMPARE(view.activeFocusItem(), view.rootItem());
3213 anotherInput.setFocus(true);
3215 qApp->inputMethod()->hide();
3217 // input panel should not be opened if TextInput is read only
3218 input->setReadOnly(true);
3219 input->setFocus(true);
3220 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3221 QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
3222 QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
3223 QGuiApplication::processEvents();
3224 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3226 // input panel should not be opened if focusOnPress is set to false
3227 input->setFocusOnPress(false);
3228 input->setFocus(false);
3229 input->setFocus(true);
3230 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3231 QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
3232 QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
3233 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3236 class MyTextInput : public QQuickTextInput
3239 MyTextInput(QQuickItem *parent = 0) : QQuickTextInput(parent)
3243 virtual QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data)
3246 return QQuickTextInput::updatePaintNode(node, data);
3251 void tst_qquicktextinput::setHAlignClearCache()
3255 input.setText("Hello world");
3256 input.setParentItem(view.rootItem());
3258 view.requestActivateWindow();
3259 QTest::qWaitForWindowShown(&view);
3261 QEXPECT_FAIL("", "QTBUG-23485", Abort);
3263 QTRY_COMPARE(input.nbPaint, 1);
3264 input.setHAlign(QQuickTextInput::AlignRight);
3265 //Changing the alignment should trigger a repaint
3266 QTRY_COMPARE(input.nbPaint, 2);
3269 void tst_qquicktextinput::focusOutClearSelection()
3272 QQuickTextInput input;
3273 QQuickTextInput input2;
3274 input.setText(QLatin1String("Hello world"));
3275 input.setFocus(true);
3276 input2.setParentItem(view.rootItem());
3277 input.setParentItem(view.rootItem());
3278 input.componentComplete();
3279 input2.componentComplete();
3281 view.requestActivateWindow();
3282 QTest::qWaitForWindowShown(&view);
3284 //The selection should work
3285 QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
3286 input2.setFocus(true);
3287 QGuiApplication::processEvents();
3288 //The input lost the focus selection should be cleared
3289 QTRY_COMPARE(input.selectedText(), QLatin1String(""));
3292 void tst_qquicktextinput::geometrySignals()
3294 QQmlComponent component(&engine, testFileUrl("geometrySignals.qml"));
3295 QObject *o = component.create();
3297 QCOMPARE(o->property("bindingWidth").toInt(), 400);
3298 QCOMPARE(o->property("bindingHeight").toInt(), 500);
3302 void tst_qquicktextinput::contentSize()
3304 QString componentStr = "import QtQuick 2.0\nTextInput { width: 75; height: 16; font.pixelSize: 10 }";
3305 QQmlComponent textComponent(&engine);
3306 textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
3307 QScopedPointer<QObject> object(textComponent.create());
3308 QQuickTextInput *textObject = qobject_cast<QQuickTextInput *>(object.data());
3310 QSignalSpy spy(textObject, SIGNAL(contentSizeChanged()));
3312 textObject->setText("The quick red fox jumped over the lazy brown dog");
3314 QVERIFY(textObject->contentWidth() > textObject->width());
3315 QVERIFY(textObject->contentHeight() < textObject->height());
3316 QCOMPARE(spy.count(), 1);
3318 textObject->setWrapMode(QQuickTextInput::WordWrap);
3319 QVERIFY(textObject->contentWidth() <= textObject->width());
3320 QVERIFY(textObject->contentHeight() > textObject->height());
3321 QCOMPARE(spy.count(), 2);
3323 textObject->setText("The quickredfoxjumpedoverthe lazy brown dog");
3325 QVERIFY(textObject->contentWidth() > textObject->width());
3326 QVERIFY(textObject->contentHeight() > textObject->height());
3327 QCOMPARE(spy.count(), 3);
3329 textObject->setText("The quick red fox jumped over the lazy brown dog");
3330 for (int w = 60; w < 120; ++w) {
3331 textObject->setWidth(w);
3332 QVERIFY(textObject->contentWidth() <= textObject->width());
3333 QVERIFY(textObject->contentHeight() > textObject->height());
3337 static void sendPreeditText(const QString &text, int cursor)
3339 QInputMethodEvent event(text, QList<QInputMethodEvent::Attribute>()
3340 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursor, text.length(), QVariant()));
3341 QCoreApplication::sendEvent(qGuiApp->focusObject(), &event);
3344 void tst_qquicktextinput::preeditAutoScroll()
3346 QString preeditText = "califragisiticexpialidocious!";
3348 QQuickView view(testFileUrl("preeditAutoScroll.qml"));
3350 view.requestActivateWindow();
3351 QTest::qWaitForWindowShown(&view);
3352 QTRY_COMPARE(&view, qGuiApp->focusWindow());
3353 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
3355 QVERIFY(input->hasActiveFocus());
3357 input->setWidth(input->implicitWidth());
3359 QSignalSpy cursorRectangleSpy(input, SIGNAL(cursorRectangleChanged()));
3360 int cursorRectangleChanges = 0;
3362 // test the text is scrolled so the preedit is visible.
3363 sendPreeditText(preeditText.mid(0, 3), 1);
3364 QVERIFY(evaluate<int>(input, QString("positionAt(0)")) != 0);
3365 QVERIFY(input->cursorRectangle().left() < input->boundingRect().width());
3366 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3368 // test the text is scrolled back when the preedit is removed.
3369 QInputMethodEvent imEvent;
3370 QCoreApplication::sendEvent(qGuiApp->focusObject(), &imEvent);
3371 QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(0)), 0);
3372 QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(input->width())), 5);
3373 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3375 QTextLayout layout(preeditText);
3376 layout.setFont(input->font());
3377 if (!qmlDisableDistanceField()) {
3379 option.setUseDesignMetrics(true);
3380 layout.setTextOption(option);
3382 layout.beginLayout();
3383 QTextLine line = layout.createLine();
3386 // test if the preedit is larger than the text input that the
3387 // character preceding the cursor is still visible.
3388 qreal x = input->positionToRectangle(0).x();
3389 for (int i = 0; i < 3; ++i) {
3390 sendPreeditText(preeditText, i + 1);
3391 int width = ceil(line.cursorToX(i, QTextLine::Trailing)) - floor(line.cursorToX(i));
3392 QVERIFY(input->cursorRectangle().right() >= width - 3);
3393 QVERIFY(input->positionToRectangle(0).x() < x);
3394 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3395 x = input->positionToRectangle(0).x();
3397 for (int i = 1; i >= 0; --i) {
3398 sendPreeditText(preeditText, i + 1);
3399 int width = ceil(line.cursorToX(i, QTextLine::Trailing)) - floor(line.cursorToX(i));
3400 QVERIFY(input->cursorRectangle().right() >= width - 3);
3401 QVERIFY(input->positionToRectangle(0).x() > x);
3402 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3403 x = input->positionToRectangle(0).x();
3406 // Test incrementing the preedit cursor doesn't cause further
3407 // scrolling when right most text is visible.
3408 sendPreeditText(preeditText, preeditText.length() - 3);
3409 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3410 x = input->positionToRectangle(0).x();
3411 for (int i = 2; i >= 0; --i) {
3412 sendPreeditText(preeditText, preeditText.length() - i);
3413 QCOMPARE(input->positionToRectangle(0).x(), x);
3414 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3416 for (int i = 1; i < 3; ++i) {
3417 sendPreeditText(preeditText, preeditText.length() - i);
3418 QCOMPARE(input->positionToRectangle(0).x(), x);
3419 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3422 // Test disabling auto scroll.
3423 QCoreApplication::sendEvent(qGuiApp->focusObject(), &imEvent);
3425 input->setAutoScroll(false);
3426 sendPreeditText(preeditText.mid(0, 3), 1);
3427 QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(0)), 0);
3428 QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(input->width())), 5);
3431 void tst_qquicktextinput::preeditCursorRectangle()
3433 QString preeditText = "super";
3435 QQuickView view(testFileUrl("inputMethodEvent.qml"));
3437 view.requestActivateWindow();
3438 QTest::qWaitForWindowShown(&view);
3439 QTRY_COMPARE(&view, qGuiApp->focusWindow());
3440 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
3443 QQuickItem *cursor = input->findChild<QQuickItem *>("cursor");
3448 QInputMethodQueryEvent query(Qt::ImCursorRectangle);
3449 QCoreApplication::sendEvent(qGuiApp->focusObject(), &query);
3450 QRectF previousRect = query.value(Qt::ImCursorRectangle).toRectF();
3452 // Verify that the micro focus rect is positioned the same for position 0 as
3453 // it would be if there was no preedit text.
3454 sendPreeditText(preeditText, 0);
3455 QCoreApplication::sendEvent(qGuiApp->focusObject(), &query);
3456 currentRect = query.value(Qt::ImCursorRectangle).toRectF();
3457 QCOMPARE(currentRect, previousRect);
3458 QCOMPARE(input->cursorRectangle(), currentRect);
3459 QCOMPARE(cursor->pos(), currentRect.topLeft());
3461 QSignalSpy inputSpy(input, SIGNAL(cursorRectangleChanged()));
3462 QSignalSpy panelSpy(qGuiApp->inputMethod(), SIGNAL(cursorRectangleChanged()));
3464 // Verify that the micro focus rect moves to the left as the cursor position
3466 for (int i = 1; i <= 5; ++i) {
3467 sendPreeditText(preeditText, i);
3468 QCoreApplication::sendEvent(qGuiApp->focusObject(), &query);
3469 currentRect = query.value(Qt::ImCursorRectangle).toRectF();
3470 QVERIFY(previousRect.left() < currentRect.left());
3471 QCOMPARE(input->cursorRectangle(), currentRect);
3472 QCOMPARE(cursor->pos(), currentRect.topLeft());
3473 QVERIFY(inputSpy.count() > 0); inputSpy.clear();
3474 QVERIFY(panelSpy.count() > 0); panelSpy.clear();
3475 previousRect = currentRect;
3478 // Verify that if the cursor rectangle is updated if the pre-edit text changes
3479 // but the (non-zero) cursor position is the same.
3482 sendPreeditText("wwwww", 5);
3483 QCoreApplication::sendEvent(qGuiApp->focusObject(), &query);
3484 currentRect = query.value(Qt::ImCursorRectangle).toRectF();
3485 QCOMPARE(input->cursorRectangle(), currentRect);
3486 QCOMPARE(cursor->pos(), currentRect.topLeft());
3487 QCOMPARE(inputSpy.count(), 1);
3488 QCOMPARE(panelSpy.count(), 1);
3490 // Verify that if there is no preedit cursor then the micro focus rect is the
3491 // same as it would be if it were positioned at the end of the preedit text.
3494 { QInputMethodEvent imEvent(preeditText, QList<QInputMethodEvent::Attribute>());
3495 QCoreApplication::sendEvent(qGuiApp->focusObject(), &imEvent); }
3496 QCoreApplication::sendEvent(qGuiApp->focusObject(), &query);
3497 currentRect = query.value(Qt::ImCursorRectangle).toRectF();
3498 QCOMPARE(currentRect, previousRect);
3499 QCOMPARE(input->cursorRectangle(), currentRect);
3500 QCOMPARE(cursor->pos(), currentRect.topLeft());
3501 QCOMPARE(inputSpy.count(), 1);
3502 QCOMPARE(panelSpy.count(), 1);
3505 void tst_qquicktextinput::inputContextMouseHandler()
3507 PlatformInputContext platformInputContext;
3508 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
3509 inputMethodPrivate->testContext = &platformInputContext;
3511 QString text = "supercalifragisiticexpialidocious!";
3512 QQuickView view(testFileUrl("inputContext.qml"));
3513 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
3516 input->setFocus(true);
3520 view.requestActivateWindow();
3521 QTest::qWaitForWindowShown(&view);
3522 QTRY_COMPARE(&view, qGuiApp->focusWindow());
3524 QTextLayout layout(text);
3525 layout.setFont(input->font());
3526 if (!qmlDisableDistanceField()) {
3528 option.setUseDesignMetrics(true);
3529 layout.setTextOption(option);
3531 layout.beginLayout();
3532 QTextLine line = layout.createLine();
3535 const qreal x = line.cursorToX(2, QTextLine::Leading);
3536 const qreal y = line.height() / 2;
3537 QPoint position = QPointF(x, y).toPoint();
3539 QInputMethodEvent inputEvent(text.mid(0, 5), QList<QInputMethodEvent::Attribute>());
3540 QGuiApplication::sendEvent(input, &inputEvent);
3542 QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, position);
3543 QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, position);
3544 QGuiApplication::processEvents();
3546 QCOMPARE(platformInputContext.m_action, QInputMethod::Click);
3547 QCOMPARE(platformInputContext.m_invokeActionCallCount, 1);
3548 QCOMPARE(platformInputContext.m_cursorPosition, 2);
3551 void tst_qquicktextinput::inputMethodComposing()
3553 QString text = "supercalifragisiticexpialidocious!";
3555 QQuickView view(testFileUrl("inputContext.qml"));
3557 view.requestActivateWindow();
3558 QTest::qWaitForWindowShown(&view);
3559 QTRY_COMPARE(&view, qGuiApp->focusWindow());
3560 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
3562 QSignalSpy spy(input, SIGNAL(inputMethodComposingChanged()));
3564 QCOMPARE(input->isInputMethodComposing(), false);
3566 QInputMethodEvent event(text.mid(3), QList<QInputMethodEvent::Attribute>());
3567 QGuiApplication::sendEvent(input, &event);
3569 QCOMPARE(input->isInputMethodComposing(), true);
3570 QCOMPARE(spy.count(), 1);
3573 QInputMethodEvent event(text.mid(12), QList<QInputMethodEvent::Attribute>());
3574 QGuiApplication::sendEvent(input, &event);
3576 QCOMPARE(spy.count(), 1);
3579 QInputMethodEvent event;
3580 QGuiApplication::sendEvent(input, &event);
3582 QCOMPARE(input->isInputMethodComposing(), false);
3583 QCOMPARE(spy.count(), 2);
3585 // Changing the text while not composing doesn't alter the composing state.
3586 input->setText(text.mid(0, 16));
3587 QCOMPARE(input->isInputMethodComposing(), false);
3588 QCOMPARE(spy.count(), 2);
3591 QInputMethodEvent event(text.mid(16), QList<QInputMethodEvent::Attribute>());
3592 QGuiApplication::sendEvent(input, &event);
3594 QCOMPARE(input->isInputMethodComposing(), true);
3595 QCOMPARE(spy.count(), 3);
3597 // Changing the text while composing cancels composition.
3598 input->setText(text.mid(0, 12));
3599 QCOMPARE(input->isInputMethodComposing(), false);
3600 QCOMPARE(spy.count(), 4);
3602 { // Preedit cursor positioned outside (empty) preedit; composing.
3603 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
3604 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, -2, 1, QVariant()));
3605 QGuiApplication::sendEvent(input, &event);
3607 QCOMPARE(input->isInputMethodComposing(), true);
3608 QCOMPARE(spy.count(), 5);
3611 { // Cursor hidden; composing
3612 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
3613 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
3614 QGuiApplication::sendEvent(input, &event);
3616 QCOMPARE(input->isInputMethodComposing(), true);
3617 QCOMPARE(spy.count(), 5);
3619 { // Default cursor attributes; composing.
3620 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
3621 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant()));
3622 QGuiApplication::sendEvent(input, &event);
3624 QCOMPARE(input->isInputMethodComposing(), true);
3625 QCOMPARE(spy.count(), 5);
3627 { // Selections are persisted: not composing
3628 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
3629 << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, -5, 4, QVariant()));
3630 QGuiApplication::sendEvent(input, &event);
3632 QCOMPARE(input->isInputMethodComposing(), false);
3633 QCOMPARE(spy.count(), 6);
3635 input->setCursorPosition(12);
3637 { // Formatting applied; composing.
3638 QTextCharFormat format;
3639 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
3640 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
3641 << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, -5, 4, format));
3642 QGuiApplication::sendEvent(input, &event);
3644 QCOMPARE(input->isInputMethodComposing(), true);
3645 QCOMPARE(spy.count(), 7);
3648 QInputMethodEvent event;
3649 QGuiApplication::sendEvent(input, &event);
3651 QCOMPARE(input->isInputMethodComposing(), false);
3652 QCOMPARE(spy.count(), 8);
3655 void tst_qquicktextinput::inputMethodUpdate()
3657 PlatformInputContext platformInputContext;
3658 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
3659 inputMethodPrivate->testContext = &platformInputContext;
3661 QQuickView view(testFileUrl("inputContext.qml"));
3663 view.requestActivateWindow();
3664 QTest::qWaitForWindowShown(&view);
3665 QTRY_COMPARE(&view, qGuiApp->focusWindow());
3666 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(view.rootObject());
3669 // text change even without cursor position change needs to trigger update
3670 input->setText("test");
3671 platformInputContext.clear();
3672 input->setText("xxxx");
3673 QVERIFY(platformInputContext.m_updateCallCount > 0);
3675 // input method event replacing text
3676 platformInputContext.clear();
3678 QInputMethodEvent inputMethodEvent;
3679 inputMethodEvent.setCommitString("y", -1, 1);
3680 QGuiApplication::sendEvent(input, &inputMethodEvent);
3682 QVERIFY(platformInputContext.m_updateCallCount > 0);
3684 // input method changing selection
3685 platformInputContext.clear();
3687 QList<QInputMethodEvent::Attribute> attributes;
3688 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 0, 2, QVariant());
3689 QInputMethodEvent inputMethodEvent("", attributes);
3690 QGuiApplication::sendEvent(input, &inputMethodEvent);
3692 QVERIFY(input->selectionStart() != input->selectionEnd());
3693 QVERIFY(platformInputContext.m_updateCallCount > 0);
3695 // programmatical selections trigger update
3696 platformInputContext.clear();
3698 QVERIFY(platformInputContext.m_updateCallCount > 0);
3701 platformInputContext.clear();
3702 QFont font = input->font();
3703 font.setBold(!font.bold());
3704 input->setFont(font);
3705 QVERIFY(platformInputContext.m_updateCallCount > 0);
3708 platformInputContext.clear();
3710 QInputMethodEvent inputMethodEvent;
3711 inputMethodEvent.setCommitString("y");
3712 QGuiApplication::sendEvent(input, &inputMethodEvent);
3714 QVERIFY(platformInputContext.m_updateCallCount > 0);
3716 // changing cursor position
3717 platformInputContext.clear();
3718 input->setCursorPosition(0);
3719 QVERIFY(platformInputContext.m_updateCallCount > 0);
3721 // read only disabled input method
3722 platformInputContext.clear();
3723 input->setReadOnly(true);
3724 QVERIFY(platformInputContext.m_updateCallCount > 0);
3725 input->setReadOnly(false);
3727 // no updates while no focus
3728 input->setFocus(false);
3729 platformInputContext.clear();
3730 input->setText("Foo");
3731 QCOMPARE(platformInputContext.m_updateCallCount, 0);
3732 input->setCursorPosition(1);
3733 QCOMPARE(platformInputContext.m_updateCallCount, 0);
3735 QCOMPARE(platformInputContext.m_updateCallCount, 0);
3736 input->setReadOnly(true);
3737 QCOMPARE(platformInputContext.m_updateCallCount, 0);
3740 void tst_qquicktextinput::cursorRectangleSize()
3742 QQuickView *canvas = new QQuickView(testFileUrl("positionAt.qml"));
3743 QVERIFY(canvas->rootObject() != 0);
3744 QQuickTextInput *textInput = qobject_cast<QQuickTextInput *>(canvas->rootObject());
3746 // make sure cursor rectangle is not at (0,0)
3747 textInput->setX(10);
3748 textInput->setY(10);
3749 textInput->setCursorPosition(3);
3750 QVERIFY(textInput != 0);
3751 textInput->setFocus(true);
3753 canvas->requestActivateWindow();
3754 QTest::qWaitForWindowShown(canvas);
3755 QTRY_VERIFY(qApp->focusObject());
3757 QInputMethodQueryEvent event(Qt::ImCursorRectangle);
3758 qApp->sendEvent(qApp->focusObject(), &event);
3759 QRectF cursorRectFromQuery = event.value(Qt::ImCursorRectangle).toRectF();
3761 QRectF cursorRectFromItem = textInput->cursorRectangle();
3762 QRectF cursorRectFromPositionToRectangle = textInput->positionToRectangle(textInput->cursorPosition());
3764 // item and input query cursor rectangles match
3765 QCOMPARE(cursorRectFromItem, cursorRectFromQuery);
3767 // item cursor rectangle and positionToRectangle calculations match
3768 QCOMPARE(cursorRectFromItem, cursorRectFromPositionToRectangle);
3770 // item-canvas transform and input item transform match
3771 QCOMPARE(QQuickItemPrivate::get(textInput)->itemToCanvasTransform(), qApp->inputMethod()->inputItemTransform());
3773 // input panel cursorRectangle property and tranformed item cursor rectangle match
3774 QRectF sceneCursorRect = QQuickItemPrivate::get(textInput)->itemToCanvasTransform().mapRect(cursorRectFromItem);
3775 QCOMPARE(sceneCursorRect, qApp->inputMethod()->cursorRectangle());
3780 void tst_qquicktextinput::tripleClickSelectsAll()
3782 QString qmlfile = testFile("positionAt.qml");
3783 QQuickView view(QUrl::fromLocalFile(qmlfile));
3785 view.requestActivateWindow();
3786 QTest::qWaitForWindowShown(&view);
3788 QTRY_COMPARE(&view, qGuiApp->focusWindow());
3790 QQuickTextInput* input = qobject_cast<QQuickTextInput*>(view.rootObject());
3793 QLatin1String hello("Hello world!");
3794 input->setSelectByMouse(true);
3795 input->setText(hello);
3797 // Clicking on the same point inside TextInput three times in a row
3798 // should trigger a triple click, thus selecting all the text.
3799 QPoint pointInside = input->pos().toPoint() + QPoint(2,2);
3800 QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
3801 QTest::mouseClick(&view, Qt::LeftButton, 0, pointInside);
3802 QGuiApplication::processEvents();
3803 QCOMPARE(input->selectedText(), hello);
3805 // Now it simulates user moving the mouse between the second and the third click.
3806 // In this situation, we don't expect a triple click.
3807 QPoint pointInsideButFar = QPoint(input->width(),input->height()) - QPoint(2,2);
3808 QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
3809 QTest::mouseClick(&view, Qt::LeftButton, 0, pointInsideButFar);
3810 QGuiApplication::processEvents();
3811 QVERIFY(input->selectedText().isEmpty());
3813 // And now we press the third click too late, so no triple click event is triggered.
3814 QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
3815 QGuiApplication::processEvents();
3816 QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 1);
3817 QTest::mouseClick(&view, Qt::LeftButton, 0, pointInside);
3818 QGuiApplication::processEvents();
3819 QVERIFY(input->selectedText().isEmpty());
3822 void tst_qquicktextinput::QTBUG_19956_data()
3824 QTest::addColumn<QString>("url");
3825 QTest::newRow("intvalidator") << "qtbug-19956int.qml";
3826 QTest::newRow("doublevalidator") << "qtbug-19956double.qml";
3830 void tst_qquicktextinput::getText_data()
3832 QTest::addColumn<QString>("text");
3833 QTest::addColumn<QString>("inputMask");
3834 QTest::addColumn<int>("start");
3835 QTest::addColumn<int>("end");
3836 QTest::addColumn<QString>("expectedText");
3838 QTest::newRow("all plain text")
3841 << 0 << standard.at(0).length()
3844 QTest::newRow("plain text sub string")
3848 << standard.at(0).mid(0, 12);
3850 QTest::newRow("plain text sub string reversed")
3854 << standard.at(0).mid(0, 12);
3856 QTest::newRow("plain text cropped beginning")
3860 << standard.at(0).mid(0, 4);
3862 QTest::newRow("plain text cropped end")
3865 << 23 << standard.at(0).length() + 8
3866 << standard.at(0).mid(23);
3868 QTest::newRow("plain text cropped beginning and end")
3871 << -9 << standard.at(0).length() + 4
3875 void tst_qquicktextinput::getText()
3877 QFETCH(QString, text);
3878 QFETCH(QString, inputMask);
3881 QFETCH(QString, expectedText);
3883 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
3884 QQmlComponent textInputComponent(&engine);
3885 textInputComponent.setData(componentStr.toLatin1(), QUrl());
3886 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
3887 QVERIFY(textInput != 0);
3889 QCOMPARE(textInput->getText(start, end), expectedText);
3892 void tst_qquicktextinput::insert_data()
3894 QTest::addColumn<QString>("text");
3895 QTest::addColumn<QString>("inputMask");
3896 QTest::addColumn<int>("selectionStart");
3897 QTest::addColumn<int>("selectionEnd");
3898 QTest::addColumn<int>("insertPosition");
3899 QTest::addColumn<QString>("insertText");
3900 QTest::addColumn<QString>("expectedText");
3901 QTest::addColumn<int>("expectedSelectionStart");
3902 QTest::addColumn<int>("expectedSelectionEnd");
3903 QTest::addColumn<int>("expectedCursorPosition");
3904 QTest::addColumn<bool>("selectionChanged");
3905 QTest::addColumn<bool>("cursorPositionChanged");
3907 QTest::newRow("at cursor position (beginning)")
3912 << QString("Hello") + standard.at(0)
3916 QTest::newRow("at cursor position (end)")
3919 << standard.at(0).length() << standard.at(0).length() << standard.at(0).length()
3921 << standard.at(0) + QString("Hello")
3922 << standard.at(0).length() + 5 << standard.at(0).length() + 5 << standard.at(0).length() + 5
3925 QTest::newRow("at cursor position (middle)")
3930 << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
3934 QTest::newRow("after cursor position (beginning)")
3939 << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
3943 QTest::newRow("before cursor position (end)")
3946 << standard.at(0).length() << standard.at(0).length() << 18
3948 << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
3949 << standard.at(0).length() + 5 << standard.at(0).length() + 5 << standard.at(0).length() + 5
3952 QTest::newRow("before cursor position (middle)")
3957 << QString("Hello") + standard.at(0)
3961 QTest::newRow("after cursor position (middle)")
3964 << 18 << 18 << standard.at(0).length()
3966 << standard.at(0) + QString("Hello")
3970 QTest::newRow("before selection")
3975 << QString("Hello") + standard.at(0)
3979 QTest::newRow("before reversed selection")
3984 << QString("Hello") + standard.at(0)
3988 QTest::newRow("after selection")
3991 << 14 << 19 << standard.at(0).length()
3993 << standard.at(0) + QString("Hello")
3997 QTest::newRow("after reversed selection")
4000 << 19 << 14 << standard.at(0).length()
4002 << standard.at(0) + QString("Hello")
4006 QTest::newRow("into selection")
4011 << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
4015 QTest::newRow("into reversed selection")
4020 << standard.at(0).mid(0, 18) + QString("Hello") + standard.at(0).mid(18)
4024 QTest::newRow("rich text into plain text")
4028 << QString("<b>Hello</b>")
4029 << QString("<b>Hello</b>") + standard.at(0)
4033 QTest::newRow("before start")
4042 QTest::newRow("past end")
4045 << 0 << 0 << standard.at(0).length() + 3
4051 const QString inputMask = "009.009.009.009";
4052 const QString ip = "192.168.2.14";
4054 QTest::newRow("mask: at cursor position (beginning)")
4059 << QString("125.168.2.14")
4063 QTest::newRow("mask: at cursor position (end)")
4066 << inputMask.length() << inputMask.length() << inputMask.length()
4069 << inputMask.length() << inputMask.length() << inputMask.length()
4072 QTest::newRow("mask: at cursor position (middle)")
4077 << QString("192.167.5.24")
4081 QTest::newRow("mask: after cursor position (beginning)")
4086 << QString("192.167.5.24")
4090 QTest::newRow("mask: before cursor position (end)")
4093 << inputMask.length() << inputMask.length() << 6
4095 << QString("192.167.5.24")
4096 << inputMask.length() << inputMask.length() << inputMask.length()
4099 QTest::newRow("mask: before cursor position (middle)")
4104 << QString("125.168.2.14")
4108 QTest::newRow("mask: after cursor position (middle)")
4117 QTest::newRow("mask: before selection")
4122 << QString("125.168.2.14")
4126 QTest::newRow("mask: before reversed selection")
4131 << QString("125.168.2.14")
4135 QTest::newRow("mask: after selection")
4144 QTest::newRow("mask: after reversed selection")
4153 QTest::newRow("mask: into selection")
4158 << QString("192.167.5.24")
4162 QTest::newRow("mask: into reversed selection")
4167 << QString("192.167.5.24")
4171 QTest::newRow("mask: before start")
4180 QTest::newRow("mask: past end")
4183 << 0 << 0 << ip.length() + 3
4189 QTest::newRow("mask: invalid characters")
4194 << QString("192.168.2.14")
4198 QTest::newRow("mask: mixed validity")
4202 << QString("a1b2c5")
4203 << QString("125.168.2.14")
4208 void tst_qquicktextinput::insert()
4210 QFETCH(QString, text);
4211 QFETCH(QString, inputMask);
4212 QFETCH(int, selectionStart);
4213 QFETCH(int, selectionEnd);
4214 QFETCH(int, insertPosition);
4215 QFETCH(QString, insertText);
4216 QFETCH(QString, expectedText);
4217 QFETCH(int, expectedSelectionStart);
4218 QFETCH(int, expectedSelectionEnd);
4219 QFETCH(int, expectedCursorPosition);
4220 QFETCH(bool, selectionChanged);
4221 QFETCH(bool, cursorPositionChanged);
4223 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
4224 QQmlComponent textInputComponent(&engine);
4225 textInputComponent.setData(componentStr.toLatin1(), QUrl());
4226 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
4227 QVERIFY(textInput != 0);
4229 textInput->select(selectionStart, selectionEnd);
4231 QSignalSpy selectionSpy(textInput, SIGNAL(selectedTextChanged()));
4232 QSignalSpy selectionStartSpy(textInput, SIGNAL(selectionStartChanged()));
4233 QSignalSpy selectionEndSpy(textInput, SIGNAL(selectionEndChanged()));
4234 QSignalSpy textSpy(textInput, SIGNAL(textChanged()));
4235 QSignalSpy cursorPositionSpy(textInput, SIGNAL(cursorPositionChanged()));
4237 textInput->insert(insertPosition, insertText);
4239 QCOMPARE(textInput->text(), expectedText);
4240 QCOMPARE(textInput->length(), inputMask.isEmpty() ? expectedText.length() : inputMask.length());
4242 QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
4243 QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
4244 QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);
4246 if (selectionStart > selectionEnd)
4247 qSwap(selectionStart, selectionEnd);
4249 QCOMPARE(selectionSpy.count() > 0, selectionChanged);
4250 QCOMPARE(selectionStartSpy.count() > 0, selectionStart != expectedSelectionStart);
4251 QCOMPARE(selectionEndSpy.count() > 0, selectionEnd != expectedSelectionEnd);
4252 QCOMPARE(textSpy.count() > 0, text != expectedText);
4253 QCOMPARE(cursorPositionSpy.count() > 0, cursorPositionChanged);
4256 void tst_qquicktextinput::remove_data()
4258 QTest::addColumn<QString>("text");
4259 QTest::addColumn<QString>("inputMask");
4260 QTest::addColumn<int>("selectionStart");
4261 QTest::addColumn<int>("selectionEnd");
4262 QTest::addColumn<int>("removeStart");
4263 QTest::addColumn<int>("removeEnd");
4264 QTest::addColumn<QString>("expectedText");
4265 QTest::addColumn<int>("expectedSelectionStart");
4266 QTest::addColumn<int>("expectedSelectionEnd");
4267 QTest::addColumn<int>("expectedCursorPosition");
4268 QTest::addColumn<bool>("selectionChanged");
4269 QTest::addColumn<bool>("cursorPositionChanged");
4271 QTest::newRow("from cursor position (beginning)")
4276 << standard.at(0).mid(5)
4280 QTest::newRow("to cursor position (beginning)")
4285 << standard.at(0).mid(5)
4289 QTest::newRow("to cursor position (end)")
4292 << standard.at(0).length() << standard.at(0).length()
4293 << standard.at(0).length() << standard.at(0).length() - 5
4294 << standard.at(0).mid(0, standard.at(0).length() - 5)
4295 << standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
4298 QTest::newRow("to cursor position (end)")
4301 << standard.at(0).length() << standard.at(0).length()
4302 << standard.at(0).length() - 5 << standard.at(0).length()
4303 << standard.at(0).mid(0, standard.at(0).length() - 5)
4304 << standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
4307 QTest::newRow("from cursor position (middle)")
4312 << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
4316 QTest::newRow("to cursor position (middle)")
4321 << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
4325 QTest::newRow("after cursor position (beginning)")
4330 << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
4334 QTest::newRow("before cursor position (end)")
4337 << standard.at(0).length() << standard.at(0).length()
4339 << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
4340 << standard.at(0).length() - 5 << standard.at(0).length() - 5 << standard.at(0).length() - 5
4343 QTest::newRow("before cursor position (middle)")
4348 << standard.at(0).mid(5)
4352 QTest::newRow("after cursor position (middle)")
4357 << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
4361 QTest::newRow("before selection")
4366 << standard.at(0).mid(5)
4370 QTest::newRow("before reversed selection")
4375 << standard.at(0).mid(5)
4379 QTest::newRow("after selection")
4383 << standard.at(0).length() - 5 << standard.at(0).length()
4384 << standard.at(0).mid(0, standard.at(0).length() - 5)
4388 QTest::newRow("after reversed selection")
4392 << standard.at(0).length() - 5 << standard.at(0).length()
4393 << standard.at(0).mid(0, standard.at(0).length() - 5)
4397 QTest::newRow("from selection")
4402 << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
4406 QTest::newRow("from reversed selection")
4411 << standard.at(0).mid(0, 18) + standard.at(0).mid(23)
4415 QTest::newRow("cropped beginning")
4420 << standard.at(0).mid(4)
4424 QTest::newRow("cropped end")
4428 << 23 << standard.at(0).length() + 8
4429 << standard.at(0).mid(0, 23)
4433 QTest::newRow("cropped beginning and end")
4437 << -9 << standard.at(0).length() + 4
4442 const QString inputMask = "009.009.009.009";
4443 const QString ip = "192.168.2.14";
4445 QTest::newRow("mask: from cursor position")
4450 << QString("192.16..14")
4454 QTest::newRow("mask: to cursor position")
4459 << QString("19.8.2.14")
4463 QTest::newRow("mask: before cursor position")
4468 << QString("2.168.2.14")
4472 QTest::newRow("mask: after cursor position")
4477 << QString("192.168.2.")
4481 QTest::newRow("mask: before selection")
4486 << QString("2.168.2.14")
4490 QTest::newRow("mask: before reversed selection")
4495 << QString("2.168.2.14")
4499 QTest::newRow("mask: after selection")
4504 << QString("192.168.2.")
4508 QTest::newRow("mask: after reversed selection")
4513 << QString("192.168.2.")
4517 QTest::newRow("mask: from selection")
4522 << QString("192.168..14")
4526 QTest::newRow("mask: from reversed selection")
4531 << QString("192.168..14")
4535 QTest::newRow("mask: cropped beginning")
4540 << QString(".168.2.14")
4544 QTest::newRow("mask: cropped end")
4549 << QString("192.168.2.1")
4553 QTest::newRow("mask: cropped beginning and end")
4563 void tst_qquicktextinput::remove()
4565 QFETCH(QString, text);
4566 QFETCH(QString, inputMask);
4567 QFETCH(int, selectionStart);
4568 QFETCH(int, selectionEnd);
4569 QFETCH(int, removeStart);
4570 QFETCH(int, removeEnd);
4571 QFETCH(QString, expectedText);
4572 QFETCH(int, expectedSelectionStart);
4573 QFETCH(int, expectedSelectionEnd);
4574 QFETCH(int, expectedCursorPosition);
4575 QFETCH(bool, selectionChanged);
4576 QFETCH(bool, cursorPositionChanged);
4578 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
4579 QQmlComponent textInputComponent(&engine);
4580 textInputComponent.setData(componentStr.toLatin1(), QUrl());
4581 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
4582 QVERIFY(textInput != 0);
4584 textInput->select(selectionStart, selectionEnd);
4586 QSignalSpy selectionSpy(textInput, SIGNAL(selectedTextChanged()));
4587 QSignalSpy selectionStartSpy(textInput, SIGNAL(selectionStartChanged()));
4588 QSignalSpy selectionEndSpy(textInput, SIGNAL(selectionEndChanged()));
4589 QSignalSpy textSpy(textInput, SIGNAL(textChanged()));
4590 QSignalSpy cursorPositionSpy(textInput, SIGNAL(cursorPositionChanged()));
4592 textInput->remove(removeStart, removeEnd);
4594 QCOMPARE(textInput->text(), expectedText);
4595 QCOMPARE(textInput->length(), inputMask.isEmpty() ? expectedText.length() : inputMask.length());
4597 if (selectionStart > selectionEnd) //
4598 qSwap(selectionStart, selectionEnd);
4600 QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
4601 QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
4602 QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);
4604 QCOMPARE(selectionSpy.count() > 0, selectionChanged);
4605 QCOMPARE(selectionStartSpy.count() > 0, selectionStart != expectedSelectionStart);
4606 QCOMPARE(selectionEndSpy.count() > 0, selectionEnd != expectedSelectionEnd);
4607 QCOMPARE(textSpy.count() > 0, text != expectedText);
4609 if (cursorPositionChanged) //
4610 QVERIFY(cursorPositionSpy.count() > 0);
4613 void tst_qquicktextinput::keySequence_data()
4615 QTest::addColumn<QString>("text");
4616 QTest::addColumn<QKeySequence>("sequence");
4617 QTest::addColumn<int>("selectionStart");
4618 QTest::addColumn<int>("selectionEnd");
4619 QTest::addColumn<int>("cursorPosition");
4620 QTest::addColumn<QString>("expectedText");
4621 QTest::addColumn<QString>("selectedText");
4622 QTest::addColumn<QQuickTextInput::EchoMode>("echoMode");
4623 QTest::addColumn<Qt::Key>("layoutDirection");
4625 // standard[0] == "the [4]quick [10]brown [16]fox [20]jumped [27]over [32]the [36]lazy [41]dog"
4627 QTest::newRow("select all")
4628 << standard.at(0) << QKeySequence(QKeySequence::SelectAll) << 0 << 0
4629 << 44 << standard.at(0) << standard.at(0)
4630 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4631 QTest::newRow("select start of line")
4632 << standard.at(0) << QKeySequence(QKeySequence::SelectStartOfLine) << 5 << 5
4633 << 0 << standard.at(0) << standard.at(0).mid(0, 5)
4634 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4635 QTest::newRow("select start of block")
4636 << standard.at(0) << QKeySequence(QKeySequence::SelectStartOfBlock) << 5 << 5
4637 << 0 << standard.at(0) << standard.at(0).mid(0, 5)
4638 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4639 QTest::newRow("select end of line")
4640 << standard.at(0) << QKeySequence(QKeySequence::SelectEndOfLine) << 5 << 5
4641 << 44 << standard.at(0) << standard.at(0).mid(5)
4642 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4643 QTest::newRow("select end of document") // ### Not handled.
4644 << standard.at(0) << QKeySequence(QKeySequence::SelectEndOfDocument) << 3 << 3
4645 << 3 << standard.at(0) << QString()
4646 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4647 QTest::newRow("select end of block")
4648 << standard.at(0) << QKeySequence(QKeySequence::SelectEndOfBlock) << 18 << 18
4649 << 44 << standard.at(0) << standard.at(0).mid(18)
4650 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4651 QTest::newRow("delete end of line")
4652 << standard.at(0) << QKeySequence(QKeySequence::DeleteEndOfLine) << 24 << 24
4653 << 24 << standard.at(0).mid(0, 24) << QString()
4654 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4655 QTest::newRow("move to start of line")
4656 << standard.at(0) << QKeySequence(QKeySequence::MoveToStartOfLine) << 31 << 31
4657 << 0 << standard.at(0) << QString()
4658 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4659 QTest::newRow("move to start of block")
4660 << standard.at(0) << QKeySequence(QKeySequence::MoveToStartOfBlock) << 25 << 25
4661 << 0 << standard.at(0) << QString()
4662 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4663 QTest::newRow("move to next char")
4664 << standard.at(0) << QKeySequence(QKeySequence::MoveToNextChar) << 12 << 12
4665 << 13 << standard.at(0) << QString()
4666 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4667 QTest::newRow("move to previous char (ltr)")
4668 << standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 3
4669 << 2 << standard.at(0) << QString()
4670 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4671 QTest::newRow("move to previous char (rtl)")
4672 << standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 3
4673 << 4 << standard.at(0) << QString()
4674 << QQuickTextInput::Normal << Qt::Key_Direction_R;
4675 QTest::newRow("move to previous char with selection")
4676 << standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 7
4677 << 3 << standard.at(0) << QString()
4678 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4679 QTest::newRow("select next char (ltr)")
4680 << standard.at(0) << QKeySequence(QKeySequence::SelectNextChar) << 23 << 23
4681 << 24 << standard.at(0) << standard.at(0).mid(23, 1)
4682 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4683 QTest::newRow("select next char (rtl)")
4684 << standard.at(0) << QKeySequence(QKeySequence::SelectNextChar) << 23 << 23
4685 << 22 << standard.at(0) << standard.at(0).mid(22, 1)
4686 << QQuickTextInput::Normal << Qt::Key_Direction_R;
4687 QTest::newRow("select previous char (ltr)")
4688 << standard.at(0) << QKeySequence(QKeySequence::SelectPreviousChar) << 19 << 19
4689 << 18 << standard.at(0) << standard.at(0).mid(18, 1)
4690 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4691 QTest::newRow("select previous char (rtl)")
4692 << standard.at(0) << QKeySequence(QKeySequence::SelectPreviousChar) << 19 << 19
4693 << 20 << standard.at(0) << standard.at(0).mid(19, 1)
4694 << QQuickTextInput::Normal << Qt::Key_Direction_R;
4695 QTest::newRow("move to next word (ltr)")
4696 << standard.at(0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
4697 << 10 << standard.at(0) << QString()
4698 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4699 QTest::newRow("move to next word (rtl)")
4700 << standard.at(0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
4701 << 4 << standard.at(0) << QString()
4702 << QQuickTextInput::Normal << Qt::Key_Direction_R;
4703 QTest::newRow("move to next word (password,ltr)")
4704 << standard.at(0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
4705 << 44 << standard.at(0) << QString()
4706 << QQuickTextInput::Password << Qt::Key_Direction_L;
4707 QTest::newRow("move to next word (password,rtl)")
4708 << standard.at(0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
4709 << 0 << standard.at(0) << QString()
4710 << QQuickTextInput::Password << Qt::Key_Direction_R;
4711 QTest::newRow("move to previous word (ltr)")
4712 << standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
4713 << 4 << standard.at(0) << QString()
4714 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4715 QTest::newRow("move to previous word (rlt)")
4716 << standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
4717 << 10 << standard.at(0) << QString()
4718 << QQuickTextInput::Normal << Qt::Key_Direction_R;
4719 QTest::newRow("move to previous word (password,ltr)")
4720 << standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
4721 << 0 << standard.at(0) << QString()
4722 << QQuickTextInput::Password << Qt::Key_Direction_L;
4723 QTest::newRow("move to previous word (password,rtl)")
4724 << standard.at(0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
4725 << 44 << standard.at(0) << QString()
4726 << QQuickTextInput::Password << Qt::Key_Direction_R;
4727 QTest::newRow("select next word")
4728 << standard.at(0) << QKeySequence(QKeySequence::SelectNextWord) << 11 << 11
4729 << 16 << standard.at(0) << standard.at(0).mid(11, 5)
4730 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4731 QTest::newRow("select previous word")
4732 << standard.at(0) << QKeySequence(QKeySequence::SelectPreviousWord) << 11 << 11
4733 << 10 << standard.at(0) << standard.at(0).mid(10, 1)
4734 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4735 QTest::newRow("delete (selection)")
4736 << standard.at(0) << QKeySequence(QKeySequence::Delete) << 12 << 15
4737 << 12 << (standard.at(0).mid(0, 12) + standard.at(0).mid(15)) << QString()
4738 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4739 QTest::newRow("delete (no selection)")
4740 << standard.at(0) << QKeySequence(QKeySequence::Delete) << 15 << 15
4741 << 15 << (standard.at(0).mid(0, 15) + standard.at(0).mid(16)) << QString()
4742 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4743 QTest::newRow("delete end of word")
4744 << standard.at(0) << QKeySequence(QKeySequence::DeleteEndOfWord) << 24 << 24
4745 << 24 << (standard.at(0).mid(0, 24) + standard.at(0).mid(27)) << QString()
4746 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4747 QTest::newRow("delete start of word")
4748 << standard.at(0) << QKeySequence(QKeySequence::DeleteStartOfWord) << 7 << 7
4749 << 4 << (standard.at(0).mid(0, 4) + standard.at(0).mid(7)) << QString()
4750 << QQuickTextInput::Normal << Qt::Key_Direction_L;
4753 void tst_qquicktextinput::keySequence()
4755 QFETCH(QString, text);
4756 QFETCH(QKeySequence, sequence);
4757 QFETCH(int, selectionStart);
4758 QFETCH(int, selectionEnd);
4759 QFETCH(int, cursorPosition);
4760 QFETCH(QString, expectedText);
4761 QFETCH(QString, selectedText);
4762 QFETCH(QQuickTextInput::EchoMode, echoMode);
4763 QFETCH(Qt::Key, layoutDirection);
4765 if (sequence.isEmpty()) {
4766 QSKIP("Key sequence is undefined");
4769 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; text: \"" + text + "\" }";
4770 QQmlComponent textInputComponent(&engine);
4771 textInputComponent.setData(componentStr.toLatin1(), QUrl());
4772 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
4773 QVERIFY(textInput != 0);
4774 textInput->setEchoMode(echoMode);
4776 QQuickCanvas canvas;
4777 textInput->setParentItem(canvas.rootItem());
4779 canvas.requestActivateWindow();
4780 QTest::qWaitForWindowShown(&canvas);
4781 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
4783 simulateKey(&canvas, layoutDirection);
4785 textInput->select(selectionStart, selectionEnd);
4787 simulateKeys(&canvas, sequence);
4789 QCOMPARE(textInput->cursorPosition(), cursorPosition);
4790 QCOMPARE(textInput->text(), expectedText);
4791 QCOMPARE(textInput->selectedText(), selectedText);
4795 #define REPLACE_UNTIL_END 1
4797 void tst_qquicktextinput::undo_data()
4799 QTest::addColumn<QStringList>("insertString");
4800 QTest::addColumn<IntList>("insertIndex");
4801 QTest::addColumn<IntList>("insertMode");
4802 QTest::addColumn<QStringList>("expectedString");
4803 QTest::addColumn<bool>("use_keys");
4805 for (int i=0; i<2; i++) {
4806 QString keys_str = "keyboard";
4807 bool use_keys = true;
4809 keys_str = "insert";
4814 IntList insertIndex;
4816 QStringList insertString;
4817 QStringList expectedString;
4820 insertMode << NORMAL;
4821 insertString << "1";
4824 insertMode << NORMAL;
4825 insertString << "5";
4828 insertMode << NORMAL;
4829 insertString << "3";
4832 insertMode << NORMAL;
4833 insertString << "2";
4836 insertMode << NORMAL;
4837 insertString << "4";
4839 expectedString << "12345";
4840 expectedString << "1235";
4841 expectedString << "135";
4842 expectedString << "15";
4843 expectedString << "";
4845 QTest::newRow(QString(keys_str + "_numbers").toLatin1()) <<
4853 IntList insertIndex;
4855 QStringList insertString;
4856 QStringList expectedString;
4859 insertMode << NORMAL;
4860 insertString << "World"; // World
4863 insertMode << NORMAL;
4864 insertString << "Hello"; // HelloWorld
4867 insertMode << NORMAL;
4868 insertString << "Well"; // WellHelloWorld
4871 insertMode << NORMAL;
4872 insertString << "There"; // WellHelloThereWorld;
4874 expectedString << "WellHelloThereWorld";
4875 expectedString << "WellHelloWorld";
4876 expectedString << "HelloWorld";
4877 expectedString << "World";
4878 expectedString << "";
4880 QTest::newRow(QString(keys_str + "_helloworld").toLatin1()) <<
4888 IntList insertIndex;
4890 QStringList insertString;
4891 QStringList expectedString;
4894 insertMode << NORMAL;
4895 insertString << "Ensuring";
4898 insertMode << NORMAL;
4899 insertString << " instan";
4902 insertMode << NORMAL;
4903 insertString << "an ";
4906 insertMode << REPLACE_UNTIL_END;
4907 insertString << " unique instance.";
4909 expectedString << "Ensuring a unique instance.";
4910 expectedString << "Ensuring an instan";
4911 expectedString << "Ensuring instan";
4912 expectedString << "";
4914 QTest::newRow(QString(keys_str + "_patterns").toLatin1()) <<
4924 void tst_qquicktextinput::undo()
4926 QFETCH(QStringList, insertString);
4927 QFETCH(IntList, insertIndex);
4928 QFETCH(IntList, insertMode);
4929 QFETCH(QStringList, expectedString);
4931 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
4932 QQmlComponent textInputComponent(&engine);
4933 textInputComponent.setData(componentStr.toLatin1(), QUrl());
4934 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
4935 QVERIFY(textInput != 0);
4937 QQuickCanvas canvas;
4938 textInput->setParentItem(canvas.rootItem());
4940 canvas.requestActivateWindow();
4941 QTest::qWaitForWindowShown(&canvas);
4942 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
4944 QVERIFY(!textInput->canUndo());
4946 QSignalSpy spy(textInput, SIGNAL(canUndoChanged()));
4950 // STEP 1: First build up an undo history by inserting or typing some strings...
4951 for (i = 0; i < insertString.size(); ++i) {
4952 if (insertIndex[i] > -1)
4953 textInput->setCursorPosition(insertIndex[i]);
4955 // experimental stuff
4956 if (insertMode[i] == REPLACE_UNTIL_END) {
4957 textInput->select(insertIndex[i], insertIndex[i] + 8);
4959 // This is what I actually want...
4960 // QTest::keyClick(testWidget, Qt::Key_End, Qt::ShiftModifier);
4963 for (int j = 0; j < insertString.at(i).length(); j++)
4964 QTest::keyClick(&canvas, insertString.at(i).at(j).toLatin1());
4967 QCOMPARE(spy.count(), 1);
4969 // STEP 2: Next call undo several times and see if we can restore to the previous state
4970 for (i = 0; i < expectedString.size() - 1; ++i) {
4971 QCOMPARE(textInput->text(), expectedString[i]);
4972 QVERIFY(textInput->canUndo());
4976 // STEP 3: Verify that we have undone everything
4977 QVERIFY(textInput->text().isEmpty());
4978 QVERIFY(!textInput->canUndo());
4979 QCOMPARE(spy.count(), 2);
4982 void tst_qquicktextinput::redo_data()
4984 QTest::addColumn<QStringList>("insertString");
4985 QTest::addColumn<IntList>("insertIndex");
4986 QTest::addColumn<QStringList>("expectedString");
4989 IntList insertIndex;
4990 QStringList insertString;
4991 QStringList expectedString;
4994 insertString << "World"; // World
4996 insertString << "Hello"; // HelloWorld
4998 insertString << "Well"; // WellHelloWorld
5000 insertString << "There"; // WellHelloThereWorld;
5002 expectedString << "World";
5003 expectedString << "HelloWorld";
5004 expectedString << "WellHelloWorld";
5005 expectedString << "WellHelloThereWorld";
5007 QTest::newRow("Inserts and setting cursor") << insertString << insertIndex << expectedString;
5011 void tst_qquicktextinput::redo()
5013 QFETCH(QStringList, insertString);
5014 QFETCH(IntList, insertIndex);
5015 QFETCH(QStringList, expectedString);
5017 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
5018 QQmlComponent textInputComponent(&engine);
5019 textInputComponent.setData(componentStr.toLatin1(), QUrl());
5020 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
5021 QVERIFY(textInput != 0);
5023 QQuickCanvas canvas;
5024 textInput->setParentItem(canvas.rootItem());
5026 canvas.requestActivateWindow();
5027 QTest::qWaitForWindowShown(&canvas);
5028 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
5030 QVERIFY(!textInput->canUndo());
5031 QVERIFY(!textInput->canRedo());
5033 QSignalSpy spy(textInput, SIGNAL(canRedoChanged()));
5036 // inserts the diff strings at diff positions
5037 for (i = 0; i < insertString.size(); ++i) {
5038 if (insertIndex[i] > -1)
5039 textInput->setCursorPosition(insertIndex[i]);
5040 for (int j = 0; j < insertString.at(i).length(); j++)
5041 QTest::keyClick(&canvas, insertString.at(i).at(j).toLatin1());
5042 QVERIFY(textInput->canUndo());
5043 QVERIFY(!textInput->canRedo());
5046 QCOMPARE(spy.count(), 0);
5049 while (!textInput->text().isEmpty()) {
5050 QVERIFY(textInput->canUndo());
5052 QVERIFY(textInput->canRedo());
5055 QCOMPARE(spy.count(), 1);
5057 for (i = 0; i < expectedString.size(); ++i) {
5058 QVERIFY(textInput->canRedo());
5060 QCOMPARE(textInput->text() , expectedString[i]);
5061 QVERIFY(textInput->canUndo());
5063 QVERIFY(!textInput->canRedo());
5064 QCOMPARE(spy.count(), 2);
5067 void tst_qquicktextinput::undo_keypressevents_data()
5069 QTest::addColumn<KeyList>("keys");
5070 QTest::addColumn<QStringList>("expectedString");
5074 QStringList expectedString;
5077 << QKeySequence::MoveToStartOfLine
5084 << QKeySequence::MoveToEndOfLine
5087 expectedString << "BEVERYAFRAID!";
5088 expectedString << "BEVERYAFRAID";
5089 expectedString << "VERYAFRAID";
5090 expectedString << "AFRAID";
5092 QTest::newRow("Inserts and moving cursor") << keys << expectedString;
5095 QStringList expectedString;
5098 keys << "1234" << QKeySequence::MoveToStartOfLine
5100 << Qt::Key_Right << Qt::Key_Right
5102 << (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
5106 expectedString << "12";
5107 expectedString << "1234";
5109 QTest::newRow("Inserts,moving,selection and delete") << keys << expectedString;
5112 QStringList expectedString;
5116 << QKeySequence::MoveToStartOfLine
5118 << (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
5120 << QKeySequence::Undo
5122 << (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
5125 expectedString << "AB";
5126 expectedString << "AB12";
5128 QTest::newRow("Inserts,moving,selection, delete and undo") << keys << expectedString;
5131 QStringList expectedString;
5136 << Qt::Key_Left << Qt::Key_Left
5140 << (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier)
5141 // overwriting '1234' with '5'
5143 // undoing deletion of 'AB'
5144 << QKeySequence::Undo
5145 // overwriting '1234' with '6'
5148 expectedString << "ab6cd";
5149 // for versions previous to 3.2 we overwrite needed two undo operations
5150 expectedString << "ab1234cd";
5151 expectedString << "abcd";
5153 QTest::newRow("Inserts,moving,selection and undo, removing selection") << keys << expectedString;
5156 QStringList expectedString;
5161 << Qt::Key_Backspace;
5163 expectedString << "AB";
5164 expectedString << "ABC";
5166 QTest::newRow("Inserts,backspace") << keys << expectedString;
5169 QStringList expectedString;
5173 << Qt::Key_Backspace
5177 expectedString << "ABZ";
5178 expectedString << "AB";
5179 expectedString << "ABC";
5181 QTest::newRow("Inserts,backspace,inserts") << keys << expectedString;
5184 QStringList expectedString;
5187 keys << "123" << QKeySequence::MoveToStartOfLine
5189 << QKeySequence::SelectEndOfLine
5190 // overwriting '123' with 'ABC'
5193 expectedString << "ABC";
5194 // for versions previous to 3.2 we overwrite needed two undo operations
5195 expectedString << "123";
5197 QTest::newRow("Inserts,moving,selection and overwriting") << keys << expectedString;
5200 QStringList expectedString;
5204 << QKeySequence::Undo
5205 << QKeySequence::Redo;
5207 expectedString << "123";
5208 expectedString << QString();
5210 QTest::newRow("Insert,undo,redo") << keys << expectedString;
5214 void tst_qquicktextinput::undo_keypressevents()
5216 QFETCH(KeyList, keys);
5217 QFETCH(QStringList, expectedString);
5219 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
5220 QQmlComponent textInputComponent(&engine);
5221 textInputComponent.setData(componentStr.toLatin1(), QUrl());
5222 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
5223 QVERIFY(textInput != 0);
5225 QQuickCanvas canvas;
5226 textInput->setParentItem(canvas.rootItem());
5228 canvas.requestActivateWindow();
5229 QTest::qWaitForWindowShown(&canvas);
5230 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
5232 simulateKeys(&canvas, keys);
5234 for (int i = 0; i < expectedString.size(); ++i) {
5235 QCOMPARE(textInput->text() , expectedString[i]);
5238 QVERIFY(textInput->text().isEmpty());
5241 void tst_qquicktextinput::QTBUG_19956()
5243 QFETCH(QString, url);
5245 QQuickView canvas(testFileUrl(url));
5247 canvas.requestActivateWindow();
5248 QTest::qWaitForWindowShown(&canvas);
5249 QVERIFY(canvas.rootObject() != 0);
5250 QQuickTextInput *input = qobject_cast<QQuickTextInput*>(canvas.rootObject());
5252 input->setFocus(true);
5253 QVERIFY(input->hasActiveFocus());
5255 QCOMPARE(canvas.rootObject()->property("topvalue").toInt(), 30);
5256 QCOMPARE(canvas.rootObject()->property("bottomvalue").toInt(), 10);
5257 QCOMPARE(canvas.rootObject()->property("text").toString(), QString("20"));
5258 QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
5260 canvas.rootObject()->setProperty("topvalue", 15);
5261 QCOMPARE(canvas.rootObject()->property("topvalue").toInt(), 15);
5262 QVERIFY(!canvas.rootObject()->property("acceptableInput").toBool());
5264 canvas.rootObject()->setProperty("topvalue", 25);
5265 QCOMPARE(canvas.rootObject()->property("topvalue").toInt(), 25);
5266 QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
5268 canvas.rootObject()->setProperty("bottomvalue", 21);
5269 QCOMPARE(canvas.rootObject()->property("bottomvalue").toInt(), 21);
5270 QVERIFY(!canvas.rootObject()->property("acceptableInput").toBool());
5272 canvas.rootObject()->setProperty("bottomvalue", 10);
5273 QCOMPARE(canvas.rootObject()->property("bottomvalue").toInt(), 10);
5274 QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
5277 void tst_qquicktextinput::QTBUG_19956_regexp()
5279 QUrl url = testFileUrl("qtbug-19956regexp.qml");
5281 QString warning = url.toString() + ":11: Unable to assign [undefined] to QRegExp";
5282 QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
5284 QQuickView canvas(url);
5286 canvas.requestActivateWindow();
5287 QTest::qWaitForWindowShown(&canvas);
5288 QVERIFY(canvas.rootObject() != 0);
5289 QQuickTextInput *input = qobject_cast<QQuickTextInput*>(canvas.rootObject());
5291 input->setFocus(true);
5292 QVERIFY(input->hasActiveFocus());
5294 canvas.rootObject()->setProperty("regexvalue", QRegExp("abc"));
5295 QCOMPARE(canvas.rootObject()->property("regexvalue").toRegExp(), QRegExp("abc"));
5296 QCOMPARE(canvas.rootObject()->property("text").toString(), QString("abc"));
5297 QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
5299 canvas.rootObject()->setProperty("regexvalue", QRegExp("abcd"));
5300 QCOMPARE(canvas.rootObject()->property("regexvalue").toRegExp(), QRegExp("abcd"));
5301 QVERIFY(!canvas.rootObject()->property("acceptableInput").toBool());
5303 canvas.rootObject()->setProperty("regexvalue", QRegExp("abc"));
5304 QCOMPARE(canvas.rootObject()->property("regexvalue").toRegExp(), QRegExp("abc"));
5305 QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
5308 void tst_qquicktextinput::implicitSize_data()
5310 QTest::addColumn<QString>("text");
5311 QTest::addColumn<QString>("wrap");
5312 QTest::newRow("plain") << "The quick red fox jumped over the lazy brown dog" << "TextInput.NoWrap";
5313 QTest::newRow("plain_wrap") << "The quick red fox jumped over the lazy brown dog" << "TextInput.Wrap";
5316 void tst_qquicktextinput::implicitSize()
5318 QFETCH(QString, text);
5319 QFETCH(QString, wrap);
5320 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; width: 50; wrapMode: " + wrap + " }";
5321 QQmlComponent textComponent(&engine);
5322 textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
5323 QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(textComponent.create());
5325 QVERIFY(textObject->width() < textObject->implicitWidth());
5326 QVERIFY(textObject->height() == textObject->implicitHeight());
5328 textObject->resetWidth();
5329 QVERIFY(textObject->width() == textObject->implicitWidth());
5330 QVERIFY(textObject->height() == textObject->implicitHeight());
5333 void tst_qquicktextinput::implicitSizeBinding_data()
5335 implicitSize_data();
5338 void tst_qquicktextinput::implicitSizeBinding()
5340 QFETCH(QString, text);
5341 QFETCH(QString, wrap);
5342 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; width: implicitWidth; height: implicitHeight; wrapMode: " + wrap + " }";
5343 QQmlComponent textComponent(&engine);
5344 textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
5345 QScopedPointer<QObject> object(textComponent.create());
5346 QQuickTextInput *textObject = qobject_cast<QQuickTextInput *>(object.data());
5348 QCOMPARE(textObject->width(), textObject->implicitWidth());
5349 QCOMPARE(textObject->height(), textObject->implicitHeight());
5351 textObject->resetWidth();
5352 QCOMPARE(textObject->width(), textObject->implicitWidth());
5353 QCOMPARE(textObject->height(), textObject->implicitHeight());
5355 textObject->resetHeight();
5356 QCOMPARE(textObject->width(), textObject->implicitWidth());
5357 QCOMPARE(textObject->height(), textObject->implicitHeight());
5361 void tst_qquicktextinput::negativeDimensions()
5363 // Verify this doesn't assert during initialization.
5364 QQmlComponent component(&engine, testFileUrl("negativeDimensions.qml"));
5365 QScopedPointer<QObject> o(component.create());
5367 QQuickTextInput *input = o->findChild<QQuickTextInput *>("input");
5369 QCOMPARE(input->width(), qreal(-1));
5370 QCOMPARE(input->height(), qreal(-1));
5374 void tst_qquicktextinput::setInputMask_data()
5376 QTest::addColumn<QString>("mask");
5377 QTest::addColumn<QString>("input");
5378 QTest::addColumn<QString>("expectedText");
5379 QTest::addColumn<QString>("expectedDisplay");
5380 QTest::addColumn<bool>("insert_text");
5382 // both keyboard and insert()
5383 for (int i=0; i<2; i++) {
5384 bool insert_text = i==0 ? false : true;
5385 QString insert_mode = "keys ";
5387 insert_mode = "insert ";
5389 QTest::newRow(QString(insert_mode + "ip_localhost").toLatin1())
5390 << QString("000.000.000.000")
5391 << QString("127.0.0.1")
5392 << QString("127.0.0.1")
5393 << QString("127.0 .0 .1 ")
5394 << bool(insert_text);
5395 QTest::newRow(QString(insert_mode + "mac").toLatin1())
5396 << QString("HH:HH:HH:HH:HH:HH;#")
5397 << QString("00:E0:81:21:9E:8E")
5398 << QString("00:E0:81:21:9E:8E")
5399 << QString("00:E0:81:21:9E:8E")
5400 << bool(insert_text);
5401 QTest::newRow(QString(insert_mode + "mac2").toLatin1())
5402 << QString("<HH:>HH:!HH:HH:HH:HH;#")
5403 << QString("AAe081219E8E")
5404 << QString("aa:E0:81:21:9E:8E")
5405 << QString("aa:E0:81:21:9E:8E")
5406 << bool(insert_text);
5407 QTest::newRow(QString(insert_mode + "byte").toLatin1())
5408 << QString("BBBBBBBB;0")
5409 << QString("11011001")
5411 << QString("11011001")
5412 << bool(insert_text);
5413 QTest::newRow(QString(insert_mode + "halfbytes").toLatin1())
5414 << QString("bbbb.bbbb;-")
5415 << QString("110. 0001")
5416 << QString("110.0001")
5417 << QString("110-.0001")
5418 << bool(insert_text);
5419 QTest::newRow(QString(insert_mode + "blank char same type as content").toLatin1())
5420 << QString("000.000.000.000;0")
5421 << QString("127.0.0.1")
5422 << QString("127...1")
5423 << QString("127.000.000.100")
5424 << bool(insert_text);
5425 QTest::newRow(QString(insert_mode + "parts of ip_localhost").toLatin1())
5426 << QString("000.000.000.000")
5427 << QString(".0.0.1")
5428 << QString(".0.0.1")
5429 << QString(" .0 .0 .1 ")
5430 << bool(insert_text);
5431 QTest::newRow(QString(insert_mode + "ip_null").toLatin1())
5432 << QString("000.000.000.000")
5435 << QString(" . . . ")
5436 << bool(insert_text);
5437 QTest::newRow(QString(insert_mode + "ip_null_hash").toLatin1())
5438 << QString("000.000.000.000;#")
5441 << QString("###.###.###.###")
5442 << bool(insert_text);
5443 QTest::newRow(QString(insert_mode + "ip_overflow").toLatin1())
5444 << QString("000.000.000.000")
5445 << QString("1234123412341234")
5446 << QString("123.412.341.234")
5447 << QString("123.412.341.234")
5448 << bool(insert_text);
5449 QTest::newRow(QString(insert_mode + "uppercase").toLatin1())
5454 << bool(insert_text);
5455 QTest::newRow(QString(insert_mode + "lowercase").toLatin1())
5460 << bool(insert_text);
5462 QTest::newRow(QString(insert_mode + "nocase").toLatin1())
5467 << bool(insert_text);
5468 QTest::newRow(QString(insert_mode + "nocase1").toLatin1())
5469 << QString("!A!A!A!A")
5473 << bool(insert_text);
5474 QTest::newRow(QString(insert_mode + "nocase2").toLatin1())
5479 << bool(insert_text);
5481 QTest::newRow(QString(insert_mode + "reserved").toLatin1())
5482 << QString("{n}[0]")
5486 << bool(insert_text);
5487 QTest::newRow(QString(insert_mode + "escape01").toLatin1())
5488 << QString("\\\\N\\\\n00")
5492 << bool(insert_text);
5493 QTest::newRow(QString(insert_mode + "escape02").toLatin1())
5494 << QString("\\\\\\\\00")
5498 << bool(insert_text);
5499 QTest::newRow(QString(insert_mode + "escape03").toLatin1())
5500 << QString("\\\\(00\\\\)")
5504 << bool(insert_text);
5506 QTest::newRow(QString(insert_mode + "upper_lower_nocase1").toLatin1())
5507 << QString(">AAAA<AAAA!AAAA")
5508 << QString("AbCdEfGhIjKl")
5509 << QString("ABCDefghIjKl")
5510 << QString("ABCDefghIjKl")
5511 << bool(insert_text);
5512 QTest::newRow(QString(insert_mode + "upper_lower_nocase2").toLatin1())
5513 << QString(">aaaa<aaaa!aaaa")
5514 << QString("AbCdEfGhIjKl")
5515 << QString("ABCDefghIjKl")
5516 << QString("ABCDefghIjKl")
5517 << bool(insert_text);
5519 QTest::newRow(QString(insert_mode + "exact_case1").toLatin1())
5520 << QString(">A<A<A>A>A<A!A!A")
5521 << QString("AbCdEFGH")
5522 << QString("AbcDEfGH")
5523 << QString("AbcDEfGH")
5524 << bool(insert_text);
5525 QTest::newRow(QString(insert_mode + "exact_case2").toLatin1())
5526 << QString(">A<A<A>A>A<A!A!A")
5527 << QString("aBcDefgh")
5528 << QString("AbcDEfgh")
5529 << QString("AbcDEfgh")
5530 << bool(insert_text);
5531 QTest::newRow(QString(insert_mode + "exact_case3").toLatin1())
5532 << QString(">a<a<a>a>a<a!a!a")
5533 << QString("AbCdEFGH")
5534 << QString("AbcDEfGH")
5535 << QString("AbcDEfGH")
5536 << bool(insert_text);
5537 QTest::newRow(QString(insert_mode + "exact_case4").toLatin1())
5538 << QString(">a<a<a>a>a<a!a!a")
5539 << QString("aBcDefgh")
5540 << QString("AbcDEfgh")
5541 << QString("AbcDEfgh")
5542 << bool(insert_text);
5543 QTest::newRow(QString(insert_mode + "exact_case5").toLatin1())
5544 << QString(">H<H<H>H>H<H!H!H")
5545 << QString("aBcDef01")
5546 << QString("AbcDEf01")
5547 << QString("AbcDEf01")
5548 << bool(insert_text);
5549 QTest::newRow(QString(insert_mode + "exact_case6").toLatin1())
5550 << QString(">h<h<h>h>h<h!h!h")
5551 << QString("aBcDef92")
5552 << QString("AbcDEf92")
5553 << QString("AbcDEf92")
5554 << bool(insert_text);
5556 QTest::newRow(QString(insert_mode + "illegal_keys1").toLatin1())
5557 << QString("AAAAAAAA")
5558 << QString("A2#a;.0!")
5561 << bool(insert_text);
5562 QTest::newRow(QString(insert_mode + "illegal_keys2").toLatin1())
5564 << QString("f4f4f4f4")
5567 << bool(insert_text);
5568 QTest::newRow(QString(insert_mode + "blank=input").toLatin1())
5569 << QString("9999;0")
5573 << bool(insert_text);
5577 void tst_qquicktextinput::setInputMask()
5579 QFETCH(QString, mask);
5580 QFETCH(QString, input);
5581 QFETCH(QString, expectedText);
5582 QFETCH(QString, expectedDisplay);
5583 QFETCH(bool, insert_text);
5585 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
5586 QQmlComponent textInputComponent(&engine);
5587 textInputComponent.setData(componentStr.toLatin1(), QUrl());
5588 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
5589 QVERIFY(textInput != 0);
5591 // then either insert using insert() or keyboard
5593 textInput->insert(0, input);
5595 QQuickCanvas canvas;
5596 textInput->setParentItem(canvas.rootItem());
5598 canvas.requestActivateWindow();
5599 QTest::qWaitForWindowShown(&canvas);
5600 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
5602 simulateKey(&canvas, Qt::Key_Home);
5603 for (int i = 0; i < input.length(); i++)
5604 QTest::keyClick(&canvas, input.at(i).toLatin1());
5607 QEXPECT_FAIL( "keys blank=input", "To eat blanks or not? Known issue. Task 43172", Abort);
5608 QEXPECT_FAIL( "insert blank=input", "To eat blanks or not? Known issue. Task 43172", Abort);
5610 QCOMPARE(textInput->text(), expectedText);
5611 QCOMPARE(textInput->displayText(), expectedDisplay);
5614 void tst_qquicktextinput::inputMask_data()
5616 QTest::addColumn<QString>("mask");
5617 QTest::addColumn<QString>("expectedMask");
5619 // if no mask is set a nul string should be returned
5620 QTest::newRow("nul 1") << QString("") << QString();
5621 QTest::newRow("nul 2") << QString() << QString();
5623 // try different masks
5624 QTest::newRow("mask 1") << QString("000.000.000.000") << QString("000.000.000.000; ");
5625 QTest::newRow("mask 2") << QString("000.000.000.000;#") << QString("000.000.000.000;#");
5626 QTest::newRow("mask 3") << QString("AAA.aa.999.###;") << QString("AAA.aa.999.###; ");
5627 QTest::newRow("mask 4") << QString(">abcdef<GHIJK") << QString(">abcdef<GHIJK; ");
5629 // set an invalid input mask...
5630 // the current behaviour is that this exact (faulty) string is returned.
5631 QTest::newRow("invalid") << QString("ABCDEFGHIKLMNOP;") << QString("ABCDEFGHIKLMNOP; ");
5633 // verify that we can unset the mask again
5634 QTest::newRow("unset") << QString("") << QString();
5637 void tst_qquicktextinput::inputMask()
5639 QFETCH(QString, mask);
5640 QFETCH(QString, expectedMask);
5642 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
5643 QQmlComponent textInputComponent(&engine);
5644 textInputComponent.setData(componentStr.toLatin1(), QUrl());
5645 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
5646 QVERIFY(textInput != 0);
5648 QCOMPARE(textInput->inputMask(), expectedMask);
5651 void tst_qquicktextinput::clearInputMask()
5653 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"000.000.000.000\" }";
5654 QQmlComponent textInputComponent(&engine);
5655 textInputComponent.setData(componentStr.toLatin1(), QUrl());
5656 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
5657 QVERIFY(textInput != 0);
5659 QVERIFY(textInput->inputMask() != QString());
5660 textInput->setInputMask(QString());
5661 QCOMPARE(textInput->inputMask(), QString());
5664 void tst_qquicktextinput::keypress_inputMask_data()
5666 QTest::addColumn<QString>("mask");
5667 QTest::addColumn<KeyList>("keys");
5668 QTest::addColumn<QString>("expectedText");
5669 QTest::addColumn<QString>("expectedDisplayText");
5673 // inserting 'A1.2B'
5674 keys << Qt::Key_Home << "A1.2B";
5675 QTest::newRow("jumping on period(separator)") << QString("000.000;_") << keys << QString("1.2") << QString("1__.2__");
5680 keys << Qt::Key_Home << "0!P3";
5681 QTest::newRow("jumping on input") << QString("D0.AA.XX.AA.00;_") << keys << QString("0..!P..3") << QString("_0.__.!P.__.3_");
5686 keys << Qt::Key_Home
5688 QTest::newRow("delete") << QString("000.000;_") << keys << QString(".") << QString("___.___");
5692 // selecting all and delete
5693 keys << Qt::Key_Home
5694 << Key(Qt::ShiftModifier, Qt::Key_End)
5696 QTest::newRow("deleting all") << QString("000.000;_") << keys << QString(".") << QString("___.___");
5700 // inserting '12.12' then two backspaces
5701 keys << Qt::Key_Home << "12.12" << Qt::Key_Backspace << Qt::Key_Backspace;
5702 QTest::newRow("backspace") << QString("000.000;_") << keys << QString("12.") << QString("12_.___");
5707 keys << Qt::Key_Home << "12ab";
5708 QTest::newRow("uppercase") << QString("9999 >AA;_") << keys << QString("12 AB") << QString("12__ AB");
5712 void tst_qquicktextinput::keypress_inputMask()
5714 QFETCH(QString, mask);
5715 QFETCH(KeyList, keys);
5716 QFETCH(QString, expectedText);
5717 QFETCH(QString, expectedDisplayText);
5719 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
5720 QQmlComponent textInputComponent(&engine);
5721 textInputComponent.setData(componentStr.toLatin1(), QUrl());
5722 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
5723 QVERIFY(textInput != 0);
5725 QQuickCanvas canvas;
5726 textInput->setParentItem(canvas.rootItem());
5728 canvas.requestActivateWindow();
5729 QTest::qWaitForWindowShown(&canvas);
5730 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
5732 simulateKeys(&canvas, keys);
5734 QCOMPARE(textInput->text(), expectedText);
5735 QCOMPARE(textInput->displayText(), expectedDisplayText);
5739 void tst_qquicktextinput::hasAcceptableInputMask_data()
5741 QTest::addColumn<QString>("optionalMask");
5742 QTest::addColumn<QString>("requiredMask");
5743 QTest::addColumn<QString>("invalid");
5744 QTest::addColumn<QString>("valid");
5746 QTest::newRow("Alphabetic optional and required")
5747 << QString("aaaa") << QString("AAAA") << QString("ab") << QString("abcd");
5748 QTest::newRow("Alphanumeric optional and require")
5749 << QString("nnnn") << QString("NNNN") << QString("R2") << QString("R2D2");
5750 QTest::newRow("Any optional and required")
5751 << QString("xxxx") << QString("XXXX") << QString("+-") << QString("+-*/");
5752 QTest::newRow("Numeric (0-9) required")
5753 << QString("0000") << QString("9999") << QString("11") << QString("1138");
5754 QTest::newRow("Numeric (1-9) optional and required")
5755 << QString("dddd") << QString("DDDD") << QString("12") << QString("1234");
5758 void tst_qquicktextinput::hasAcceptableInputMask()
5760 QFETCH(QString, optionalMask);
5761 QFETCH(QString, requiredMask);
5762 QFETCH(QString, invalid);
5763 QFETCH(QString, valid);
5765 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + optionalMask + "\" }";
5766 QQmlComponent textInputComponent(&engine);
5767 textInputComponent.setData(componentStr.toLatin1(), QUrl());
5768 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
5769 QVERIFY(textInput != 0);
5771 QQuickCanvas canvas;
5772 textInput->setParentItem(canvas.rootItem());
5774 canvas.requestActivateWindow();
5775 QTest::qWaitForWindowShown(&canvas);
5776 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
5778 // test that invalid input (for required) work for optionalMask
5779 textInput->setText(invalid);
5780 QVERIFY(textInput->hasAcceptableInput());
5782 // at the moment we don't strip the blank character if it is valid input, this makes the test between x vs X useless
5783 QEXPECT_FAIL( "Any optional and required", "To eat blanks or not? Known issue. Task 43172", Abort);
5785 // test requiredMask
5786 textInput->setInputMask(requiredMask);
5787 textInput->setText(invalid);
5788 QVERIFY(!textInput->hasAcceptableInput());
5790 textInput->setText(valid);
5791 QVERIFY(textInput->hasAcceptableInput());
5794 void tst_qquicktextinput::maskCharacter_data()
5796 QTest::addColumn<QString>("mask");
5797 QTest::addColumn<QString>("input");
5798 QTest::addColumn<bool>("expectedValid");
5800 QTest::newRow("Hex") << QString("H")
5801 << QString("0123456789abcdefABCDEF") << true;
5802 QTest::newRow("hex") << QString("h")
5803 << QString("0123456789abcdefABCDEF") << true;
5804 QTest::newRow("HexInvalid") << QString("H")
5805 << QString("ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ")
5807 QTest::newRow("hexInvalid") << QString("h")
5808 << QString("ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ")
5810 QTest::newRow("Bin") << QString("B")
5811 << QString("01") << true;
5812 QTest::newRow("bin") << QString("b")
5813 << QString("01") << true;
5814 QTest::newRow("BinInvalid") << QString("B")
5815 << QString("23456789qwertyuiopasdfghjklzxcvbnm")
5817 QTest::newRow("binInvalid") << QString("b")
5818 << QString("23456789qwertyuiopasdfghjklzxcvbnm")
5822 void tst_qquicktextinput::maskCharacter()
5824 QFETCH(QString, mask);
5825 QFETCH(QString, input);
5826 QFETCH(bool, expectedValid);
5828 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
5829 QQmlComponent textInputComponent(&engine);
5830 textInputComponent.setData(componentStr.toLatin1(), QUrl());
5831 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(textInputComponent.create());
5832 QVERIFY(textInput != 0);
5834 QQuickCanvas canvas;
5835 textInput->setParentItem(canvas.rootItem());
5837 canvas.requestActivateWindow();
5838 QTest::qWaitForWindowShown(&canvas);
5839 QTRY_COMPARE(QGuiApplication::focusWindow(), &canvas);
5841 for (int i = 0; i < input.size(); ++i) {
5842 QString in = QString(input.at(i));
5843 QString expected = expectedValid ? in : QString();
5844 textInput->setText(QString(input.at(i)));
5845 QCOMPARE(textInput->text(), expected);
5849 QTEST_MAIN(tst_qquicktextinput)
5851 #include "tst_qquicktextinput.moc"