Fix test fails related to QTBUG-22237
[profile/ivi/qtdeclarative.git] / tests / auto / declarative / qsgtextinput / tst_qsgtextinput.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
6 **
7 ** This file is part of the test suite of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:LGPL$
10 ** GNU Lesser General Public License Usage
11 ** This file may be used under the terms of the GNU Lesser General Public
12 ** License version 2.1 as published by the Free Software Foundation and
13 ** appearing in the file LICENSE.LGPL included in the packaging of this
14 ** file. Please review the following information to ensure the GNU Lesser
15 ** General Public License version 2.1 requirements will be met:
16 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 **
18 ** In addition, as a special exception, Nokia gives you certain additional
19 ** rights. These rights are described in the Nokia Qt LGPL Exception
20 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 **
22 ** GNU General Public License Usage
23 ** Alternatively, this file may be used under the terms of the GNU General
24 ** Public License version 3.0 as published by the Free Software Foundation
25 ** and appearing in the file LICENSE.GPL included in the packaging of this
26 ** file. Please review the following information to ensure the GNU General
27 ** Public License version 3.0 requirements will be met:
28 ** http://www.gnu.org/copyleft/gpl.html.
29 **
30 ** Other Usage
31 ** Alternatively, this file may be used in accordance with the terms and
32 ** conditions contained in a signed written agreement between you and Nokia.
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 #include <qtest.h>
42 #include <QtTest/QSignalSpy>
43 #include "../shared/util.h"
44 #include <QtDeclarative/qdeclarativeengine.h>
45 #include <QFile>
46 #include <QtDeclarative/qsgview.h>
47 #include <QtGui/qguiapplication.h>
48 #include <QInputPanel>
49 #include <private/qsgtextinput_p.h>
50 #include <private/qsgtextinput_p_p.h>
51 #include <QDebug>
52 #include <QDir>
53 #include <QStyle>
54 #include <QInputContext>
55 #include <private/qsgdistancefieldglyphcache_p.h>
56 #include <QtOpenGL/QGLShaderProgram>
57 #include <math.h>
58
59 #include "qplatformdefs.h"
60
61 Q_DECLARE_METATYPE(QSGTextInput::SelectionMode)
62 DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
63
64 #define QTBUG_21691
65 #define QTBUG_21691_MESSAGE "QTBUG-21691: The test needs to be rewritten to not use QInputContext"
66
67
68 QString createExpectedFileIfNotFound(const QString& filebasename, const QImage& actual)
69 {
70     // XXX This will be replaced by some clever persistent platform image store.
71     QString persistent_dir = TESTDATA("");
72     QString arch = "unknown-architecture"; // QTest needs to help with this.
73
74     QString expectfile = persistent_dir + QDir::separator() + filebasename + "-" + arch + ".png";
75
76     if (!QFile::exists(expectfile)) {
77         actual.save(expectfile);
78         qWarning() << "created" << expectfile;
79     }
80
81     return expectfile;
82 }
83
84 class tst_qsgtextinput : public QObject
85
86 {
87     Q_OBJECT
88 public:
89     tst_qsgtextinput();
90
91 private slots:
92     void initTestCase();
93     void cleanupTestCase();
94     void text();
95     void width();
96     void font();
97     void color();
98     void selection();
99     void isRightToLeft_data();
100     void isRightToLeft();
101     void moveCursorSelection_data();
102     void moveCursorSelection();
103     void moveCursorSelectionSequence_data();
104     void moveCursorSelectionSequence();
105     void dragMouseSelection();
106     void mouseSelectionMode_data();
107     void mouseSelectionMode();
108     void tripleClickSelectsAll();
109
110     void horizontalAlignment_data();
111     void horizontalAlignment();
112     void horizontalAlignment_RightToLeft();
113
114     void positionAt();
115
116     void maxLength();
117     void masks();
118     void validators();
119     void inputMethods();
120
121     void passwordCharacter();
122     void cursorDelegate();
123     void cursorVisible();
124     void cursorRectangle();
125     void navigation();
126     void navigation_RTL();
127     void copyAndPaste();
128     void canPasteEmpty();
129     void canPaste();
130     void readOnly();
131
132     void openInputPanel();
133     void setHAlignClearCache();
134     void focusOutClearSelection();
135
136     void echoMode();
137 #ifdef QT_GUI_PASSWORD_ECHO_DELAY
138     void passwordEchoDelay();
139 #endif
140     void geometrySignals();
141     void testQtQuick11Attributes();
142     void testQtQuick11Attributes_data();
143
144     void preeditAutoScroll();
145     void preeditMicroFocus();
146     void inputContextMouseHandler();
147     void inputMethodComposing();
148     void cursorRectangleSize();
149
150     void QTBUG_19956();
151     void QTBUG_19956_data();
152     void QTBUG_19956_regexp();
153
154 private:
155     void simulateKey(QSGView *, int key);
156
157     QDeclarativeEngine engine;
158     QStringList standard;
159     QStringList colorStrings;
160 };
161 void tst_qsgtextinput::initTestCase()
162 {
163 }
164
165 void tst_qsgtextinput::cleanupTestCase()
166 {
167
168 }
169 tst_qsgtextinput::tst_qsgtextinput()
170 {
171     standard << "the quick brown fox jumped over the lazy dog"
172         << "It's supercalifragisiticexpialidocious!"
173         << "Hello, world!"
174         << "!dlrow ,olleH"
175         << " spacey   text ";
176
177     colorStrings << "aliceblue"
178                  << "antiquewhite"
179                  << "aqua"
180                  << "darkkhaki"
181                  << "darkolivegreen"
182                  << "dimgray"
183                  << "palevioletred"
184                  << "lightsteelblue"
185                  << "#000000"
186                  << "#AAAAAA"
187                  << "#FFFFFF"
188                  << "#2AC05F";
189 }
190
191 void tst_qsgtextinput::text()
192 {
193     {
194         QDeclarativeComponent textinputComponent(&engine);
195         textinputComponent.setData("import QtQuick 2.0\nTextInput {  text: \"\"  }", QUrl());
196         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
197
198         QVERIFY(textinputObject != 0);
199         QCOMPARE(textinputObject->text(), QString(""));
200
201         delete textinputObject;
202     }
203
204     for (int i = 0; i < standard.size(); i++)
205     {
206         QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
207         QDeclarativeComponent textinputComponent(&engine);
208         textinputComponent.setData(componentStr.toLatin1(), QUrl());
209         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
210
211         QVERIFY(textinputObject != 0);
212         QCOMPARE(textinputObject->text(), standard.at(i));
213
214         delete textinputObject;
215     }
216
217 }
218
219 void tst_qsgtextinput::width()
220 {
221     // uses Font metrics to find the width for standard
222     {
223         QDeclarativeComponent textinputComponent(&engine);
224         textinputComponent.setData("import QtQuick 2.0\nTextInput {  text: \"\" }", QUrl());
225         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
226
227         QVERIFY(textinputObject != 0);
228         QCOMPARE(textinputObject->width(), 0.0);
229
230         delete textinputObject;
231     }
232
233     bool requiresUnhintedMetrics = !qmlDisableDistanceField();
234
235     for (int i = 0; i < standard.size(); i++)
236     {
237         QFont f;
238         qreal metricWidth = 0.0;
239         if (requiresUnhintedMetrics) {
240             QString s = standard.at(i);
241             s.replace(QLatin1Char('\n'), QChar::LineSeparator);
242
243             QTextLayout layout(s);
244             layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
245             {
246                 QTextOption option;
247                 option.setUseDesignMetrics(true);
248                 layout.setTextOption(option);
249             }
250
251             layout.beginLayout();
252             forever {
253                 QTextLine line = layout.createLine();
254                 if (!line.isValid())
255                     break;
256             }
257
258             layout.endLayout();
259
260             metricWidth = ceil(layout.boundingRect().width());
261         } else {
262             QFontMetricsF fm(f);
263             metricWidth = fm.width(standard.at(i));
264         }
265
266         QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
267         QDeclarativeComponent textinputComponent(&engine);
268         textinputComponent.setData(componentStr.toLatin1(), QUrl());
269         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
270
271         QVERIFY(textinputObject != 0);
272         int delta = abs(int(int(textinputObject->width()) - metricWidth));
273         QVERIFY(delta <= 3.0); // As best as we can hope for cross-platform.
274
275         delete textinputObject;
276     }
277 }
278
279 void tst_qsgtextinput::font()
280 {
281     //test size, then bold, then italic, then family
282     {
283         QString componentStr = "import QtQuick 2.0\nTextInput {  font.pointSize: 40; text: \"Hello World\" }";
284         QDeclarativeComponent textinputComponent(&engine);
285         textinputComponent.setData(componentStr.toLatin1(), QUrl());
286         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
287
288         QVERIFY(textinputObject != 0);
289         QCOMPARE(textinputObject->font().pointSize(), 40);
290         QCOMPARE(textinputObject->font().bold(), false);
291         QCOMPARE(textinputObject->font().italic(), false);
292
293         delete textinputObject;
294     }
295
296     {
297         QString componentStr = "import QtQuick 2.0\nTextInput {  font.bold: true; text: \"Hello World\" }";
298         QDeclarativeComponent textinputComponent(&engine);
299         textinputComponent.setData(componentStr.toLatin1(), QUrl());
300         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
301
302         QVERIFY(textinputObject != 0);
303         QCOMPARE(textinputObject->font().bold(), true);
304         QCOMPARE(textinputObject->font().italic(), false);
305
306         delete textinputObject;
307     }
308
309     {
310         QString componentStr = "import QtQuick 2.0\nTextInput {  font.italic: true; text: \"Hello World\" }";
311         QDeclarativeComponent textinputComponent(&engine);
312         textinputComponent.setData(componentStr.toLatin1(), QUrl());
313         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
314
315         QVERIFY(textinputObject != 0);
316         QCOMPARE(textinputObject->font().italic(), true);
317         QCOMPARE(textinputObject->font().bold(), false);
318
319         delete textinputObject;
320     }
321
322     {
323         QString componentStr = "import QtQuick 2.0\nTextInput {  font.family: \"Helvetica\"; text: \"Hello World\" }";
324         QDeclarativeComponent textinputComponent(&engine);
325         textinputComponent.setData(componentStr.toLatin1(), QUrl());
326         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
327
328         QVERIFY(textinputObject != 0);
329         QCOMPARE(textinputObject->font().family(), QString("Helvetica"));
330         QCOMPARE(textinputObject->font().bold(), false);
331         QCOMPARE(textinputObject->font().italic(), false);
332
333         delete textinputObject;
334     }
335
336     {
337         QString componentStr = "import QtQuick 2.0\nTextInput {  font.family: \"\"; text: \"Hello World\" }";
338         QDeclarativeComponent textinputComponent(&engine);
339         textinputComponent.setData(componentStr.toLatin1(), QUrl());
340         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
341
342         QVERIFY(textinputObject != 0);
343         QCOMPARE(textinputObject->font().family(), QString(""));
344
345         delete textinputObject;
346     }
347 }
348
349 void tst_qsgtextinput::color()
350 {
351     //test color
352     for (int i = 0; i < colorStrings.size(); i++)
353     {
354         QString componentStr = "import QtQuick 2.0\nTextInput {  color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
355         QDeclarativeComponent textinputComponent(&engine);
356         textinputComponent.setData(componentStr.toLatin1(), QUrl());
357         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
358         QVERIFY(textinputObject != 0);
359         QCOMPARE(textinputObject->color(), QColor(colorStrings.at(i)));
360
361         delete textinputObject;
362     }
363
364     //test selection color
365     for (int i = 0; i < colorStrings.size(); i++)
366     {
367         QString componentStr = "import QtQuick 2.0\nTextInput {  selectionColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
368         QDeclarativeComponent textinputComponent(&engine);
369         textinputComponent.setData(componentStr.toLatin1(), QUrl());
370         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
371         QVERIFY(textinputObject != 0);
372         QCOMPARE(textinputObject->selectionColor(), QColor(colorStrings.at(i)));
373
374         delete textinputObject;
375     }
376
377     //test selected text color
378     for (int i = 0; i < colorStrings.size(); i++)
379     {
380         QString componentStr = "import QtQuick 2.0\nTextInput {  selectedTextColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
381         QDeclarativeComponent textinputComponent(&engine);
382         textinputComponent.setData(componentStr.toLatin1(), QUrl());
383         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
384         QVERIFY(textinputObject != 0);
385         QCOMPARE(textinputObject->selectedTextColor(), QColor(colorStrings.at(i)));
386
387         delete textinputObject;
388     }
389
390     {
391         QString colorStr = "#AA001234";
392         QColor testColor("#001234");
393         testColor.setAlpha(170);
394
395         QString componentStr = "import QtQuick 2.0\nTextInput {  color: \"" + colorStr + "\"; text: \"Hello World\" }";
396         QDeclarativeComponent textinputComponent(&engine);
397         textinputComponent.setData(componentStr.toLatin1(), QUrl());
398         QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
399
400         QVERIFY(textinputObject != 0);
401         QCOMPARE(textinputObject->color(), testColor);
402
403         delete textinputObject;
404     }
405 }
406
407 void tst_qsgtextinput::selection()
408 {
409     QString testStr = standard[0];
410     QString componentStr = "import QtQuick 2.0\nTextInput {  text: \""+ testStr +"\"; }";
411     QDeclarativeComponent textinputComponent(&engine);
412     textinputComponent.setData(componentStr.toLatin1(), QUrl());
413     QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
414     QVERIFY(textinputObject != 0);
415
416
417     //Test selection follows cursor
418     for (int i=0; i<= testStr.size(); i++) {
419         textinputObject->setCursorPosition(i);
420         QCOMPARE(textinputObject->cursorPosition(), i);
421         QCOMPARE(textinputObject->selectionStart(), i);
422         QCOMPARE(textinputObject->selectionEnd(), i);
423         QVERIFY(textinputObject->selectedText().isNull());
424     }
425
426     textinputObject->setCursorPosition(0);
427     QVERIFY(textinputObject->cursorPosition() == 0);
428     QVERIFY(textinputObject->selectionStart() == 0);
429     QVERIFY(textinputObject->selectionEnd() == 0);
430     QVERIFY(textinputObject->selectedText().isNull());
431
432     // Verify invalid positions are ignored.
433     textinputObject->setCursorPosition(-1);
434     QVERIFY(textinputObject->cursorPosition() == 0);
435     QVERIFY(textinputObject->selectionStart() == 0);
436     QVERIFY(textinputObject->selectionEnd() == 0);
437     QVERIFY(textinputObject->selectedText().isNull());
438
439     textinputObject->setCursorPosition(textinputObject->text().count()+1);
440     QVERIFY(textinputObject->cursorPosition() == 0);
441     QVERIFY(textinputObject->selectionStart() == 0);
442     QVERIFY(textinputObject->selectionEnd() == 0);
443     QVERIFY(textinputObject->selectedText().isNull());
444
445     //Test selection
446     for (int i=0; i<= testStr.size(); i++) {
447         textinputObject->select(0,i);
448         QCOMPARE(testStr.mid(0,i), textinputObject->selectedText());
449     }
450     for (int i=0; i<= testStr.size(); i++) {
451         textinputObject->select(i,testStr.size());
452         QCOMPARE(testStr.mid(i,testStr.size()-i), textinputObject->selectedText());
453     }
454
455     textinputObject->setCursorPosition(0);
456     QVERIFY(textinputObject->cursorPosition() == 0);
457     QVERIFY(textinputObject->selectionStart() == 0);
458     QVERIFY(textinputObject->selectionEnd() == 0);
459     QVERIFY(textinputObject->selectedText().isNull());
460
461     //Test Error Ignoring behaviour
462     textinputObject->setCursorPosition(0);
463     QVERIFY(textinputObject->selectedText().isNull());
464     textinputObject->select(-10,0);
465     QVERIFY(textinputObject->selectedText().isNull());
466     textinputObject->select(100,110);
467     QVERIFY(textinputObject->selectedText().isNull());
468     textinputObject->select(0,-10);
469     QVERIFY(textinputObject->selectedText().isNull());
470     textinputObject->select(0,100);
471     QVERIFY(textinputObject->selectedText().isNull());
472     textinputObject->select(0,10);
473     QVERIFY(textinputObject->selectedText().size() == 10);
474     textinputObject->select(-10,10);
475     QVERIFY(textinputObject->selectedText().size() == 10);
476     textinputObject->select(100,101);
477     QVERIFY(textinputObject->selectedText().size() == 10);
478     textinputObject->select(0,-10);
479     QVERIFY(textinputObject->selectedText().size() == 10);
480     textinputObject->select(0,100);
481     QVERIFY(textinputObject->selectedText().size() == 10);
482
483     textinputObject->deselect();
484     QVERIFY(textinputObject->selectedText().isNull());
485     textinputObject->select(0,10);
486     QVERIFY(textinputObject->selectedText().size() == 10);
487     textinputObject->deselect();
488     QVERIFY(textinputObject->selectedText().isNull());
489
490     delete textinputObject;
491 }
492
493 void tst_qsgtextinput::isRightToLeft_data()
494 {
495     QTest::addColumn<QString>("text");
496     QTest::addColumn<bool>("emptyString");
497     QTest::addColumn<bool>("firstCharacter");
498     QTest::addColumn<bool>("lastCharacter");
499     QTest::addColumn<bool>("middleCharacter");
500     QTest::addColumn<bool>("startString");
501     QTest::addColumn<bool>("midString");
502     QTest::addColumn<bool>("endString");
503
504     const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
505     QTest::newRow("Empty") << "" << false << false << false << false << false << false << false;
506     QTest::newRow("Neutral") << "23244242" << false << false << false << false << false << false << false;
507     QTest::newRow("LTR") << "Hello world" << false << false << false << false << false << false << false;
508     QTest::newRow("RTL") << QString::fromUtf16(arabic_str, 11) << false << true << true << true << true << true << true;
509     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;
510     QTest::newRow("Bidi LTR + RTL + LTR") << QString("Hello world") + QString::fromUtf16(arabic_str, 11) + QString("Hello world") << false << false << false << true << false << false << false;
511 }
512
513 void tst_qsgtextinput::isRightToLeft()
514 {
515     QFETCH(QString, text);
516     QFETCH(bool, emptyString);
517     QFETCH(bool, firstCharacter);
518     QFETCH(bool, lastCharacter);
519     QFETCH(bool, middleCharacter);
520     QFETCH(bool, startString);
521     QFETCH(bool, midString);
522     QFETCH(bool, endString);
523
524     QSGTextInput textInput;
525     textInput.setText(text);
526
527     // first test that the right string is delivered to the QString::isRightToLeft()
528     QCOMPARE(textInput.isRightToLeft(0,0), text.mid(0,0).isRightToLeft());
529     QCOMPARE(textInput.isRightToLeft(0,1), text.mid(0,1).isRightToLeft());
530     QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), text.mid(text.count()-2, text.count()-1).isRightToLeft());
531     QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), text.mid(text.count()/2, text.count()/2 + 1).isRightToLeft());
532     QCOMPARE(textInput.isRightToLeft(0,text.count()/4), text.mid(0,text.count()/4).isRightToLeft());
533     QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), text.mid(text.count()/4,3*text.count()/4).isRightToLeft());
534     if (text.isEmpty())
535         QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
536     QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), text.mid(3*text.count()/4,text.count()-1).isRightToLeft());
537
538     // then test that the feature actually works
539     QCOMPARE(textInput.isRightToLeft(0,0), emptyString);
540     QCOMPARE(textInput.isRightToLeft(0,1), firstCharacter);
541     QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), lastCharacter);
542     QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), middleCharacter);
543     QCOMPARE(textInput.isRightToLeft(0,text.count()/4), startString);
544     QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), midString);
545     if (text.isEmpty())
546         QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
547     QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), endString);
548 }
549
550 void tst_qsgtextinput::moveCursorSelection_data()
551 {
552     QTest::addColumn<QString>("testStr");
553     QTest::addColumn<int>("cursorPosition");
554     QTest::addColumn<int>("movePosition");
555     QTest::addColumn<QSGTextInput::SelectionMode>("mode");
556     QTest::addColumn<int>("selectionStart");
557     QTest::addColumn<int>("selectionEnd");
558     QTest::addColumn<bool>("reversible");
559
560     // () contains the text selected by the cursor.
561     // <> contains the actual selection.
562
563     QTest::newRow("(t)he|characters")
564             << standard[0] << 0 << 1 << QSGTextInput::SelectCharacters << 0 << 1 << true;
565     QTest::newRow("do(g)|characters")
566             << standard[0] << 43 << 44 << QSGTextInput::SelectCharacters << 43 << 44 << true;
567     QTest::newRow("jum(p)ed|characters")
568             << standard[0] << 23 << 24 << QSGTextInput::SelectCharacters << 23 << 24 << true;
569     QTest::newRow("jumped( )over|characters")
570             << standard[0] << 26 << 27 << QSGTextInput::SelectCharacters << 26 << 27 << true;
571     QTest::newRow("(the )|characters")
572             << standard[0] << 0 << 4 << QSGTextInput::SelectCharacters << 0 << 4 << true;
573     QTest::newRow("( dog)|characters")
574             << standard[0] << 40 << 44 << QSGTextInput::SelectCharacters << 40 << 44 << true;
575     QTest::newRow("( jumped )|characters")
576             << standard[0] << 19 << 27 << QSGTextInput::SelectCharacters << 19 << 27 << true;
577     QTest::newRow("th(e qu)ick|characters")
578             << standard[0] << 2 << 6 << QSGTextInput::SelectCharacters << 2 << 6 << true;
579     QTest::newRow("la(zy d)og|characters")
580             << standard[0] << 38 << 42 << QSGTextInput::SelectCharacters << 38 << 42 << true;
581     QTest::newRow("jum(ped ov)er|characters")
582             << standard[0] << 23 << 29 << QSGTextInput::SelectCharacters << 23 << 29 << true;
583     QTest::newRow("()the|characters")
584             << standard[0] << 0 << 0 << QSGTextInput::SelectCharacters << 0 << 0 << true;
585     QTest::newRow("dog()|characters")
586             << standard[0] << 44 << 44 << QSGTextInput::SelectCharacters << 44 << 44 << true;
587     QTest::newRow("jum()ped|characters")
588             << standard[0] << 23 << 23 << QSGTextInput::SelectCharacters << 23 << 23 << true;
589
590     QTest::newRow("<(t)he>|words")
591             << standard[0] << 0 << 1 << QSGTextInput::SelectWords << 0 << 3 << true;
592     QTest::newRow("<do(g)>|words")
593             << standard[0] << 43 << 44 << QSGTextInput::SelectWords << 41 << 44 << true;
594     QTest::newRow("<jum(p)ed>|words")
595             << standard[0] << 23 << 24 << QSGTextInput::SelectWords << 20 << 26 << true;
596     QTest::newRow("<jumped( )>over|words,ltr")
597             << standard[0] << 26 << 27 << QSGTextInput::SelectWords << 20 << 27 << false;
598     QTest::newRow("jumped<( )over>|words,rtl")
599             << standard[0] << 27 << 26 << QSGTextInput::SelectWords << 26 << 31 << false;
600     QTest::newRow("<(the )>quick|words,ltr")
601             << standard[0] << 0 << 4 << QSGTextInput::SelectWords << 0 << 4 << false;
602     QTest::newRow("<(the )quick>|words,rtl")
603             << standard[0] << 4 << 0 << QSGTextInput::SelectWords << 0 << 9 << false;
604     QTest::newRow("<lazy( dog)>|words,ltr")
605             << standard[0] << 40 << 44 << QSGTextInput::SelectWords << 36 << 44 << false;
606     QTest::newRow("lazy<( dog)>|words,rtl")
607             << standard[0] << 44 << 40 << QSGTextInput::SelectWords << 40 << 44 << false;
608     QTest::newRow("<fox( jumped )>over|words,ltr")
609             << standard[0] << 19 << 27 << QSGTextInput::SelectWords << 16 << 27 << false;
610     QTest::newRow("fox<( jumped )over>|words,rtl")
611             << standard[0] << 27 << 19 << QSGTextInput::SelectWords << 19 << 31 << false;
612     QTest::newRow("<th(e qu)ick>|words")
613             << standard[0] << 2 << 6 << QSGTextInput::SelectWords << 0 << 9 << true;
614     QTest::newRow("<la(zy d)og|words>")
615             << standard[0] << 38 << 42 << QSGTextInput::SelectWords << 36 << 44 << true;
616     QTest::newRow("<jum(ped ov)er>|words")
617             << standard[0] << 23 << 29 << QSGTextInput::SelectWords << 20 << 31 << true;
618     QTest::newRow("<()>the|words")
619             << standard[0] << 0 << 0 << QSGTextInput::SelectWords << 0 << 0 << true;
620     QTest::newRow("dog<()>|words")
621             << standard[0] << 44 << 44 << QSGTextInput::SelectWords << 44 << 44 << true;
622     QTest::newRow("jum<()>ped|words")
623             << standard[0] << 23 << 23 << QSGTextInput::SelectWords << 23 << 23 << true;
624
625     QTest::newRow("Hello<(,)> |words")
626             << standard[2] << 5 << 6 << QSGTextInput::SelectWords << 5 << 6 << true;
627     QTest::newRow("Hello<(, )>world|words,ltr")
628             << standard[2] << 5 << 7 << QSGTextInput::SelectWords << 5 << 7 << false;
629     QTest::newRow("Hello<(, )world>|words,rtl")
630             << standard[2] << 7 << 5 << QSGTextInput::SelectWords << 5 << 12 << false;
631     QTest::newRow("<Hel(lo, )>world|words,ltr")
632             << standard[2] << 3 << 7 << QSGTextInput::SelectWords << 0 << 7 << false;
633     QTest::newRow("<Hel(lo, )world>|words,rtl")
634             << standard[2] << 7 << 3 << QSGTextInput::SelectWords << 0 << 12 << false;
635     QTest::newRow("<Hel(lo)>,|words")
636             << standard[2] << 3 << 5 << QSGTextInput::SelectWords << 0 << 5 << true;
637     QTest::newRow("Hello<()>,|words")
638             << standard[2] << 5 << 5 << QSGTextInput::SelectWords << 5 << 5 << true;
639     QTest::newRow("Hello,<()>|words")
640             << standard[2] << 6 << 6 << QSGTextInput::SelectWords << 6 << 6 << true;
641     QTest::newRow("Hello<,( )>world|words,ltr")
642             << standard[2] << 6 << 7 << QSGTextInput::SelectWords << 5 << 7 << false;
643     QTest::newRow("Hello,<( )world>|words,rtl")
644             << standard[2] << 7 << 6 << QSGTextInput::SelectWords << 6 << 12 << false;
645     QTest::newRow("Hello<,( world)>|words,ltr")
646             << standard[2] << 6 << 12 << QSGTextInput::SelectWords << 5 << 12 << false;
647     QTest::newRow("Hello,<( world)>|words,rtl")
648             << standard[2] << 12 << 6 << QSGTextInput::SelectWords << 6 << 12 << false;
649     QTest::newRow("Hello<,( world!)>|words,ltr")
650             << standard[2] << 6 << 13 << QSGTextInput::SelectWords << 5 << 13 << false;
651     QTest::newRow("Hello,<( world!)>|words,rtl")
652             << standard[2] << 13 << 6 << QSGTextInput::SelectWords << 6 << 13 << false;
653     QTest::newRow("Hello<(, world!)>|words")
654             << standard[2] << 5 << 13 << QSGTextInput::SelectWords << 5 << 13 << true;
655     // Fails due to an issue with QTextBoundaryFinder and punctuation at the end of strings.
656     // QTBUG-11365
657     // QTest::newRow("world<(!)>|words")
658     //         << standard[2] << 12 << 13 << QSGTextInput::SelectWords << 12 << 13 << true;
659     QTest::newRow("world!<()>)|words")
660             << standard[2] << 13 << 13 << QSGTextInput::SelectWords << 13 << 13 << true;
661     QTest::newRow("world<()>!)|words")
662             << standard[2] << 12 << 12 << QSGTextInput::SelectWords << 12 << 12 << true;
663
664     QTest::newRow("<(,)>olleH |words")
665             << standard[3] << 7 << 8 << QSGTextInput::SelectWords << 7 << 8 << true;
666     QTest::newRow("<dlrow( ,)>olleH|words,ltr")
667             << standard[3] << 6 << 8 << QSGTextInput::SelectWords << 1 << 8 << false;
668     QTest::newRow("dlrow<( ,)>olleH|words,rtl")
669             << standard[3] << 8 << 6 << QSGTextInput::SelectWords << 6 << 8 << false;
670     QTest::newRow("<dlrow( ,ol)leH>|words,ltr")
671             << standard[3] << 6 << 10 << QSGTextInput::SelectWords << 1 << 13 << false;
672     QTest::newRow("dlrow<( ,ol)leH>|words,rtl")
673             << standard[3] << 10 << 6 << QSGTextInput::SelectWords << 6 << 13 << false;
674     QTest::newRow(",<(ol)leH>,|words")
675             << standard[3] << 8 << 10 << QSGTextInput::SelectWords << 8 << 13 << true;
676     QTest::newRow(",<()>olleH|words")
677             << standard[3] << 8 << 8 << QSGTextInput::SelectWords << 8 << 8 << true;
678     QTest::newRow("<()>,olleH|words")
679             << standard[3] << 7 << 7 << QSGTextInput::SelectWords << 7 << 7 << true;
680     QTest::newRow("<dlrow( )>,olleH|words,ltr")
681             << standard[3] << 6 << 7 << QSGTextInput::SelectWords << 1 << 7 << false;
682     QTest::newRow("dlrow<( ),>olleH|words,rtl")
683             << standard[3] << 7 << 6 << QSGTextInput::SelectWords << 6 << 8 << false;
684     QTest::newRow("<(dlrow )>,olleH|words,ltr")
685             << standard[3] << 1 << 7 << QSGTextInput::SelectWords << 1 << 7 << false;
686     QTest::newRow("<(dlrow ),>olleH|words,rtl")
687             << standard[3] << 7 << 1 << QSGTextInput::SelectWords << 1 << 8 << false;
688     QTest::newRow("<(!dlrow )>,olleH|words,ltr")
689             << standard[3] << 0 << 7 << QSGTextInput::SelectWords << 0 << 7 << false;
690     QTest::newRow("<(!dlrow ),>olleH|words,rtl")
691             << standard[3] << 7 << 0 << QSGTextInput::SelectWords << 0 << 8 << false;
692     QTest::newRow("(!dlrow ,)olleH|words")
693             << standard[3] << 0 << 8 << QSGTextInput::SelectWords << 0 << 8 << true;
694     QTest::newRow("<(!)>dlrow|words")
695             << standard[3] << 0 << 1 << QSGTextInput::SelectWords << 0 << 1 << true;
696     QTest::newRow("<()>!dlrow|words")
697             << standard[3] << 0 << 0 << QSGTextInput::SelectWords << 0 << 0 << true;
698     QTest::newRow("!<()>dlrow|words")
699             << standard[3] << 1 << 1 << QSGTextInput::SelectWords << 1 << 1 << true;
700
701     QTest::newRow(" <s(pac)ey>   text |words")
702             << standard[4] << 1 << 4 << QSGTextInput::SelectWords << 1 << 7 << true;
703     QTest::newRow(" spacey   <t(ex)t> |words")
704             << standard[4] << 11 << 13 << QSGTextInput::SelectWords << 10 << 14 << false; // Should be reversible. QTBUG-11365
705     QTest::newRow("<( )>spacey   text |words|ltr")
706             << standard[4] << 0 << 1 << QSGTextInput::SelectWords << 0 << 1 << false;
707     QTest::newRow("<( )spacey>   text |words|rtl")
708             << standard[4] << 1 << 0 << QSGTextInput::SelectWords << 0 << 7 << false;
709     QTest::newRow("spacey   <text( )>|words|ltr")
710             << standard[4] << 14 << 15 << QSGTextInput::SelectWords << 10 << 15 << false;
711 //    QTBUG-11365
712 //    QTest::newRow("spacey   text<( )>|words|rtl")
713 //            << standard[4] << 15 << 14 << QSGTextInput::SelectWords << 14 << 15 << false;
714     QTest::newRow("<()> spacey   text |words")
715             << standard[4] << 0 << 0 << QSGTextInput::SelectWords << 0 << 0 << false;
716     QTest::newRow(" spacey   text <()>|words")
717             << standard[4] << 15 << 15 << QSGTextInput::SelectWords << 15 << 15 << false;
718 }
719
720 void tst_qsgtextinput::moveCursorSelection()
721 {
722     QFETCH(QString, testStr);
723     QFETCH(int, cursorPosition);
724     QFETCH(int, movePosition);
725     QFETCH(QSGTextInput::SelectionMode, mode);
726     QFETCH(int, selectionStart);
727     QFETCH(int, selectionEnd);
728     QFETCH(bool, reversible);
729
730     QString componentStr = "import QtQuick 2.0\nTextInput {  text: \""+ testStr +"\"; }";
731     QDeclarativeComponent textinputComponent(&engine);
732     textinputComponent.setData(componentStr.toLatin1(), QUrl());
733     QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
734     QVERIFY(textinputObject != 0);
735
736     textinputObject->setCursorPosition(cursorPosition);
737     textinputObject->moveCursorSelection(movePosition, mode);
738
739     QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
740     QCOMPARE(textinputObject->selectionStart(), selectionStart);
741     QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
742
743     if (reversible) {
744         textinputObject->setCursorPosition(movePosition);
745         textinputObject->moveCursorSelection(cursorPosition, mode);
746
747         QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
748         QCOMPARE(textinputObject->selectionStart(), selectionStart);
749         QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
750     }
751
752     delete textinputObject;
753 }
754
755 void tst_qsgtextinput::moveCursorSelectionSequence_data()
756 {
757     QTest::addColumn<QString>("testStr");
758     QTest::addColumn<int>("cursorPosition");
759     QTest::addColumn<int>("movePosition1");
760     QTest::addColumn<int>("movePosition2");
761     QTest::addColumn<int>("selection1Start");
762     QTest::addColumn<int>("selection1End");
763     QTest::addColumn<int>("selection2Start");
764     QTest::addColumn<int>("selection2End");
765
766     // () contains the text selected by the cursor.
767     // <> contains the actual selection.
768     // ^ is the revised cursor position.
769     // {} contains the revised selection.
770
771     QTest::newRow("the {<quick( bro)wn> f^ox} jumped|ltr")
772             << standard[0]
773             << 9 << 13 << 17
774             << 4 << 15
775             << 4 << 19;
776     QTest::newRow("the quick<( {bro)wn> f^ox} jumped|rtl")
777             << standard[0]
778             << 13 << 9 << 17
779             << 9 << 15
780             << 10 << 19;
781     QTest::newRow("the {<quick( bro)wn> ^}fox jumped|ltr")
782             << standard[0]
783             << 9 << 13 << 16
784             << 4 << 15
785             << 4 << 16;
786     QTest::newRow("the quick<( {bro)wn> ^}fox jumped|rtl")
787             << standard[0]
788             << 13 << 9 << 16
789             << 9 << 15
790             << 10 << 16;
791     QTest::newRow("the {<quick( bro)wn^>} fox jumped|ltr")
792             << standard[0]
793             << 9 << 13 << 15
794             << 4 << 15
795             << 4 << 15;
796     QTest::newRow("the quick<( {bro)wn^>} f^ox jumped|rtl")
797             << standard[0]
798             << 13 << 9 << 15
799             << 9 << 15
800             << 10 << 15;
801     QTest::newRow("the {<quick() ^}bro)wn> fox|ltr")
802             << standard[0]
803             << 9 << 13 << 10
804             << 4 << 15
805             << 4 << 10;
806     QTest::newRow("the quick<( {^bro)wn>} fox|rtl")
807             << standard[0]
808             << 13 << 9 << 10
809             << 9 << 15
810             << 10 << 15;
811     QTest::newRow("the {<quick^}( bro)wn> fox|ltr")
812             << standard[0]
813             << 9 << 13 << 9
814             << 4 << 15
815             << 4 << 9;
816     QTest::newRow("the quick{<(^ bro)wn>} fox|rtl")
817             << standard[0]
818             << 13 << 9 << 9
819             << 9 << 15
820             << 9 << 15;
821     QTest::newRow("the {<qui^ck}( bro)wn> fox|ltr")
822             << standard[0]
823             << 9 << 13 << 7
824             << 4 << 15
825             << 4 << 9;
826     QTest::newRow("the {<qui^ck}( bro)wn> fox|rtl")
827             << standard[0]
828             << 13 << 9 << 7
829             << 9 << 15
830             << 4 << 15;
831     QTest::newRow("the {<^quick}( bro)wn> fox|ltr")
832             << standard[0]
833             << 9 << 13 << 4
834             << 4 << 15
835             << 4 << 9;
836     QTest::newRow("the {<^quick}( bro)wn> fox|rtl")
837             << standard[0]
838             << 13 << 9 << 4
839             << 9 << 15
840             << 4 << 15;
841     QTest::newRow("the{^ <quick}( bro)wn> fox|ltr")
842             << standard[0]
843             << 9 << 13 << 3
844             << 4 << 15
845             << 3 << 9;
846     QTest::newRow("the{^ <quick}( bro)wn> fox|rtl")
847             << standard[0]
848             << 13 << 9 << 3
849             << 9 << 15
850             << 3 << 15;
851     QTest::newRow("{t^he <quick}( bro)wn> fox|ltr")
852             << standard[0]
853             << 9 << 13 << 1
854             << 4 << 15
855             << 0 << 9;
856     QTest::newRow("{t^he <quick}( bro)wn> fox|rtl")
857             << standard[0]
858             << 13 << 9 << 1
859             << 9 << 15
860             << 0 << 15;
861
862     QTest::newRow("{<He(ll)o>, w^orld}!|ltr")
863             << standard[2]
864             << 2 << 4 << 8
865             << 0 << 5
866             << 0 << 12;
867     QTest::newRow("{<He(ll)o>, w^orld}!|rtl")
868             << standard[2]
869             << 4 << 2 << 8
870             << 0 << 5
871             << 0 << 12;
872
873     QTest::newRow("!{dlro^w ,<o(ll)eH>}|ltr")
874             << standard[3]
875             << 9 << 11 << 5
876             << 8 << 13
877             << 1 << 13;
878     QTest::newRow("!{dlro^w ,<o(ll)eH>}|rtl")
879             << standard[3]
880             << 11 << 9 << 5
881             << 8 << 13
882             << 1 << 13;
883
884     QTest::newRow("{<(^} sp)acey>   text |ltr")
885             << standard[4]
886             << 0 << 3 << 0
887             << 0 << 7
888             << 0 << 0;
889     QTest::newRow("{<( ^}sp)acey>   text |ltr")
890             << standard[4]
891             << 0 << 3 << 1
892             << 0 << 7
893             << 0 << 1;
894     QTest::newRow("<( {s^p)acey>}   text |rtl")
895             << standard[4]
896             << 3 << 0 << 2
897             << 0 << 7
898             << 1 << 7;
899     QTest::newRow("<( {^sp)acey>}   text |rtl")
900             << standard[4]
901             << 3 << 0 << 1
902             << 0 << 7
903             << 1 << 7;
904
905     QTest::newRow(" spacey   <te(xt {^)>}|rtl")
906             << standard[4]
907             << 15 << 12 << 15
908             << 10 << 15
909             << 15 << 15;
910 //    QTBUG-11365
911 //    QTest::newRow(" spacey   <te(xt{^ )>}|rtl")
912 //            << standard[4]
913 //            << 15 << 12 << 14
914 //            << 10 << 15
915 //            << 14 << 15;
916     QTest::newRow(" spacey   {<te(x^t} )>|ltr")
917             << standard[4]
918             << 12 << 15 << 13
919             << 10 << 15
920             << 10 << 14;
921 //    QTBUG-11365
922 //    QTest::newRow(" spacey   {<te(xt^} )>|ltr")
923 //            << standard[4]
924 //            << 12 << 15 << 14
925 //            << 10 << 15
926 //            << 10 << 14;
927 }
928
929 void tst_qsgtextinput::moveCursorSelectionSequence()
930 {
931     QFETCH(QString, testStr);
932     QFETCH(int, cursorPosition);
933     QFETCH(int, movePosition1);
934     QFETCH(int, movePosition2);
935     QFETCH(int, selection1Start);
936     QFETCH(int, selection1End);
937     QFETCH(int, selection2Start);
938     QFETCH(int, selection2End);
939
940     QString componentStr = "import QtQuick 2.0\nTextInput {  text: \""+ testStr +"\"; }";
941     QDeclarativeComponent textinputComponent(&engine);
942     textinputComponent.setData(componentStr.toLatin1(), QUrl());
943     QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
944     QVERIFY(textinputObject != 0);
945
946     textinputObject->setCursorPosition(cursorPosition);
947
948     textinputObject->moveCursorSelection(movePosition1, QSGTextInput::SelectWords);
949     QCOMPARE(textinputObject->selectedText(), testStr.mid(selection1Start, selection1End - selection1Start));
950     QCOMPARE(textinputObject->selectionStart(), selection1Start);
951     QCOMPARE(textinputObject->selectionEnd(), selection1End);
952
953     textinputObject->moveCursorSelection(movePosition2, QSGTextInput::SelectWords);
954     QCOMPARE(textinputObject->selectedText(), testStr.mid(selection2Start, selection2End - selection2Start));
955     QCOMPARE(textinputObject->selectionStart(), selection2Start);
956     QCOMPARE(textinputObject->selectionEnd(), selection2End);
957
958     delete textinputObject;
959 }
960
961 void tst_qsgtextinput::dragMouseSelection()
962 {
963     QString qmlfile = TESTDATA("mouseselection_true.qml");
964
965     QSGView canvas(QUrl::fromLocalFile(qmlfile));
966
967     canvas.show();
968     canvas.requestActivateWindow();
969     QTest::qWaitForWindowShown(&canvas);
970
971     QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
972
973     QVERIFY(canvas.rootObject() != 0);
974     QSGTextInput *textInputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
975     QVERIFY(textInputObject != 0);
976
977     // press-and-drag-and-release from x1 to x2
978     int x1 = 10;
979     int x2 = 70;
980     int y = textInputObject->height()/2;
981     QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
982     QTest::mouseMove(&canvas, QPoint(x2, y));
983     QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
984     QTest::qWait(100);
985     QString str1;
986     QVERIFY((str1 = textInputObject->selectedText()).length() > 3);
987     QVERIFY(str1.length() > 3);
988
989     // press and drag the current selection.
990     x1 = 40;
991     x2 = 100;
992     QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
993     QTest::mouseMove(&canvas, QPoint(x2, y));
994     QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
995     QTest::qWait(300);
996     QString str2 = textInputObject->selectedText();
997     QVERIFY(str2.length() > 3);
998
999     QVERIFY(str1 != str2);
1000 }
1001
1002 void tst_qsgtextinput::mouseSelectionMode_data()
1003 {
1004     QTest::addColumn<QString>("qmlfile");
1005     QTest::addColumn<bool>("selectWords");
1006
1007     // import installed
1008     QTest::newRow("SelectWords") << TESTDATA("mouseselectionmode_words.qml") << true;
1009     QTest::newRow("SelectCharacters") << TESTDATA("mouseselectionmode_characters.qml") << false;
1010     QTest::newRow("default") << TESTDATA("mouseselectionmode_default.qml") << false;
1011 }
1012
1013 void tst_qsgtextinput::mouseSelectionMode()
1014 {
1015     QFETCH(QString, qmlfile);
1016     QFETCH(bool, selectWords);
1017
1018     QString text = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1019
1020     QSGView canvas(QUrl::fromLocalFile(qmlfile));
1021
1022     canvas.show();
1023     canvas.requestActivateWindow();
1024     QTest::qWaitForWindowShown(&canvas);
1025     QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1026
1027     QVERIFY(canvas.rootObject() != 0);
1028     QSGTextInput *textInputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
1029     QVERIFY(textInputObject != 0);
1030
1031     // press-and-drag-and-release from x1 to x2
1032     int x1 = 10;
1033     int x2 = 70;
1034     int y = textInputObject->height()/2;
1035     QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
1036     QTest::mouseMove(&canvas, QPoint(x2,y)); // doesn't work
1037     QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
1038     QTest::qWait(300);
1039     if (selectWords) {
1040         QTRY_COMPARE(textInputObject->selectedText(), text);
1041     } else {
1042         QTRY_VERIFY(textInputObject->selectedText().length() > 3);
1043         QVERIFY(textInputObject->selectedText() != text);
1044     }
1045 }
1046
1047 void tst_qsgtextinput::horizontalAlignment_data()
1048 {
1049     QTest::addColumn<int>("hAlign");
1050     QTest::addColumn<QString>("expectfile");
1051
1052     QTest::newRow("L") << int(Qt::AlignLeft) << "halign_left";
1053     QTest::newRow("R") << int(Qt::AlignRight) << "halign_right";
1054     QTest::newRow("C") << int(Qt::AlignHCenter) << "halign_center";
1055 }
1056
1057 void tst_qsgtextinput::horizontalAlignment()
1058 {
1059     QSKIP("Image comparison of text is almost guaranteed to fail during development");
1060
1061     QFETCH(int, hAlign);
1062     QFETCH(QString, expectfile);
1063
1064     QSGView canvas(QUrl::fromLocalFile(TESTDATA("horizontalAlignment.qml")));
1065
1066     canvas.show();
1067     canvas.requestActivateWindow();
1068     QTest::qWaitForWindowShown(&canvas);
1069     QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1070     QObject *ob = canvas.rootObject();
1071     QVERIFY(ob != 0);
1072     ob->setProperty("horizontalAlignment",hAlign);
1073     QImage actual = canvas.grabFrameBuffer();
1074
1075     expectfile = createExpectedFileIfNotFound(expectfile, actual);
1076
1077     QImage expect(expectfile);
1078
1079     QCOMPARE(actual,expect);
1080 }
1081
1082 void tst_qsgtextinput::horizontalAlignment_RightToLeft()
1083 {
1084     QSGView canvas(QUrl::fromLocalFile(TESTDATA("horizontalAlignment_RightToLeft.qml")));
1085     QSGTextInput *textInput = canvas.rootObject()->findChild<QSGTextInput*>("text");
1086     QVERIFY(textInput != 0);
1087     canvas.show();
1088
1089     const QString rtlText = textInput->text();
1090
1091     QSGTextInputPrivate *textInputPrivate = QSGTextInputPrivate::get(textInput);
1092     QVERIFY(textInputPrivate != 0);
1093     QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1094
1095     // implicit alignment should follow the reading direction of RTL text
1096     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1097     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1098     QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1099
1100     // explicitly left aligned
1101     textInput->setHAlign(QSGTextInput::AlignLeft);
1102     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignLeft);
1103     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1104     QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1105
1106     // explicitly right aligned
1107     textInput->setHAlign(QSGTextInput::AlignRight);
1108     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1109     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1110     QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1111
1112     // explicitly center aligned
1113     textInput->setHAlign(QSGTextInput::AlignHCenter);
1114     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1115     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignHCenter);
1116     QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1117     QVERIFY(-textInputPrivate->hscroll + textInputPrivate->width > canvas.width()/2);
1118
1119     // reseted alignment should go back to following the text reading direction
1120     textInput->resetHAlign();
1121     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1122     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1123     QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1124
1125     // mirror the text item
1126     QSGItemPrivate::get(textInput)->setLayoutMirror(true);
1127
1128     // mirrored implicit alignment should continue to follow the reading direction of the text
1129     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1130     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1131     QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1132
1133     // explicitly right aligned behaves as left aligned
1134     textInput->setHAlign(QSGTextInput::AlignRight);
1135     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1136     QCOMPARE(textInput->effectiveHAlign(), QSGTextInput::AlignLeft);
1137     QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1138
1139     // mirrored explicitly left aligned behaves as right aligned
1140     textInput->setHAlign(QSGTextInput::AlignLeft);
1141     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignLeft);
1142     QCOMPARE(textInput->effectiveHAlign(), QSGTextInput::AlignRight);
1143     QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1144
1145     // disable mirroring
1146     QSGItemPrivate::get(textInput)->setLayoutMirror(false);
1147     QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1148     textInput->resetHAlign();
1149
1150     // English text should be implicitly left aligned
1151     textInput->setText("Hello world!");
1152     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignLeft);
1153     QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1154
1155     canvas.requestActivateWindow();
1156     QTest::qWaitForWindowShown(&canvas);
1157     QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1158
1159     // If there is no commited text, the preedit text should determine the alignment.
1160     textInput->setText(QString());
1161     { QInputMethodEvent ev(rtlText, QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(&canvas, &ev); }
1162     QEXPECT_FAIL("", "QTBUG-21691", Continue);
1163     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1164     { QInputMethodEvent ev("Hello world!", QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(&canvas, &ev); }
1165     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignLeft);
1166
1167 #ifndef Q_OS_MAC    // QTBUG-18040
1168     // empty text with implicit alignment follows the system locale-based
1169     // keyboard input direction from QGuiApplication::keyboardInputDirection
1170     textInput->setText("");
1171     QCOMPARE(textInput->hAlign(), QGuiApplication::keyboardInputDirection() == Qt::LeftToRight ?
1172                                   QSGTextInput::AlignLeft : QSGTextInput::AlignRight);
1173     if (QGuiApplication::keyboardInputDirection() == Qt::LeftToRight)
1174         QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1175     else
1176         QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1177     textInput->setHAlign(QSGTextInput::AlignRight);
1178     QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1179     QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1180 #endif
1181
1182 #ifndef Q_OS_MAC    // QTBUG-18040
1183     // alignment of TextInput with no text set to it
1184     QString componentStr = "import QtQuick 2.0\nTextInput {}";
1185     QDeclarativeComponent textComponent(&engine);
1186     textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
1187     QSGTextInput *textObject = qobject_cast<QSGTextInput*>(textComponent.create());
1188     QCOMPARE(textObject->hAlign(), QGuiApplication::keyboardInputDirection() == Qt::LeftToRight ?
1189                                   QSGTextInput::AlignLeft : QSGTextInput::AlignRight);
1190     delete textObject;
1191 #endif
1192 }
1193
1194 void tst_qsgtextinput::positionAt()
1195 {
1196     QSGView canvas(QUrl::fromLocalFile(TESTDATA("positionAt.qml")));
1197     QVERIFY(canvas.rootObject() != 0);
1198     canvas.show();
1199     canvas.requestActivateWindow();
1200     QTest::qWaitForWindowShown(&canvas);
1201
1202     QSGTextInput *textinputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
1203     QVERIFY(textinputObject != 0);
1204
1205     // Check autoscrolled...
1206     QFontMetrics fm(textinputObject->font());
1207
1208     int pos = textinputObject->positionAt(textinputObject->width()/2);
1209     int textWidth = 0;
1210     int textLeftWidth = 0;
1211     if (!qmlDisableDistanceField()) {
1212         {
1213             QTextLayout layout(textinputObject->text().left(pos));
1214
1215             {
1216                 QTextOption option;
1217                 option.setUseDesignMetrics(true);
1218                 layout.setTextOption(option);
1219             }
1220
1221             layout.beginLayout();
1222             QTextLine line = layout.createLine();
1223             layout.endLayout();
1224
1225             textLeftWidth = ceil(line.horizontalAdvance());
1226         }
1227         {
1228             QTextLayout layout(textinputObject->text());
1229
1230             {
1231                 QTextOption option;
1232                 option.setUseDesignMetrics(true);
1233                 layout.setTextOption(option);
1234             }
1235
1236             layout.beginLayout();
1237             QTextLine line = layout.createLine();
1238             layout.endLayout();
1239
1240             textWidth = ceil(line.horizontalAdvance());
1241         }
1242     } else {
1243         textWidth = fm.width(textinputObject->text());
1244         textLeftWidth = fm.width(textinputObject->text().left(pos));
1245     }
1246
1247     int diff = abs(textWidth - (textLeftWidth+textinputObject->width()/2));
1248
1249     // some tollerance for different fonts.
1250     QEXPECT_FAIL("", "QTBUG-21689", Abort);
1251 #ifdef Q_OS_LINUX
1252     QVERIFY(diff < 2);
1253 #else
1254     QVERIFY(diff < 5);
1255 #endif
1256
1257     int x = textinputObject->positionToRectangle(pos + 1).x() - 1;
1258     QCOMPARE(textinputObject->positionAt(x, QSGTextInput::CursorBetweenCharacters), pos + 1);
1259     QCOMPARE(textinputObject->positionAt(x, QSGTextInput::CursorOnCharacter), pos);
1260
1261     // Check without autoscroll...
1262     textinputObject->setAutoScroll(false);
1263     pos = textinputObject->positionAt(textinputObject->width()/2);
1264
1265     if (!qmlDisableDistanceField()) {
1266         {
1267             QTextLayout layout(textinputObject->text().left(pos));
1268
1269             {
1270                 QTextOption option;
1271                 option.setUseDesignMetrics(true);
1272                 layout.setTextOption(option);
1273             }
1274
1275             layout.beginLayout();
1276             QTextLine line = layout.createLine();
1277             layout.endLayout();
1278
1279             textLeftWidth = ceil(line.horizontalAdvance());
1280         }
1281     } else {
1282         textLeftWidth = fm.width(textinputObject->text().left(pos));
1283     }
1284
1285     diff = abs(int(textLeftWidth-textinputObject->width()/2));
1286
1287     // some tollerance for different fonts.
1288 #ifdef Q_OS_LINUX
1289     QVERIFY(diff < 2);
1290 #else
1291     QVERIFY(diff < 5);
1292 #endif
1293
1294     x = textinputObject->positionToRectangle(pos + 1).x() - 1;
1295     QCOMPARE(textinputObject->positionAt(x, QSGTextInput::CursorBetweenCharacters), pos + 1);
1296     QCOMPARE(textinputObject->positionAt(x, QSGTextInput::CursorOnCharacter), pos);
1297
1298     const qreal x0 = textinputObject->positionToRectangle(pos).x();
1299     const qreal x1 = textinputObject->positionToRectangle(pos + 1).x();
1300
1301     QString preeditText = textinputObject->text().mid(0, pos);
1302     textinputObject->setText(textinputObject->text().mid(pos));
1303     textinputObject->setCursorPosition(0);
1304
1305     QInputMethodEvent inputEvent(preeditText, QList<QInputMethodEvent::Attribute>());
1306     QGuiApplication::sendEvent(&canvas, &inputEvent);
1307
1308     // Check all points within the preedit text return the same position.
1309     QCOMPARE(textinputObject->positionAt(0), 0);
1310     QCOMPARE(textinputObject->positionAt(x0 / 2), 0);
1311     QCOMPARE(textinputObject->positionAt(x0), 0);
1312
1313     // Verify positioning returns to normal after the preedit text.
1314     QCOMPARE(textinputObject->positionAt(x1), 1);
1315     QCOMPARE(textinputObject->positionToRectangle(1).x(), x1);
1316 }
1317
1318 void tst_qsgtextinput::maxLength()
1319 {
1320     QSGView canvas(QUrl::fromLocalFile(TESTDATA("maxLength.qml")));
1321     QVERIFY(canvas.rootObject() != 0);
1322     canvas.show();
1323     canvas.requestActivateWindow();
1324     QTest::qWaitForWindowShown(&canvas);
1325
1326     QSGTextInput *textinputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
1327     QVERIFY(textinputObject != 0);
1328     QVERIFY(textinputObject->text().isEmpty());
1329     QVERIFY(textinputObject->maxLength() == 10);
1330     foreach (const QString &str, standard) {
1331         QVERIFY(textinputObject->text().length() <= 10);
1332         textinputObject->setText(str);
1333         QVERIFY(textinputObject->text().length() <= 10);
1334     }
1335
1336     textinputObject->setText("");
1337     QTRY_VERIFY(textinputObject->hasActiveFocus() == true);
1338     for (int i=0; i<20; i++) {
1339         QTRY_COMPARE(textinputObject->text().length(), qMin(i,10));
1340         //simulateKey(&canvas, Qt::Key_A);
1341         QTest::keyPress(&canvas, Qt::Key_A);
1342         QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1343         QTest::qWait(50);
1344     }
1345 }
1346
1347 void tst_qsgtextinput::masks()
1348 {
1349     //Not a comprehensive test of the possible masks, that's done elsewhere (QLineEdit)
1350     //QString componentStr = "import QtQuick 2.0\nTextInput {  inputMask: 'HHHHhhhh'; }";
1351     QSGView canvas(QUrl::fromLocalFile(TESTDATA("masks.qml")));
1352     canvas.show();
1353     canvas.requestActivateWindow();
1354     QVERIFY(canvas.rootObject() != 0);
1355     QSGTextInput *textinputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
1356     QVERIFY(textinputObject != 0);
1357     QTRY_VERIFY(textinputObject->hasActiveFocus() == true);
1358     QVERIFY(textinputObject->text().length() == 0);
1359     QCOMPARE(textinputObject->inputMask(), QString("HHHHhhhh; "));
1360     for (int i=0; i<10; i++) {
1361         QTRY_COMPARE(qMin(i,8), textinputObject->text().length());
1362         QCOMPARE(i>=4, textinputObject->hasAcceptableInput());
1363         //simulateKey(&canvas, Qt::Key_A);
1364         QTest::keyPress(&canvas, Qt::Key_A);
1365         QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1366         QTest::qWait(50);
1367     }
1368 }
1369
1370 void tst_qsgtextinput::validators()
1371 {
1372     // Note that this test assumes that the validators are working properly
1373     // so you may need to run their tests first. All validators are checked
1374     // here to ensure that their exposure to QML is working.
1375
1376     QSGView canvas(QUrl::fromLocalFile(TESTDATA("validators.qml")));
1377     canvas.show();
1378     canvas.requestActivateWindow();
1379
1380     QVERIFY(canvas.rootObject() != 0);
1381
1382     QSGTextInput *intInput = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("intInput")));
1383     QVERIFY(intInput);
1384     intInput->setFocus(true);
1385     QTRY_VERIFY(intInput->hasActiveFocus());
1386     QTest::keyPress(&canvas, Qt::Key_1);
1387     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1388     QTest::qWait(50);
1389     QTRY_COMPARE(intInput->text(), QLatin1String("1"));
1390     QCOMPARE(intInput->hasAcceptableInput(), false);
1391     QTest::keyPress(&canvas, Qt::Key_2);
1392     QTest::keyRelease(&canvas, Qt::Key_2, Qt::NoModifier ,10);
1393     QTest::qWait(50);
1394     QTRY_COMPARE(intInput->text(), QLatin1String("1"));
1395     QCOMPARE(intInput->hasAcceptableInput(), false);
1396     QTest::keyPress(&canvas, Qt::Key_1);
1397     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1398     QTest::qWait(50);
1399     QCOMPARE(intInput->text(), QLatin1String("11"));
1400     QCOMPARE(intInput->hasAcceptableInput(), true);
1401     QTest::keyPress(&canvas, Qt::Key_0);
1402     QTest::keyRelease(&canvas, Qt::Key_0, Qt::NoModifier ,10);
1403     QTest::qWait(50);
1404     QCOMPARE(intInput->text(), QLatin1String("11"));
1405     QCOMPARE(intInput->hasAcceptableInput(), true);
1406
1407     QSGTextInput *dblInput = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("dblInput")));
1408     QTRY_VERIFY(dblInput);
1409     dblInput->setFocus(true);
1410     QVERIFY(dblInput->hasActiveFocus() == true);
1411     QTest::keyPress(&canvas, Qt::Key_1);
1412     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1413     QTest::qWait(50);
1414     QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
1415     QCOMPARE(dblInput->hasAcceptableInput(), false);
1416     QTest::keyPress(&canvas, Qt::Key_2);
1417     QTest::keyRelease(&canvas, Qt::Key_2, Qt::NoModifier ,10);
1418     QTest::qWait(50);
1419     QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
1420     QCOMPARE(dblInput->hasAcceptableInput(), true);
1421     QTest::keyPress(&canvas, Qt::Key_Period);
1422     QTest::keyRelease(&canvas, Qt::Key_Period, Qt::NoModifier ,10);
1423     QTest::qWait(50);
1424     QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
1425     QCOMPARE(dblInput->hasAcceptableInput(), true);
1426     QTest::keyPress(&canvas, Qt::Key_1);
1427     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1428     QTest::qWait(50);
1429     QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
1430     QCOMPARE(dblInput->hasAcceptableInput(), true);
1431     QTest::keyPress(&canvas, Qt::Key_1);
1432     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1433     QTest::qWait(50);
1434     QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
1435     QCOMPARE(dblInput->hasAcceptableInput(), true);
1436     QTest::keyPress(&canvas, Qt::Key_1);
1437     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1438     QTest::qWait(50);
1439     QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
1440     QCOMPARE(dblInput->hasAcceptableInput(), true);
1441
1442     QSGTextInput *strInput = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("strInput")));
1443     QTRY_VERIFY(strInput);
1444     strInput->setFocus(true);
1445     QVERIFY(strInput->hasActiveFocus() == true);
1446     QTest::keyPress(&canvas, Qt::Key_1);
1447     QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1448     QTest::qWait(50);
1449     QTRY_COMPARE(strInput->text(), QLatin1String(""));
1450     QCOMPARE(strInput->hasAcceptableInput(), false);
1451     QTest::keyPress(&canvas, Qt::Key_A);
1452     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1453     QTest::qWait(50);
1454     QTRY_COMPARE(strInput->text(), QLatin1String("a"));
1455     QCOMPARE(strInput->hasAcceptableInput(), false);
1456     QTest::keyPress(&canvas, Qt::Key_A);
1457     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1458     QTest::qWait(50);
1459     QTRY_COMPARE(strInput->text(), QLatin1String("aa"));
1460     QCOMPARE(strInput->hasAcceptableInput(), true);
1461     QTest::keyPress(&canvas, Qt::Key_A);
1462     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1463     QTest::qWait(50);
1464     QTRY_COMPARE(strInput->text(), QLatin1String("aaa"));
1465     QCOMPARE(strInput->hasAcceptableInput(), true);
1466     QTest::keyPress(&canvas, Qt::Key_A);
1467     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1468     QTest::qWait(50);
1469     QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
1470     QCOMPARE(strInput->hasAcceptableInput(), true);
1471     QTest::keyPress(&canvas, Qt::Key_A);
1472     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1473     QTest::qWait(50);
1474     QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
1475     QCOMPARE(strInput->hasAcceptableInput(), true);
1476 }
1477
1478 void tst_qsgtextinput::inputMethods()
1479 {
1480     QSGView canvas(QUrl::fromLocalFile(TESTDATA("inputmethods.qml")));
1481     canvas.show();
1482     canvas.requestActivateWindow();
1483     QTest::qWaitForWindowShown(&canvas);
1484
1485     // test input method hints
1486     QVERIFY(canvas.rootObject() != 0);
1487     QSGTextInput *input = qobject_cast<QSGTextInput *>(canvas.rootObject());
1488     QVERIFY(input != 0);
1489     QVERIFY(input->inputMethodHints() & Qt::ImhNoPredictiveText);
1490     input->setInputMethodHints(Qt::ImhUppercaseOnly);
1491     QVERIFY(input->inputMethodHints() & Qt::ImhUppercaseOnly);
1492
1493     input->setFocus(true);
1494     QVERIFY(input->hasActiveFocus() == true);
1495     // test that input method event is committed
1496     QInputMethodEvent event;
1497     event.setCommitString( "My ", -12, 0);
1498     QGuiApplication::sendEvent(&canvas, &event);
1499     QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
1500     QCOMPARE(input->text(), QString("My Hello world!"));
1501
1502     input->setCursorPosition(2);
1503     event.setCommitString("Your", -2, 2);
1504     QGuiApplication::sendEvent(&canvas, &event);
1505     QCOMPARE(input->text(), QString("Your Hello world!"));
1506     QCOMPARE(input->cursorPosition(), 4);
1507
1508     input->setCursorPosition(7);
1509     event.setCommitString("Goodbye", -2, 5);
1510     QGuiApplication::sendEvent(&canvas, &event);
1511     QCOMPARE(input->text(), QString("Your Goodbye world!"));
1512     QCOMPARE(input->cursorPosition(), 12);
1513
1514     input->setCursorPosition(8);
1515     event.setCommitString("Our", -8, 4);
1516     QGuiApplication::sendEvent(&canvas, &event);
1517     QCOMPARE(input->text(), QString("Our Goodbye world!"));
1518     QCOMPARE(input->cursorPosition(), 7);
1519 }
1520
1521 /*
1522 TextInput element should only handle left/right keys until the cursor reaches
1523 the extent of the text, then they should ignore the keys.
1524
1525 */
1526 void tst_qsgtextinput::navigation()
1527 {
1528     QSGView canvas(QUrl::fromLocalFile(TESTDATA("navigation.qml")));
1529     canvas.show();
1530     canvas.requestActivateWindow();
1531
1532     QVERIFY(canvas.rootObject() != 0);
1533
1534     QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1535
1536     QVERIFY(input != 0);
1537     input->setCursorPosition(0);
1538     QTRY_VERIFY(input->hasActiveFocus() == true);
1539     simulateKey(&canvas, Qt::Key_Left);
1540     QVERIFY(input->hasActiveFocus() == false);
1541     simulateKey(&canvas, Qt::Key_Right);
1542     QVERIFY(input->hasActiveFocus() == true);
1543     //QT-2944: If text is selected, ensure we deselect upon cursor motion
1544     input->setCursorPosition(input->text().length());
1545     input->select(0,input->text().length());
1546     QVERIFY(input->selectionStart() != input->selectionEnd());
1547     simulateKey(&canvas, Qt::Key_Right);
1548     QVERIFY(input->selectionStart() == input->selectionEnd());
1549     QVERIFY(input->selectionStart() == input->text().length());
1550     QVERIFY(input->hasActiveFocus() == true);
1551     simulateKey(&canvas, Qt::Key_Right);
1552     QVERIFY(input->hasActiveFocus() == false);
1553     simulateKey(&canvas, Qt::Key_Left);
1554     QVERIFY(input->hasActiveFocus() == true);
1555
1556     // Up and Down should NOT do Home/End, even on Mac OS X (QTBUG-10438).
1557     input->setCursorPosition(2);
1558     QCOMPARE(input->cursorPosition(),2);
1559     simulateKey(&canvas, Qt::Key_Up);
1560     QCOMPARE(input->cursorPosition(),2);
1561     simulateKey(&canvas, Qt::Key_Down);
1562     QCOMPARE(input->cursorPosition(),2);
1563 }
1564
1565 void tst_qsgtextinput::navigation_RTL()
1566 {
1567     QSGView canvas(QUrl::fromLocalFile(TESTDATA("navigation.qml")));
1568     canvas.show();
1569     canvas.requestActivateWindow();
1570
1571     QVERIFY(canvas.rootObject() != 0);
1572
1573     QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1574
1575     QVERIFY(input != 0);
1576     const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
1577     input->setText(QString::fromUtf16(arabic_str, 11));
1578
1579     input->setCursorPosition(0);
1580     QTRY_VERIFY(input->hasActiveFocus() == true);
1581
1582     // move off
1583     simulateKey(&canvas, Qt::Key_Right);
1584     QVERIFY(input->hasActiveFocus() == false);
1585
1586     // move back
1587     simulateKey(&canvas, Qt::Key_Left);
1588     QVERIFY(input->hasActiveFocus() == true);
1589
1590     input->setCursorPosition(input->text().length());
1591     QVERIFY(input->hasActiveFocus() == true);
1592
1593     // move off
1594     simulateKey(&canvas, Qt::Key_Left);
1595     QVERIFY(input->hasActiveFocus() == false);
1596
1597     // move back
1598     simulateKey(&canvas, Qt::Key_Right);
1599     QVERIFY(input->hasActiveFocus() == true);
1600 }
1601
1602 void tst_qsgtextinput::copyAndPaste() {
1603 #ifndef QT_NO_CLIPBOARD
1604
1605 #ifdef Q_WS_MAC
1606     {
1607         PasteboardRef pasteboard;
1608         OSStatus status = PasteboardCreate(0, &pasteboard);
1609         if (status == noErr)
1610             CFRelease(pasteboard);
1611         else
1612             QSKIP("This machine doesn't support the clipboard");
1613     }
1614 #endif
1615
1616     QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
1617     QDeclarativeComponent textInputComponent(&engine);
1618     textInputComponent.setData(componentStr.toLatin1(), QUrl());
1619     QSGTextInput *textInput = qobject_cast<QSGTextInput*>(textInputComponent.create());
1620     QVERIFY(textInput != 0);
1621
1622     // copy and paste
1623     QCOMPARE(textInput->text().length(), 12);
1624     textInput->select(0, textInput->text().length());;
1625     textInput->copy();
1626     QCOMPARE(textInput->selectedText(), QString("Hello world!"));
1627     QCOMPARE(textInput->selectedText().length(), 12);
1628     textInput->setCursorPosition(0);
1629     QVERIFY(textInput->canPaste());
1630     textInput->paste();
1631     QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
1632     QCOMPARE(textInput->text().length(), 24);
1633
1634     // can paste
1635     QVERIFY(textInput->canPaste());
1636     textInput->setReadOnly(true);
1637     QVERIFY(!textInput->canPaste());
1638     textInput->setReadOnly(false);
1639     QVERIFY(textInput->canPaste());
1640
1641     // select word
1642     textInput->setCursorPosition(0);
1643     textInput->selectWord();
1644     QCOMPARE(textInput->selectedText(), QString("Hello"));
1645
1646     // select all and cut
1647     textInput->selectAll();
1648     textInput->cut();
1649     QCOMPARE(textInput->text().length(), 0);
1650     textInput->paste();
1651     QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
1652     QCOMPARE(textInput->text().length(), 24);
1653
1654     // clear copy buffer
1655     QClipboard *clipboard = QGuiApplication::clipboard();
1656     QVERIFY(clipboard);
1657     clipboard->clear();
1658     QVERIFY(!textInput->canPaste());
1659
1660     // test that copy functionality is disabled
1661     // when echo mode is set to hide text/password mode
1662     int index = 0;
1663     while (index < 4) {
1664         QSGTextInput::EchoMode echoMode = QSGTextInput::EchoMode(index);
1665         textInput->setEchoMode(echoMode);
1666         textInput->setText("My password");
1667         textInput->select(0, textInput->text().length());;
1668         textInput->copy();
1669         if (echoMode == QSGTextInput::Normal) {
1670             QVERIFY(!clipboard->text().isEmpty());
1671             QCOMPARE(clipboard->text(), QString("My password"));
1672             clipboard->clear();
1673         } else {
1674             QVERIFY(clipboard->text().isEmpty());
1675         }
1676         index++;
1677     }
1678
1679     delete textInput;
1680 #endif
1681 }
1682
1683 void tst_qsgtextinput::canPasteEmpty() {
1684 #ifndef QT_NO_CLIPBOARD
1685
1686     QGuiApplication::clipboard()->clear();
1687
1688     QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
1689     QDeclarativeComponent textInputComponent(&engine);
1690     textInputComponent.setData(componentStr.toLatin1(), QUrl());
1691     QSGTextInput *textInput = qobject_cast<QSGTextInput*>(textInputComponent.create());
1692     QVERIFY(textInput != 0);
1693
1694     QLineControl lc;
1695     bool cp = !lc.isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
1696     QCOMPARE(textInput->canPaste(), cp);
1697
1698 #endif
1699 }
1700
1701 void tst_qsgtextinput::canPaste() {
1702 #ifndef QT_NO_CLIPBOARD
1703
1704     QGuiApplication::clipboard()->setText("Some text");
1705
1706     QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
1707     QDeclarativeComponent textInputComponent(&engine);
1708     textInputComponent.setData(componentStr.toLatin1(), QUrl());
1709     QSGTextInput *textInput = qobject_cast<QSGTextInput*>(textInputComponent.create());
1710     QVERIFY(textInput != 0);
1711
1712     QLineControl lc;
1713     bool cp = !lc.isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
1714     QCOMPARE(textInput->canPaste(), cp);
1715
1716 #endif
1717 }
1718
1719 void tst_qsgtextinput::passwordCharacter()
1720 {
1721     QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\"; font.family: \"Helvetica\"; echoMode: TextInput.Password }";
1722     QDeclarativeComponent textInputComponent(&engine);
1723     textInputComponent.setData(componentStr.toLatin1(), QUrl());
1724     QSGTextInput *textInput = qobject_cast<QSGTextInput*>(textInputComponent.create());
1725     QVERIFY(textInput != 0);
1726
1727     textInput->setPasswordCharacter("X");
1728     qreal implicitWidth = textInput->implicitWidth();
1729     textInput->setPasswordCharacter(".");
1730
1731     // QTBUG-12383 content is updated and redrawn
1732     QVERIFY(textInput->implicitWidth() < implicitWidth);
1733
1734     delete textInput;
1735 }
1736
1737 void tst_qsgtextinput::cursorDelegate()
1738 {
1739     QSGView view(QUrl::fromLocalFile(TESTDATA("cursorTest.qml")));
1740     view.show();
1741     view.requestActivateWindow();
1742     QSGTextInput *textInputObject = view.rootObject()->findChild<QSGTextInput*>("textInputObject");
1743     QVERIFY(textInputObject != 0);
1744     QVERIFY(textInputObject->findChild<QSGItem*>("cursorInstance"));
1745     //Test Delegate gets created
1746     textInputObject->setFocus(true);
1747     QSGItem* delegateObject = textInputObject->findChild<QSGItem*>("cursorInstance");
1748     QVERIFY(delegateObject);
1749     QCOMPARE(delegateObject->property("localProperty").toString(), QString("Hello"));
1750     //Test Delegate gets moved
1751     for (int i=0; i<= textInputObject->text().length(); i++) {
1752         textInputObject->setCursorPosition(i);
1753         QCOMPARE(textInputObject->cursorRectangle().x(), qRound(delegateObject->x()));
1754         QCOMPARE(textInputObject->cursorRectangle().y(), qRound(delegateObject->y()));
1755     }
1756     textInputObject->setCursorPosition(0);
1757     QCOMPARE(textInputObject->cursorRectangle().x(), qRound(delegateObject->x()));
1758     QCOMPARE(textInputObject->cursorRectangle().y(), qRound(delegateObject->y()));
1759     //Test Delegate gets deleted
1760     textInputObject->setCursorDelegate(0);
1761     QVERIFY(!textInputObject->findChild<QSGItem*>("cursorInstance"));
1762 }
1763
1764 void tst_qsgtextinput::cursorVisible()
1765 {
1766     QSGView view(QUrl::fromLocalFile(TESTDATA("cursorVisible.qml")));
1767     view.show();
1768     view.requestActivateWindow();
1769     QTest::qWaitForWindowShown(&view);
1770     QTRY_COMPARE(&view, qGuiApp->focusWindow());
1771
1772     QSGTextInput input;
1773     QSignalSpy spy(&input, SIGNAL(cursorVisibleChanged(bool)));
1774
1775     QCOMPARE(input.isCursorVisible(), false);
1776
1777     input.setCursorVisible(true);
1778     QCOMPARE(input.isCursorVisible(), true);
1779     QCOMPARE(spy.count(), 1);
1780
1781     input.setCursorVisible(false);
1782     QCOMPARE(input.isCursorVisible(), false);
1783     QCOMPARE(spy.count(), 2);
1784
1785     input.setFocus(true);
1786     QCOMPARE(input.isCursorVisible(), false);
1787     QCOMPARE(spy.count(), 2);
1788
1789     input.setParentItem(view.rootObject());
1790     QCOMPARE(input.isCursorVisible(), true);
1791     QCOMPARE(spy.count(), 3);
1792
1793     input.setFocus(false);
1794     QCOMPARE(input.isCursorVisible(), false);
1795     QCOMPARE(spy.count(), 4);
1796
1797     input.setFocus(true);
1798     QCOMPARE(input.isCursorVisible(), true);
1799     QCOMPARE(spy.count(), 5);
1800
1801     view.setWindowState(Qt::WindowNoState);
1802     QEXPECT_FAIL("", "Most likely a side-effect of QTBUG-21489", Abort);
1803     QCOMPARE(input.isCursorVisible(), false);
1804     QCOMPARE(spy.count(), 6);
1805
1806     view.requestActivateWindow();
1807     QCOMPARE(input.isCursorVisible(), true);
1808     QCOMPARE(spy.count(), 7);
1809
1810     // on mac, setActiveWindow(0) on mac does not deactivate the current application
1811     // (you have to switch to a different app or hide the current app to trigger this)
1812 #if !defined(Q_WS_MAC)
1813     // QGuiApplication has no equivalent of setActiveWindow(0).  Is this different to clearing the
1814     // active state of the window or can it be removed?
1815 //    QApplication::setActiveWindow(0);
1816 //    QTRY_COMPARE(QApplication::focusWindow(), static_cast<QWidget *>(0));
1817 //    QCOMPARE(input.isCursorVisible(), false);
1818 //    QCOMPARE(spy.count(), 8);
1819
1820 //    view.requestActivateWindow();
1821 //    QTRY_COMPARE(view.windowState(), Qt::WindowActive);
1822 //    QCOMPARE(input.isCursorVisible(), true);
1823 //    QCOMPARE(spy.count(), 9);
1824 #endif
1825 }
1826
1827 void tst_qsgtextinput::cursorRectangle()
1828 {
1829     QSKIP("QTBUG-21689");
1830
1831     QString text = "Hello World!";
1832
1833     QSGTextInput input;
1834     input.setText(text);
1835     QFontMetricsF fm(input.font());
1836     input.setWidth(fm.width(text.mid(0, 5)));
1837
1838     QRect r;
1839
1840     // some tolerance for different fonts.
1841 #ifdef Q_OS_LINUX
1842     const int error = 2;
1843 #else
1844     const int error = 5;
1845 #endif
1846
1847
1848     for (int i = 0; i <= 5; ++i) {
1849         input.setCursorPosition(i);
1850         r = input.cursorRectangle();
1851         int textWidth = fm.width(text.mid(0, i));
1852
1853         QVERIFY(r.left() < textWidth + error);
1854         QVERIFY(r.right() > textWidth - error);
1855         QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRect(), r);
1856     }
1857
1858     // Check the cursor rectangle remains within the input bounding rect when auto scrolling.
1859     QVERIFY(r.left() < input.boundingRect().width());
1860     QVERIFY(r.right() >= input.width() - error);
1861
1862     for (int i = 6; i < text.length(); ++i) {
1863         input.setCursorPosition(i);
1864         QCOMPARE(r, input.cursorRectangle());
1865         QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRect(), r);
1866     }
1867
1868     for (int i = text.length() - 2; i >= 0; --i) {
1869         input.setCursorPosition(i);
1870         r = input.cursorRectangle();
1871         QVERIFY(r.right() >= 0);
1872         QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRect(), r);
1873     }
1874
1875     input.setText("Hi!");
1876     input.setHAlign(QSGTextInput::AlignRight);
1877     r = input.cursorRectangle();
1878     QVERIFY(r.left() < input.boundingRect().width());
1879     QVERIFY(r.right() >= input.width() - error);
1880 }
1881
1882 void tst_qsgtextinput::readOnly()
1883 {
1884     QSGView canvas(QUrl::fromLocalFile(TESTDATA("readOnly.qml")));
1885     canvas.show();
1886     canvas.requestActivateWindow();
1887
1888     QVERIFY(canvas.rootObject() != 0);
1889
1890     QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1891
1892     QVERIFY(input != 0);
1893     QTRY_VERIFY(input->hasActiveFocus() == true);
1894     QVERIFY(input->isReadOnly() == true);
1895     QString initial = input->text();
1896     for (int k=Qt::Key_0; k<=Qt::Key_Z; k++)
1897         simulateKey(&canvas, k);
1898     simulateKey(&canvas, Qt::Key_Return);
1899     simulateKey(&canvas, Qt::Key_Space);
1900     simulateKey(&canvas, Qt::Key_Escape);
1901     QCOMPARE(input->text(), initial);
1902
1903     input->setCursorPosition(3);
1904     input->setReadOnly(false);
1905     QCOMPARE(input->isReadOnly(), false);
1906     QCOMPARE(input->cursorPosition(), input->text().length());
1907 }
1908
1909 void tst_qsgtextinput::echoMode()
1910 {
1911     QSGView canvas(QUrl::fromLocalFile(TESTDATA("echoMode.qml")));
1912     canvas.show();
1913     canvas.requestActivateWindow();
1914     QTest::qWaitForWindowShown(&canvas);
1915     QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1916
1917     QVERIFY(canvas.rootObject() != 0);
1918
1919     QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1920
1921     QVERIFY(input != 0);
1922     QTRY_VERIFY(input->hasActiveFocus() == true);
1923     QString initial = input->text();
1924     Qt::InputMethodHints ref;
1925     QCOMPARE(initial, QLatin1String("ABCDefgh"));
1926     QCOMPARE(input->echoMode(), QSGTextInput::Normal);
1927     QCOMPARE(input->displayText(), input->text());
1928     //Normal
1929     ref &= ~Qt::ImhHiddenText;
1930     ref &= ~(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
1931     QCOMPARE(input->inputMethodHints(), ref);
1932     input->setEchoMode(QSGTextInput::NoEcho);
1933     QCOMPARE(input->text(), initial);
1934     QCOMPARE(input->displayText(), QLatin1String(""));
1935     QCOMPARE(input->passwordCharacter(), QLatin1String("*"));
1936     //NoEcho
1937     ref |= Qt::ImhHiddenText;
1938     ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
1939     QCOMPARE(input->inputMethodHints(), ref);
1940     input->setEchoMode(QSGTextInput::Password);
1941     //Password
1942     ref |= Qt::ImhHiddenText;
1943     ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
1944     QCOMPARE(input->text(), initial);
1945     QCOMPARE(input->displayText(), QLatin1String("********"));
1946     QCOMPARE(input->inputMethodHints(), ref);
1947     input->setPasswordCharacter(QChar('Q'));
1948     QCOMPARE(input->passwordCharacter(), QLatin1String("Q"));
1949     QCOMPARE(input->text(), initial);
1950     QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
1951     input->setEchoMode(QSGTextInput::PasswordEchoOnEdit);
1952     //PasswordEchoOnEdit
1953     ref &= ~Qt::ImhHiddenText;
1954     ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
1955     QCOMPARE(input->inputMethodHints(), ref);
1956     QCOMPARE(input->text(), initial);
1957     QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
1958     QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("QQQQQQQQ"));
1959     QTest::keyPress(&canvas, Qt::Key_A);//Clearing previous entry is part of PasswordEchoOnEdit
1960     QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1961     QCOMPARE(input->text(), QLatin1String("a"));
1962     QCOMPARE(input->displayText(), QLatin1String("a"));
1963     QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("a"));
1964     input->setFocus(false);
1965     QVERIFY(input->hasActiveFocus() == false);
1966     QCOMPARE(input->displayText(), QLatin1String("Q"));
1967     QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("Q"));
1968     input->setFocus(true);
1969     QVERIFY(input->hasActiveFocus());
1970     QInputMethodEvent inputEvent;
1971     inputEvent.setCommitString(initial);
1972     QGuiApplication::sendEvent(input, &inputEvent);
1973     QCOMPARE(input->text(), initial);
1974     QCOMPARE(input->displayText(), initial);
1975     QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), initial);
1976 }
1977
1978 #ifdef QT_GUI_PASSWORD_ECHO_DELAY
1979 void tst_qdeclarativetextinput::passwordEchoDelay()
1980 {
1981     QSGView canvas(QUrl::fromLocalFile(TESTDATA("echoMode.qml")));
1982     canvas.show();
1983     canvas.setFocus();
1984     QGuiApplication::setActiveWindow(&canvas);
1985     QTest::qWaitForWindowShown(&canvas);
1986     QTRY_COMPARE(&canvas, qGuiApp->focusWindow());
1987
1988     QVERIFY(canvas.rootObject() != 0);
1989
1990     QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1991
1992     QChar fillChar = QLatin1Char('*');
1993
1994     input->setEchoMode(QDeclarativeTextInput::Password);
1995     QCOMPARE(input->displayText(), QString(8, fillChar));
1996     input->setText(QString());
1997     QCOMPARE(input->displayText(), QString());
1998
1999     QTest::keyPress(&canvas, '0');
2000     QTest::keyPress(&canvas, '1');
2001     QTest::keyPress(&canvas, '2');
2002     QCOMPARE(input->displayText(), QString(2, fillChar) + QLatin1Char('2'));
2003     QTest::keyPress(&canvas, '3');
2004     QTest::keyPress(&canvas, '4');
2005     QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
2006     QTest::keyPress(&canvas, Qt::Key_Backspace);
2007     QCOMPARE(input->displayText(), QString(4, fillChar));
2008     QTest::keyPress(&canvas, '4');
2009     QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
2010     QTest::qWait(QT_GUI_PASSWORD_ECHO_DELAY);
2011     QTRY_COMPARE(input->displayText(), QString(5, fillChar));
2012     QTest::keyPress(&canvas, '5');
2013     QCOMPARE(input->displayText(), QString(5, fillChar) + QLatin1Char('5'));
2014     input->setFocus(false);
2015     QVERIFY(!input->hasFocus());
2016     QCOMPARE(input->displayText(), QString(6, fillChar));
2017     input->setFocus(true);
2018     QTRY_VERIFY(input->hasFocus());
2019     QCOMPARE(input->displayText(), QString(6, fillChar));
2020     QTest::keyPress(&canvas, '6');
2021     QCOMPARE(input->displayText(), QString(6, fillChar) + QLatin1Char('6'));
2022
2023     QInputMethodEvent ev;
2024     ev.setCommitString(QLatin1String("7"));
2025     QGuiApplication::sendEvent(&canvas, &ev);
2026     QCOMPARE(input->displayText(), QString(7, fillChar) + QLatin1Char('7'));
2027 }
2028 #endif
2029
2030
2031 void tst_qsgtextinput::simulateKey(QSGView *view, int key)
2032 {
2033     QKeyEvent press(QKeyEvent::KeyPress, key, 0);
2034     QKeyEvent release(QKeyEvent::KeyRelease, key, 0);
2035
2036     QGuiApplication::sendEvent(view, &press);
2037     QGuiApplication::sendEvent(view, &release);
2038 }
2039
2040 #ifndef QTBUG_21691
2041 class MyInputContext : public QInputContext
2042 {
2043 public:
2044     MyInputContext() : updateReceived(false), eventType(QEvent::None) {}
2045     ~MyInputContext() {}
2046
2047     QString identifierName() { return QString(); }
2048     QString language() { return QString(); }
2049
2050     void reset() {}
2051
2052     bool isComposing() const { return false; }
2053
2054     void update() { updateReceived = true; }
2055
2056     void mouseHandler(int x, QMouseEvent *event)
2057     {
2058         cursor = x;
2059         eventType = event->type();
2060         eventPosition = event->pos();
2061         eventGlobalPosition = event->globalPos();
2062         eventButton = event->button();
2063         eventButtons = event->buttons();
2064         eventModifiers = event->modifiers();
2065     }
2066
2067     void sendPreeditText(const QString &text, int cursor)
2068     {
2069         QList<QInputMethodEvent::Attribute> attributes;
2070         attributes.append(QInputMethodEvent::Attribute(
2071                 QInputMethodEvent::Cursor, cursor, text.length(), QVariant()));
2072
2073         QInputMethodEvent event(text, attributes);
2074         sendEvent(event);
2075     }
2076
2077     bool updateReceived;
2078     int cursor;
2079     QEvent::Type eventType;
2080     QPoint eventPosition;
2081     QPoint eventGlobalPosition;
2082     Qt::MouseButton eventButton;
2083     Qt::MouseButtons eventButtons;
2084     Qt::KeyboardModifiers eventModifiers;
2085 };
2086 #endif
2087
2088 void tst_qsgtextinput::openInputPanel()
2089 {
2090     QSGView view(QUrl::fromLocalFile(TESTDATA("openInputPanel.qml")));
2091     view.show();
2092     view.requestActivateWindow();
2093     QTest::qWaitForWindowShown(&view);
2094     QTRY_COMPARE(&view, qGuiApp->focusWindow());
2095
2096     QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2097     QVERIFY(input);
2098
2099     // check default values
2100     QVERIFY(input->focusOnPress());
2101     QVERIFY(!input->hasActiveFocus());
2102     qDebug() << &input << qApp->inputPanel()->inputItem();
2103     QCOMPARE(qApp->inputPanel()->inputItem(), static_cast<QObject*>(0));
2104     QEXPECT_FAIL("", "QTBUG-21946", Abort);
2105     QCOMPARE(qApp->inputPanel()->visible(), false);
2106
2107     // input panel should open on focus
2108     QPoint centerPoint(view.width()/2, view.height()/2);
2109     Qt::KeyboardModifiers noModifiers = 0;
2110     QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
2111     QGuiApplication::processEvents();
2112     QVERIFY(input->hasActiveFocus());
2113     QCOMPARE(qApp->inputPanel()->inputItem(), input);
2114     QCOMPARE(qApp->inputPanel()->visible(), true);
2115     QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
2116
2117     // input panel should be re-opened when pressing already focused TextInput
2118     qApp->inputPanel()->hide();
2119     QCOMPARE(qApp->inputPanel()->visible(), false);
2120     QVERIFY(input->hasActiveFocus());
2121     QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
2122     QGuiApplication::processEvents();
2123     QCOMPARE(qApp->inputPanel()->visible(), true);
2124     QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
2125
2126     // input panel should stay visible if focus is lost to another text inputor
2127     QSignalSpy inputPanelVisibilitySpy(qApp->inputPanel(), SIGNAL(visibleChanged()));
2128     QSGTextInput anotherInput;
2129     anotherInput.setParentItem(view.rootObject());
2130     anotherInput.setFocus(true);
2131     QCOMPARE(qApp->inputPanel()->visible(), true);
2132     QCOMPARE(qApp->inputPanel()->inputItem(), qobject_cast<QObject*>(&anotherInput));
2133     QCOMPARE(inputPanelVisibilitySpy.count(), 0);
2134
2135     anotherInput.setFocus(false);
2136     QCOMPARE(qApp->inputPanel()->inputItem(), static_cast<QObject*>(0));
2137     QCOMPARE(view.activeFocusItem(), view.rootItem());
2138     anotherInput.setFocus(true);
2139
2140     // input item should be null if focus is lost to an item that doesn't accept inputs
2141     QSGItem item;
2142     item.setParentItem(view.rootObject());
2143     item.setFocus(true);
2144     QCOMPARE(qApp->inputPanel()->inputItem(), static_cast<QObject*>(0));
2145     QCOMPARE(view.activeFocusItem(), &item);
2146
2147     qApp->inputPanel()->hide();
2148
2149     // input panel should not be opened if TextInput is read only
2150     input->setReadOnly(true);
2151     input->setFocus(true);
2152     QCOMPARE(qApp->inputPanel()->visible(), false);
2153     QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
2154     QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
2155     QGuiApplication::processEvents();
2156     QCOMPARE(qApp->inputPanel()->visible(), false);
2157
2158     // input panel should not be opened if focusOnPress is set to false
2159     input->setFocusOnPress(false);
2160     input->setFocus(false);
2161     input->setFocus(true);
2162     QCOMPARE(qApp->inputPanel()->visible(), false);
2163     QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
2164     QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
2165     QCOMPARE(qApp->inputPanel()->visible(), false);
2166
2167     // input panel should open when openSoftwareInputPanel is called
2168     input->openSoftwareInputPanel();
2169     QCOMPARE(qApp->inputPanel()->visible(), true);
2170
2171     // input panel should close when closeSoftwareInputPanel is called
2172     input->closeSoftwareInputPanel();
2173     QCOMPARE(qApp->inputPanel()->visible(), false);
2174 }
2175
2176 class MyTextInput : public QSGTextInput
2177 {
2178 public:
2179     MyTextInput(QSGItem *parent = 0) : QSGTextInput(parent)
2180     {
2181         nbPaint = 0;
2182     }
2183     virtual QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data)
2184     {
2185        nbPaint++;
2186        return QSGTextInput::updatePaintNode(node, data);
2187     }
2188     int nbPaint;
2189 };
2190
2191 void tst_qsgtextinput::setHAlignClearCache()
2192 {
2193     QSGView view;
2194     MyTextInput input;
2195     input.setText("Hello world");
2196     input.setParentItem(view.rootItem());
2197     view.show();
2198     view.requestActivateWindow();
2199     QTest::qWaitForWindowShown(&view);
2200     QTRY_COMPARE(input.nbPaint, 1);
2201     input.setHAlign(QSGTextInput::AlignRight);
2202     //Changing the alignment should trigger a repaint
2203     QTRY_COMPARE(input.nbPaint, 2);
2204 }
2205
2206 void tst_qsgtextinput::focusOutClearSelection()
2207 {
2208     QSGView view;
2209     QSGTextInput input;
2210     QSGTextInput input2;
2211     input.setText(QLatin1String("Hello world"));
2212     input.setFocus(true);
2213     input2.setParentItem(view.rootItem());
2214     input.setParentItem(view.rootItem());
2215     view.show();
2216     view.requestActivateWindow();
2217     QTest::qWaitForWindowShown(&view);
2218     input.select(2,5);
2219     //The selection should work
2220     QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
2221     input2.setFocus(true);
2222     QGuiApplication::processEvents();
2223     //The input lost the focus selection should be cleared
2224     QTRY_COMPARE(input.selectedText(), QLatin1String(""));
2225 }
2226
2227 void tst_qsgtextinput::geometrySignals()
2228 {
2229     QDeclarativeComponent component(&engine, TESTDATA("geometrySignals.qml"));
2230     QObject *o = component.create();
2231     QVERIFY(o);
2232     QCOMPARE(o->property("bindingWidth").toInt(), 400);
2233     QCOMPARE(o->property("bindingHeight").toInt(), 500);
2234     delete o;
2235 }
2236
2237 void tst_qsgtextinput::testQtQuick11Attributes()
2238 {
2239     QFETCH(QString, code);
2240     QFETCH(QString, warning);
2241     QFETCH(QString, error);
2242
2243     QDeclarativeEngine engine;
2244     QObject *obj;
2245
2246     QDeclarativeComponent valid(&engine);
2247     valid.setData("import QtQuick 2.0; TextInput { " + code.toUtf8() + " }", QUrl(""));
2248     obj = valid.create();
2249     QVERIFY(obj);
2250     QVERIFY(valid.errorString().isEmpty());
2251     delete obj;
2252
2253     QDeclarativeComponent invalid(&engine);
2254     invalid.setData("import QtQuick 1.0; TextInput { " + code.toUtf8() + " }", QUrl(""));
2255     QTest::ignoreMessage(QtWarningMsg, warning.toUtf8());
2256     obj = invalid.create();
2257     QCOMPARE(invalid.errorString(), error);
2258     delete obj;
2259 }
2260
2261 void tst_qsgtextinput::testQtQuick11Attributes_data()
2262 {
2263     QTest::addColumn<QString>("code");
2264     QTest::addColumn<QString>("warning");
2265     QTest::addColumn<QString>("error");
2266
2267     QTest::newRow("canPaste") << "property bool foo: canPaste"
2268         << "<Unknown File>:1: ReferenceError: Can't find variable: canPaste"
2269         << "";
2270
2271     QTest::newRow("moveCursorSelection") << "Component.onCompleted: moveCursorSelection(0, TextEdit.SelectCharacters)"
2272         << "<Unknown File>:1: ReferenceError: Can't find variable: moveCursorSelection"
2273         << "";
2274
2275     QTest::newRow("deselect") << "Component.onCompleted: deselect()"
2276         << "<Unknown File>:1: ReferenceError: Can't find variable: deselect"
2277         << "";
2278 }
2279
2280 void tst_qsgtextinput::preeditAutoScroll()
2281 {
2282 #ifdef QTBUG_21691
2283     QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
2284     QVERIFY(false);
2285 #else
2286     QString preeditText = "califragisiticexpialidocious!";
2287
2288     QSGView view(QUrl::fromLocalFile(TESTDATA("preeditAutoScroll.qml")));
2289     MyInputContext ic;
2290     // QSGCanvas won't set the Qt::WA_InputMethodEnabled flag unless a suitable item has active focus
2291     // and QWidget won't allow an input context to be set when the flag is not set.
2292     view.setAttribute(Qt::WA_InputMethodEnabled, true);
2293     view.setInputContext(&ic);
2294     view.setAttribute(Qt::WA_InputMethodEnabled, false);
2295     view.show();
2296     view.requestActivateWindow();
2297     QTest::qWaitForWindowShown(&view);
2298     QTRY_COMPARE(&view, qGuiApp->focusWindow());
2299     QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2300     QVERIFY(input);
2301
2302     QSignalSpy cursorRectangleSpy(input, SIGNAL(cursorRectangleChanged()));
2303     int cursorRectangleChanges = 0;
2304
2305     QFontMetricsF fm(input->font());
2306     input->setWidth(fm.width(input->text()));
2307
2308     // test the text is scrolled so the preedit is visible.
2309     ic.sendPreeditText(preeditText.mid(0, 3), 1);
2310     QVERIFY(input->positionAt(0) != 0);
2311     QVERIFY(input->cursorRectangle().left() < input->boundingRect().width());
2312     QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2313
2314     // test the text is scrolled back when the preedit is removed.
2315     ic.sendEvent(QInputMethodEvent());
2316     QCOMPARE(input->positionAt(0), 0);
2317     QCOMPARE(input->positionAt(input->width()), 5);
2318     QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2319
2320     // some tolerance for different fonts.
2321 #ifdef Q_OS_LINUX
2322     const int error = 2;
2323 #else
2324     const int error = 5;
2325 #endif
2326
2327     // test if the preedit is larger than the text input that the
2328     // character preceding the cursor is still visible.
2329     qreal x = input->positionToRectangle(0).x();
2330     for (int i = 0; i < 3; ++i) {
2331         ic.sendPreeditText(preeditText, i + 1);
2332         QVERIFY(input->cursorRectangle().right() >= fm.width(preeditText.at(i)) - error);
2333         QVERIFY(input->positionToRectangle(0).x() < x);
2334         QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2335         x = input->positionToRectangle(0).x();
2336     }
2337     for (int i = 1; i >= 0; --i) {
2338         ic.sendPreeditText(preeditText, i + 1);
2339         QVERIFY(input->cursorRectangle().right() >= fm.width(preeditText.at(i)) - error);
2340         QVERIFY(input->positionToRectangle(0).x() > x);
2341         QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2342         x = input->positionToRectangle(0).x();
2343     }
2344
2345     // Test incrementing the preedit cursor doesn't cause further
2346     // scrolling when right most text is visible.
2347     ic.sendPreeditText(preeditText, preeditText.length() - 3);
2348     QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2349     x = input->positionToRectangle(0).x();
2350     for (int i = 2; i >= 0; --i) {
2351         ic.sendPreeditText(preeditText, preeditText.length() - i);
2352         QCOMPARE(input->positionToRectangle(0).x(), x);
2353         QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2354     }
2355     for (int i = 1; i <  3; ++i) {
2356         ic.sendPreeditText(preeditText, preeditText.length() - i);
2357         QCOMPARE(input->positionToRectangle(0).x(), x);
2358         QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2359     }
2360
2361     // Test disabling auto scroll.
2362     ic.sendEvent(QInputMethodEvent());
2363
2364     input->setAutoScroll(false);
2365     ic.sendPreeditText(preeditText.mid(0, 3), 1);
2366     QCOMPARE(input->positionAt(0), 0);
2367     QCOMPARE(input->positionAt(input->width()), 5);
2368 #endif
2369 }
2370
2371 void tst_qsgtextinput::preeditMicroFocus()
2372 {
2373 #ifdef QTBUG_21691
2374     QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
2375     QVERIFY(false);
2376 #else
2377     QString preeditText = "super";
2378
2379     QSGView view(QUrl::fromLocalFile(TESTDATA("inputMethodEvent.qml")));
2380     MyInputContext ic;
2381     // QSGCanvas won't set the Qt::WA_InputMethodEnabled flag unless a suitable item has active focus
2382     // and QWidget won't allow an input context to be set when the flag is not set.
2383     view.setAttribute(Qt::WA_InputMethodEnabled, true);
2384     view.setInputContext(&ic);
2385     view.setAttribute(Qt::WA_InputMethodEnabled, false);
2386     view.show();
2387     view.requestActivateWindow();
2388     QTest::qWaitForWindowShown(&view);
2389     QTRY_COMPARE(&view, qGuiApp->focusWindow());
2390     QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2391     QVERIFY(input);
2392
2393     QRect currentRect;
2394     QRect previousRect = input->inputMethodQuery(Qt::ImCursorRectangle).toRect();
2395
2396     // Verify that the micro focus rect is positioned the same for position 0 as
2397     // it would be if there was no preedit text.
2398     ic.updateReceived = false;
2399     ic.sendPreeditText(preeditText, 0);
2400     currentRect = input->inputMethodQuery(Qt::ImCursorRectangle).toRect();
2401     QCOMPARE(currentRect, previousRect);
2402 #if defined(Q_WS_X11) || defined(Q_WS_QWS)
2403     QCOMPARE(ic.updateReceived, true);
2404 #endif
2405
2406     // Verify that the micro focus rect moves to the left as the cursor position
2407     // is incremented.
2408     for (int i = 1; i <= 5; ++i) {
2409         ic.updateReceived = false;
2410         ic.sendPreeditText(preeditText, i);
2411         currentRect = input->inputMethodQuery(Qt::ImCursorRectangle).toRect();
2412         QVERIFY(previousRect.left() < currentRect.left());
2413 #if defined(Q_WS_X11) || defined(Q_WS_QWS)
2414         QCOMPARE(ic.updateReceived, true);
2415 #endif
2416         previousRect = currentRect;
2417     }
2418
2419     // Verify that if there is no preedit cursor then the micro focus rect is the
2420     // same as it would be if it were positioned at the end of the preedit text.
2421     ic.sendPreeditText(preeditText, 0);
2422     ic.updateReceived = false;
2423     ic.sendEvent(QInputMethodEvent(preeditText, QList<QInputMethodEvent::Attribute>()));
2424     currentRect = input->inputMethodQuery(Qt::ImCursorRectangle).toRect();
2425     QCOMPARE(currentRect, previousRect);
2426 #if defined(Q_WS_X11) || defined(Q_WS_QWS)
2427     QCOMPARE(ic.updateReceived, true);
2428 #endif
2429 #endif
2430 }
2431
2432 void tst_qsgtextinput::inputContextMouseHandler()
2433 {
2434 #ifdef QTBUG_21691
2435     QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
2436     QVERIFY(false);
2437 #else
2438     QString text = "supercalifragisiticexpialidocious!";
2439
2440     QSGView view(QUrl::fromLocalFile(TESTDATA("inputContext.qml")));
2441     MyInputContext ic;
2442     // QSGCanvas won't set the Qt::WA_InputMethodEnabled flag unless a suitable item has active focus
2443     // and QWidget won't allow an input context to be set when the flag is not set.
2444     view.setAttribute(Qt::WA_InputMethodEnabled, true);
2445     view.setInputContext(&ic);
2446     view.setAttribute(Qt::WA_InputMethodEnabled, false);
2447     view.show();
2448     view.requestActivateWindow();
2449     QTest::qWaitForWindowShown(&view);
2450     QTRY_COMPARE(&view, qGuiApp->focusWindow());
2451     QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2452     QVERIFY(input);
2453
2454     QFontMetricsF fm(input->font());
2455     const qreal y = fm.height() / 2;
2456
2457     QPoint position2 = input->mapToScene(QPointF(fm.width(text.mid(0, 2)), y)).toPoint();
2458     QPoint position8 = input->mapToScene(QPointF(fm.width(text.mid(0, 8)), y)).toPoint();
2459     QPoint position20 = input->mapToScene(QPointF(fm.width(text.mid(0, 20)), y)).toPoint();
2460     QPoint position27 = input->mapToScene(QPointF(fm.width(text.mid(0, 27)), y)).toPoint();
2461     QPoint globalPosition2 = view.mapToGlobal(position2);
2462     QPoint globalposition8 = view.mapToGlobal(position8);
2463     QPoint globalposition20 = view.mapToGlobal(position20);
2464     QPoint globalposition27 = view.mapToGlobal(position27);
2465
2466     ic.sendEvent(QInputMethodEvent(text.mid(12), QList<QInputMethodEvent::Attribute>()));
2467
2468     QTest::mouseDClick(&view, Qt::LeftButton, Qt::NoModifier, position2);
2469     QCOMPARE(ic.eventType, QEvent::MouseButtonDblClick);
2470     QCOMPARE(ic.eventPosition, position2);
2471     QCOMPARE(ic.eventGlobalPosition, globalPosition2);
2472     QCOMPARE(ic.eventButton, Qt::LeftButton);
2473     QCOMPARE(ic.eventModifiers, Qt::NoModifier);
2474     QVERIFY(ic.cursor < 0);
2475     ic.eventType = QEvent::None;
2476
2477     QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, position2);
2478     QCOMPARE(ic.eventType, QEvent::MouseButtonPress);
2479     QCOMPARE(ic.eventPosition, position2);
2480     QCOMPARE(ic.eventGlobalPosition, globalPosition2);
2481     QCOMPARE(ic.eventButton, Qt::LeftButton);
2482     QCOMPARE(ic.eventModifiers, Qt::NoModifier);
2483     QVERIFY(ic.cursor < 0);
2484     ic.eventType = QEvent::None;
2485
2486     {   QMouseEvent mv(QEvent::MouseMove, position8, globalposition8, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
2487         QGuiApplication::sendEvent(&view, &mv); }
2488     QCOMPARE(ic.eventType, QEvent::None);
2489
2490     {   QMouseEvent mv(QEvent::MouseMove, position27, globalposition27, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
2491         QGuiApplication::sendEvent(&view, &mv); }
2492     QCOMPARE(ic.eventType, QEvent::MouseMove);
2493     QCOMPARE(ic.eventPosition, position27);
2494     QCOMPARE(ic.eventGlobalPosition, globalposition27);
2495     QCOMPARE(ic.eventButton, Qt::LeftButton);
2496     QCOMPARE(ic.eventModifiers, Qt::NoModifier);
2497     QVERIFY(ic.cursor >= 14 && ic.cursor <= 16);    // 15 is expected but some platforms may be off by one.
2498     ic.eventType = QEvent::None;
2499
2500     QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, position27);
2501     QCOMPARE(ic.eventType, QEvent::MouseButtonRelease);
2502     QCOMPARE(ic.eventPosition, position27);
2503     QCOMPARE(ic.eventGlobalPosition, globalposition27);
2504     QCOMPARE(ic.eventButton, Qt::LeftButton);
2505     QCOMPARE(ic.eventModifiers, Qt::NoModifier);
2506     QVERIFY(ic.cursor >= 14 && ic.cursor <= 16);
2507     ic.eventType = QEvent::None;
2508
2509     // And in the other direction.
2510     QTest::mouseDClick(&view, Qt::LeftButton, Qt::ControlModifier, position27);
2511     QCOMPARE(ic.eventType, QEvent::MouseButtonDblClick);
2512     QCOMPARE(ic.eventPosition, position27);
2513     QCOMPARE(ic.eventGlobalPosition, globalposition27);
2514     QCOMPARE(ic.eventButton, Qt::LeftButton);
2515     QCOMPARE(ic.eventModifiers, Qt::ControlModifier);
2516     QVERIFY(ic.cursor >= 14 && ic.cursor <= 16);
2517     ic.eventType = QEvent::None;
2518
2519     QTest::mousePress(&view, Qt::RightButton, Qt::ControlModifier, position27);
2520     QCOMPARE(ic.eventType, QEvent::MouseButtonPress);
2521     QCOMPARE(ic.eventPosition, position27);
2522     QCOMPARE(ic.eventGlobalPosition, globalposition27);
2523     QCOMPARE(ic.eventButton, Qt::RightButton);
2524     QCOMPARE(ic.eventModifiers, Qt::ControlModifier);
2525     QVERIFY(ic.cursor >= 14 && ic.cursor <= 16);
2526     ic.eventType = QEvent::None;
2527
2528     {   QMouseEvent mv(QEvent::MouseMove, position20, globalposition20, Qt::RightButton, Qt::RightButton,Qt::ControlModifier);
2529         QGuiApplication::sendEvent(&view, &mv); }
2530     QCOMPARE(ic.eventType, QEvent::MouseMove);
2531     QCOMPARE(ic.eventPosition, position20);
2532     QCOMPARE(ic.eventGlobalPosition, globalposition20);
2533     QCOMPARE(ic.eventButton, Qt::RightButton);
2534     QCOMPARE(ic.eventModifiers, Qt::ControlModifier);
2535     QVERIFY(ic.cursor >= 7 && ic.cursor <= 9);
2536     ic.eventType = QEvent::None;
2537
2538     {   QMouseEvent mv(QEvent::MouseMove, position2, globalPosition2, Qt::RightButton, Qt::RightButton,Qt::ControlModifier);
2539         QGuiApplication::sendEvent(&view, &mv); }
2540     QCOMPARE(ic.eventType, QEvent::None);
2541
2542     QTest::mouseRelease(&view, Qt::RightButton, Qt::ControlModifier, position2);
2543     QCOMPARE(ic.eventType, QEvent::MouseButtonRelease);
2544     QCOMPARE(ic.eventPosition, position2);
2545     QCOMPARE(ic.eventGlobalPosition, globalPosition2);
2546     QCOMPARE(ic.eventButton, Qt::RightButton);
2547     QCOMPARE(ic.eventModifiers, Qt::ControlModifier);
2548     QVERIFY(ic.cursor < 0);
2549     ic.eventType = QEvent::None;
2550 #endif
2551 }
2552
2553 void tst_qsgtextinput::inputMethodComposing()
2554 {
2555     QString text = "supercalifragisiticexpialidocious!";
2556
2557     QSGView view(QUrl::fromLocalFile(TESTDATA("inputContext.qml")));
2558     view.show();
2559     view.requestActivateWindow();
2560     QTest::qWaitForWindowShown(&view);
2561     QTRY_COMPARE(&view, qGuiApp->focusWindow());
2562     QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2563     QVERIFY(input);
2564     QSignalSpy spy(input, SIGNAL(inputMethodComposingChanged()));
2565
2566     QCOMPARE(input->isInputMethodComposing(), false);
2567     {
2568         QInputMethodEvent event(text.mid(3), QList<QInputMethodEvent::Attribute>());
2569         QGuiApplication::sendEvent(input, &event);
2570     }
2571     QCOMPARE(input->isInputMethodComposing(), true);
2572     QCOMPARE(spy.count(), 1);
2573
2574     {
2575         QInputMethodEvent event(text.mid(12), QList<QInputMethodEvent::Attribute>());
2576         QGuiApplication::sendEvent(input, &event);
2577     }
2578     QCOMPARE(spy.count(), 1);
2579
2580     {
2581         QInputMethodEvent event;
2582         QGuiApplication::sendEvent(input, &event);
2583     }
2584     QCOMPARE(input->isInputMethodComposing(), false);
2585     QCOMPARE(spy.count(), 2);
2586 }
2587
2588 void tst_qsgtextinput::cursorRectangleSize()
2589 {
2590     QSGView *canvas = new QSGView(QUrl::fromLocalFile(TESTDATA("positionAt.qml")));
2591     QVERIFY(canvas->rootObject() != 0);
2592     canvas->show();
2593     canvas->requestActivateWindow();
2594     QTest::qWaitForWindowShown(canvas);
2595
2596     QSGTextInput *textInput = qobject_cast<QSGTextInput *>(canvas->rootObject());
2597     QVERIFY(textInput != 0);
2598     textInput->setFocus(Qt::OtherFocusReason);
2599     QRectF cursorRect = textInput->positionToRectangle(textInput->cursorPosition());
2600     QRectF microFocusFromScene = canvas->inputMethodQuery(Qt::ImCursorRectangle).toRectF();
2601     QInputMethodQueryEvent event(Qt::ImCursorRectangle);
2602     qApp->sendEvent(qApp->inputPanel()->inputItem(), &event);
2603
2604     QRectF microFocusFromApp = event.value(Qt::ImCursorRectangle).toRectF();
2605
2606     QCOMPARE(microFocusFromScene.size(), cursorRect.size());
2607     QCOMPARE(microFocusFromApp.size(), cursorRect.size());
2608
2609     delete canvas;
2610 }
2611
2612 void tst_qsgtextinput::tripleClickSelectsAll()
2613 {
2614     QString qmlfile = TESTDATA("positionAt.qml");
2615     QSGView view(QUrl::fromLocalFile(qmlfile));
2616     view.show();
2617     view.requestActivateWindow();
2618     QTest::qWaitForWindowShown(&view);
2619
2620     QTRY_COMPARE(&view, qGuiApp->focusWindow());
2621
2622     QSGTextInput* input = qobject_cast<QSGTextInput*>(view.rootObject());
2623     QVERIFY(input);
2624
2625     QLatin1String hello("Hello world!");
2626     input->setSelectByMouse(true);
2627     input->setText(hello);
2628
2629     // Clicking on the same point inside TextInput three times in a row
2630     // should trigger a triple click, thus selecting all the text.
2631     QPoint pointInside = input->pos().toPoint() + QPoint(2,2);
2632     QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
2633     QTest::mouseClick(&view, Qt::LeftButton, 0, pointInside);
2634     QGuiApplication::processEvents();
2635     QCOMPARE(input->selectedText(), hello);
2636
2637     // Now it simulates user moving the mouse between the second and the third click.
2638     // In this situation, we don't expect a triple click.
2639     QPoint pointInsideButFar = QPoint(input->width(),input->height()) - QPoint(2,2);
2640     QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
2641     QTest::mouseClick(&view, Qt::LeftButton, 0, pointInsideButFar);
2642     QGuiApplication::processEvents();
2643     QVERIFY(input->selectedText().isEmpty());
2644
2645     // And now we press the third click too late, so no triple click event is triggered.
2646     QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
2647     QGuiApplication::processEvents();
2648     QTest::qWait(QApplication::doubleClickInterval() + 1);
2649     QTest::mouseClick(&view, Qt::LeftButton, 0, pointInside);
2650     QGuiApplication::processEvents();
2651     QVERIFY(input->selectedText().isEmpty());
2652 }
2653
2654 void tst_qsgtextinput::QTBUG_19956_data()
2655 {
2656     QTest::addColumn<QString>("url");
2657     QTest::newRow("intvalidator") << "qtbug-19956int.qml";
2658     QTest::newRow("doublevalidator") << "qtbug-19956double.qml";
2659 }
2660
2661 void tst_qsgtextinput::QTBUG_19956()
2662 {
2663     QFETCH(QString, url);
2664
2665     QSGView canvas(QUrl::fromLocalFile(TESTDATA(url)));
2666     canvas.show();
2667     canvas.requestActivateWindow();
2668     QTest::qWaitForWindowShown(&canvas);
2669     QVERIFY(canvas.rootObject() != 0);
2670     QSGTextInput *input = qobject_cast<QSGTextInput*>(canvas.rootObject());
2671     QVERIFY(input);
2672     input->setFocus(true);
2673     QVERIFY(input->hasActiveFocus());
2674
2675     QCOMPARE(canvas.rootObject()->property("topvalue").toInt(), 30);
2676     QCOMPARE(canvas.rootObject()->property("bottomvalue").toInt(), 10);
2677     QCOMPARE(canvas.rootObject()->property("text").toString(), QString("20"));
2678     QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
2679
2680     canvas.rootObject()->setProperty("topvalue", 15);
2681     QCOMPARE(canvas.rootObject()->property("topvalue").toInt(), 15);
2682     QVERIFY(!canvas.rootObject()->property("acceptableInput").toBool());
2683
2684     canvas.rootObject()->setProperty("topvalue", 25);
2685     QCOMPARE(canvas.rootObject()->property("topvalue").toInt(), 25);
2686     QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
2687
2688     canvas.rootObject()->setProperty("bottomvalue", 21);
2689     QCOMPARE(canvas.rootObject()->property("bottomvalue").toInt(), 21);
2690     QVERIFY(!canvas.rootObject()->property("acceptableInput").toBool());
2691
2692     canvas.rootObject()->setProperty("bottomvalue", 10);
2693     QCOMPARE(canvas.rootObject()->property("bottomvalue").toInt(), 10);
2694     QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
2695 }
2696
2697 void tst_qsgtextinput::QTBUG_19956_regexp()
2698 {
2699     QSGView canvas(QUrl::fromLocalFile(TESTDATA("qtbug-19956regexp.qml")));
2700     canvas.show();
2701     canvas.requestActivateWindow();
2702     QTest::qWaitForWindowShown(&canvas);
2703     QVERIFY(canvas.rootObject() != 0);
2704     QSGTextInput *input = qobject_cast<QSGTextInput*>(canvas.rootObject());
2705     QVERIFY(input);
2706     input->setFocus(true);
2707     QVERIFY(input->hasActiveFocus());
2708
2709     canvas.rootObject()->setProperty("regexvalue", QRegExp("abc"));
2710     QCOMPARE(canvas.rootObject()->property("regexvalue").toRegExp(), QRegExp("abc"));
2711     QCOMPARE(canvas.rootObject()->property("text").toString(), QString("abc"));
2712     QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
2713
2714     canvas.rootObject()->setProperty("regexvalue", QRegExp("abcd"));
2715     QCOMPARE(canvas.rootObject()->property("regexvalue").toRegExp(), QRegExp("abcd"));
2716     QVERIFY(!canvas.rootObject()->property("acceptableInput").toBool());
2717
2718     canvas.rootObject()->setProperty("regexvalue", QRegExp("abc"));
2719     QCOMPARE(canvas.rootObject()->property("regexvalue").toRegExp(), QRegExp("abc"));
2720     QVERIFY(canvas.rootObject()->property("acceptableInput").toBool());
2721 }
2722
2723 QTEST_MAIN(tst_qsgtextinput)
2724
2725 #include "tst_qsgtextinput.moc"