1 /****************************************************************************
3 ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4 ** All rights reserved.
5 ** Contact: Nokia Corporation (qt-info@nokia.com)
7 ** This file is part of the test suite of the Qt Toolkit.
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.
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.
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.
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.
40 ****************************************************************************/
42 #include <QtTest/QSignalSpy>
43 #include "../../../shared/util.h"
44 #include <QtDeclarative/qdeclarativeengine.h>
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>
54 #include <QInputContext>
55 #include <private/qsgdistancefieldglyphcache_p.h>
56 #include <QtOpenGL/QGLShaderProgram>
59 #include "qplatformdefs.h"
62 // In Symbian OS test data is located in applications private dir
66 Q_DECLARE_METATYPE(QSGTextInput::SelectionMode)
67 DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
70 #define QTBUG_21691_MESSAGE "QTBUG-21691: The test needs to be rewritten to not use QInputContext"
72 #define QTBUG_21489_MESSAGE "Pre-condition failure because of QTBUG-21489. This can be safely ignored if there no subsequent failures"
74 QString createExpectedFileIfNotFound(const QString& filebasename, const QImage& actual)
76 // XXX This will be replaced by some clever persistent platform image store.
77 QString persistent_dir = SRCDIR "/data";
78 QString arch = "unknown-architecture"; // QTest needs to help with this.
80 QString expectfile = persistent_dir + QDir::separator() + filebasename + "-" + arch + ".png";
82 if (!QFile::exists(expectfile)) {
83 actual.save(expectfile);
84 qWarning() << "created" << expectfile;
90 class tst_qsgtextinput : public QObject
99 void cleanupTestCase();
105 void isRightToLeft_data();
106 void isRightToLeft();
107 void moveCursorSelection_data();
108 void moveCursorSelection();
109 void moveCursorSelectionSequence_data();
110 void moveCursorSelectionSequence();
111 void dragMouseSelection();
112 void mouseSelectionMode_data();
113 void mouseSelectionMode();
114 void tripleClickSelectsAll();
116 void horizontalAlignment_data();
117 void horizontalAlignment();
118 void horizontalAlignment_RightToLeft();
127 void passwordCharacter();
128 void cursorDelegate();
129 void cursorVisible();
130 void cursorRectangle();
132 void navigation_RTL();
134 void canPasteEmpty();
138 void openInputPanel();
139 void setHAlignClearCache();
140 void focusOutClearSelection();
143 #ifdef QT_GUI_PASSWORD_ECHO_DELAY
144 void passwordEchoDelay();
146 void geometrySignals();
147 void testQtQuick11Attributes();
148 void testQtQuick11Attributes_data();
150 void preeditAutoScroll();
151 void preeditMicroFocus();
152 void inputContextMouseHandler();
153 void inputMethodComposing();
154 void cursorRectangleSize();
157 void simulateKey(QSGView *, int key);
159 QDeclarativeEngine engine;
160 QStringList standard;
161 QStringList colorStrings;
163 void tst_qsgtextinput::initTestCase()
167 void tst_qsgtextinput::cleanupTestCase()
171 tst_qsgtextinput::tst_qsgtextinput()
173 standard << "the quick brown fox jumped over the lazy dog"
174 << "It's supercalifragisiticexpialidocious!"
179 colorStrings << "aliceblue"
193 void tst_qsgtextinput::text()
196 QDeclarativeComponent textinputComponent(&engine);
197 textinputComponent.setData("import QtQuick 2.0\nTextInput { text: \"\" }", QUrl());
198 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
200 QVERIFY(textinputObject != 0);
201 QCOMPARE(textinputObject->text(), QString(""));
203 delete textinputObject;
206 for (int i = 0; i < standard.size(); i++)
208 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
209 QDeclarativeComponent textinputComponent(&engine);
210 textinputComponent.setData(componentStr.toLatin1(), QUrl());
211 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
213 QVERIFY(textinputObject != 0);
214 QCOMPARE(textinputObject->text(), standard.at(i));
216 delete textinputObject;
221 void tst_qsgtextinput::width()
223 // uses Font metrics to find the width for standard
225 QDeclarativeComponent textinputComponent(&engine);
226 textinputComponent.setData("import QtQuick 2.0\nTextInput { text: \"\" }", QUrl());
227 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
229 QVERIFY(textinputObject != 0);
230 QCOMPARE(textinputObject->width(), 0.0);
232 delete textinputObject;
235 bool requiresUnhintedMetrics = !qmlDisableDistanceField();
237 for (int i = 0; i < standard.size(); i++)
240 qreal metricWidth = 0.0;
241 if (requiresUnhintedMetrics) {
242 QString s = standard.at(i);
243 s.replace(QLatin1Char('\n'), QChar::LineSeparator);
245 QTextLayout layout(s);
246 layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
249 option.setUseDesignMetrics(true);
250 layout.setTextOption(option);
253 layout.beginLayout();
255 QTextLine line = layout.createLine();
262 metricWidth = ceil(layout.boundingRect().width());
265 metricWidth = fm.width(standard.at(i));
268 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
269 QDeclarativeComponent textinputComponent(&engine);
270 textinputComponent.setData(componentStr.toLatin1(), QUrl());
271 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
273 QVERIFY(textinputObject != 0);
274 int delta = abs(int(int(textinputObject->width()) - metricWidth));
275 QEXPECT_FAIL("", "QTBUG-21689", Abort);
276 QVERIFY(delta <= 3.0); // As best as we can hope for cross-platform.
278 delete textinputObject;
282 void tst_qsgtextinput::font()
284 //test size, then bold, then italic, then family
286 QString componentStr = "import QtQuick 2.0\nTextInput { font.pointSize: 40; text: \"Hello World\" }";
287 QDeclarativeComponent textinputComponent(&engine);
288 textinputComponent.setData(componentStr.toLatin1(), QUrl());
289 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
291 QVERIFY(textinputObject != 0);
292 QCOMPARE(textinputObject->font().pointSize(), 40);
293 QCOMPARE(textinputObject->font().bold(), false);
294 QCOMPARE(textinputObject->font().italic(), false);
296 delete textinputObject;
300 QString componentStr = "import QtQuick 2.0\nTextInput { font.bold: true; text: \"Hello World\" }";
301 QDeclarativeComponent textinputComponent(&engine);
302 textinputComponent.setData(componentStr.toLatin1(), QUrl());
303 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
305 QVERIFY(textinputObject != 0);
306 QCOMPARE(textinputObject->font().bold(), true);
307 QCOMPARE(textinputObject->font().italic(), false);
309 delete textinputObject;
313 QString componentStr = "import QtQuick 2.0\nTextInput { font.italic: true; text: \"Hello World\" }";
314 QDeclarativeComponent textinputComponent(&engine);
315 textinputComponent.setData(componentStr.toLatin1(), QUrl());
316 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
318 QVERIFY(textinputObject != 0);
319 QCOMPARE(textinputObject->font().italic(), true);
320 QCOMPARE(textinputObject->font().bold(), false);
322 delete textinputObject;
326 QString componentStr = "import QtQuick 2.0\nTextInput { font.family: \"Helvetica\"; text: \"Hello World\" }";
327 QDeclarativeComponent textinputComponent(&engine);
328 textinputComponent.setData(componentStr.toLatin1(), QUrl());
329 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
331 QVERIFY(textinputObject != 0);
332 QCOMPARE(textinputObject->font().family(), QString("Helvetica"));
333 QCOMPARE(textinputObject->font().bold(), false);
334 QCOMPARE(textinputObject->font().italic(), false);
336 delete textinputObject;
340 QString componentStr = "import QtQuick 2.0\nTextInput { font.family: \"\"; text: \"Hello World\" }";
341 QDeclarativeComponent textinputComponent(&engine);
342 textinputComponent.setData(componentStr.toLatin1(), QUrl());
343 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
345 QVERIFY(textinputObject != 0);
346 QCOMPARE(textinputObject->font().family(), QString(""));
348 delete textinputObject;
352 void tst_qsgtextinput::color()
355 for (int i = 0; i < colorStrings.size(); i++)
357 QString componentStr = "import QtQuick 2.0\nTextInput { color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
358 QDeclarativeComponent textinputComponent(&engine);
359 textinputComponent.setData(componentStr.toLatin1(), QUrl());
360 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
361 QVERIFY(textinputObject != 0);
362 QCOMPARE(textinputObject->color(), QColor(colorStrings.at(i)));
364 delete textinputObject;
367 //test selection color
368 for (int i = 0; i < colorStrings.size(); i++)
370 QString componentStr = "import QtQuick 2.0\nTextInput { selectionColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
371 QDeclarativeComponent textinputComponent(&engine);
372 textinputComponent.setData(componentStr.toLatin1(), QUrl());
373 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
374 QVERIFY(textinputObject != 0);
375 QCOMPARE(textinputObject->selectionColor(), QColor(colorStrings.at(i)));
377 delete textinputObject;
380 //test selected text color
381 for (int i = 0; i < colorStrings.size(); i++)
383 QString componentStr = "import QtQuick 2.0\nTextInput { selectedTextColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
384 QDeclarativeComponent textinputComponent(&engine);
385 textinputComponent.setData(componentStr.toLatin1(), QUrl());
386 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
387 QVERIFY(textinputObject != 0);
388 QCOMPARE(textinputObject->selectedTextColor(), QColor(colorStrings.at(i)));
390 delete textinputObject;
394 QString colorStr = "#AA001234";
395 QColor testColor("#001234");
396 testColor.setAlpha(170);
398 QString componentStr = "import QtQuick 2.0\nTextInput { color: \"" + colorStr + "\"; text: \"Hello World\" }";
399 QDeclarativeComponent textinputComponent(&engine);
400 textinputComponent.setData(componentStr.toLatin1(), QUrl());
401 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
403 QVERIFY(textinputObject != 0);
404 QCOMPARE(textinputObject->color(), testColor);
406 delete textinputObject;
410 void tst_qsgtextinput::selection()
412 QString testStr = standard[0];
413 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
414 QDeclarativeComponent textinputComponent(&engine);
415 textinputComponent.setData(componentStr.toLatin1(), QUrl());
416 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
417 QVERIFY(textinputObject != 0);
420 //Test selection follows cursor
421 for(int i=0; i<= testStr.size(); i++) {
422 textinputObject->setCursorPosition(i);
423 QCOMPARE(textinputObject->cursorPosition(), i);
424 QCOMPARE(textinputObject->selectionStart(), i);
425 QCOMPARE(textinputObject->selectionEnd(), i);
426 QVERIFY(textinputObject->selectedText().isNull());
429 textinputObject->setCursorPosition(0);
430 QVERIFY(textinputObject->cursorPosition() == 0);
431 QVERIFY(textinputObject->selectionStart() == 0);
432 QVERIFY(textinputObject->selectionEnd() == 0);
433 QVERIFY(textinputObject->selectedText().isNull());
435 // Verify invalid positions are ignored.
436 textinputObject->setCursorPosition(-1);
437 QVERIFY(textinputObject->cursorPosition() == 0);
438 QVERIFY(textinputObject->selectionStart() == 0);
439 QVERIFY(textinputObject->selectionEnd() == 0);
440 QVERIFY(textinputObject->selectedText().isNull());
442 textinputObject->setCursorPosition(textinputObject->text().count()+1);
443 QVERIFY(textinputObject->cursorPosition() == 0);
444 QVERIFY(textinputObject->selectionStart() == 0);
445 QVERIFY(textinputObject->selectionEnd() == 0);
446 QVERIFY(textinputObject->selectedText().isNull());
449 for(int i=0; i<= testStr.size(); i++) {
450 textinputObject->select(0,i);
451 QCOMPARE(testStr.mid(0,i), textinputObject->selectedText());
453 for(int i=0; i<= testStr.size(); i++) {
454 textinputObject->select(i,testStr.size());
455 QCOMPARE(testStr.mid(i,testStr.size()-i), textinputObject->selectedText());
458 textinputObject->setCursorPosition(0);
459 QVERIFY(textinputObject->cursorPosition() == 0);
460 QVERIFY(textinputObject->selectionStart() == 0);
461 QVERIFY(textinputObject->selectionEnd() == 0);
462 QVERIFY(textinputObject->selectedText().isNull());
464 //Test Error Ignoring behaviour
465 textinputObject->setCursorPosition(0);
466 QVERIFY(textinputObject->selectedText().isNull());
467 textinputObject->select(-10,0);
468 QVERIFY(textinputObject->selectedText().isNull());
469 textinputObject->select(100,110);
470 QVERIFY(textinputObject->selectedText().isNull());
471 textinputObject->select(0,-10);
472 QVERIFY(textinputObject->selectedText().isNull());
473 textinputObject->select(0,100);
474 QVERIFY(textinputObject->selectedText().isNull());
475 textinputObject->select(0,10);
476 QVERIFY(textinputObject->selectedText().size() == 10);
477 textinputObject->select(-10,10);
478 QVERIFY(textinputObject->selectedText().size() == 10);
479 textinputObject->select(100,101);
480 QVERIFY(textinputObject->selectedText().size() == 10);
481 textinputObject->select(0,-10);
482 QVERIFY(textinputObject->selectedText().size() == 10);
483 textinputObject->select(0,100);
484 QVERIFY(textinputObject->selectedText().size() == 10);
486 textinputObject->deselect();
487 QVERIFY(textinputObject->selectedText().isNull());
488 textinputObject->select(0,10);
489 QVERIFY(textinputObject->selectedText().size() == 10);
490 textinputObject->deselect();
491 QVERIFY(textinputObject->selectedText().isNull());
493 delete textinputObject;
496 void tst_qsgtextinput::isRightToLeft_data()
498 QTest::addColumn<QString>("text");
499 QTest::addColumn<bool>("emptyString");
500 QTest::addColumn<bool>("firstCharacter");
501 QTest::addColumn<bool>("lastCharacter");
502 QTest::addColumn<bool>("middleCharacter");
503 QTest::addColumn<bool>("startString");
504 QTest::addColumn<bool>("midString");
505 QTest::addColumn<bool>("endString");
507 const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
508 QTest::newRow("Empty") << "" << false << false << false << false << false << false << false;
509 QTest::newRow("Neutral") << "23244242" << false << false << false << false << false << false << false;
510 QTest::newRow("LTR") << "Hello world" << false << false << false << false << false << false << false;
511 QTest::newRow("RTL") << QString::fromUtf16(arabic_str, 11) << false << true << true << true << true << true << true;
512 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;
513 QTest::newRow("Bidi LTR + RTL + LTR") << QString("Hello world") + QString::fromUtf16(arabic_str, 11) + QString("Hello world") << false << false << false << true << false << false << false;
516 void tst_qsgtextinput::isRightToLeft()
518 QFETCH(QString, text);
519 QFETCH(bool, emptyString);
520 QFETCH(bool, firstCharacter);
521 QFETCH(bool, lastCharacter);
522 QFETCH(bool, middleCharacter);
523 QFETCH(bool, startString);
524 QFETCH(bool, midString);
525 QFETCH(bool, endString);
527 QSGTextInput textInput;
528 textInput.setText(text);
530 // first test that the right string is delivered to the QString::isRightToLeft()
531 QCOMPARE(textInput.isRightToLeft(0,0), text.mid(0,0).isRightToLeft());
532 QCOMPARE(textInput.isRightToLeft(0,1), text.mid(0,1).isRightToLeft());
533 QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), text.mid(text.count()-2, text.count()-1).isRightToLeft());
534 QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), text.mid(text.count()/2, text.count()/2 + 1).isRightToLeft());
535 QCOMPARE(textInput.isRightToLeft(0,text.count()/4), text.mid(0,text.count()/4).isRightToLeft());
536 QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), text.mid(text.count()/4,3*text.count()/4).isRightToLeft());
538 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
539 QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), text.mid(3*text.count()/4,text.count()-1).isRightToLeft());
541 // then test that the feature actually works
542 QCOMPARE(textInput.isRightToLeft(0,0), emptyString);
543 QCOMPARE(textInput.isRightToLeft(0,1), firstCharacter);
544 QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), lastCharacter);
545 QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), middleCharacter);
546 QCOMPARE(textInput.isRightToLeft(0,text.count()/4), startString);
547 QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), midString);
549 QTest::ignoreMessage(QtWarningMsg, "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
550 QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), endString);
553 void tst_qsgtextinput::moveCursorSelection_data()
555 QTest::addColumn<QString>("testStr");
556 QTest::addColumn<int>("cursorPosition");
557 QTest::addColumn<int>("movePosition");
558 QTest::addColumn<QSGTextInput::SelectionMode>("mode");
559 QTest::addColumn<int>("selectionStart");
560 QTest::addColumn<int>("selectionEnd");
561 QTest::addColumn<bool>("reversible");
563 // () contains the text selected by the cursor.
564 // <> contains the actual selection.
566 QTest::newRow("(t)he|characters")
567 << standard[0] << 0 << 1 << QSGTextInput::SelectCharacters << 0 << 1 << true;
568 QTest::newRow("do(g)|characters")
569 << standard[0] << 43 << 44 << QSGTextInput::SelectCharacters << 43 << 44 << true;
570 QTest::newRow("jum(p)ed|characters")
571 << standard[0] << 23 << 24 << QSGTextInput::SelectCharacters << 23 << 24 << true;
572 QTest::newRow("jumped( )over|characters")
573 << standard[0] << 26 << 27 << QSGTextInput::SelectCharacters << 26 << 27 << true;
574 QTest::newRow("(the )|characters")
575 << standard[0] << 0 << 4 << QSGTextInput::SelectCharacters << 0 << 4 << true;
576 QTest::newRow("( dog)|characters")
577 << standard[0] << 40 << 44 << QSGTextInput::SelectCharacters << 40 << 44 << true;
578 QTest::newRow("( jumped )|characters")
579 << standard[0] << 19 << 27 << QSGTextInput::SelectCharacters << 19 << 27 << true;
580 QTest::newRow("th(e qu)ick|characters")
581 << standard[0] << 2 << 6 << QSGTextInput::SelectCharacters << 2 << 6 << true;
582 QTest::newRow("la(zy d)og|characters")
583 << standard[0] << 38 << 42 << QSGTextInput::SelectCharacters << 38 << 42 << true;
584 QTest::newRow("jum(ped ov)er|characters")
585 << standard[0] << 23 << 29 << QSGTextInput::SelectCharacters << 23 << 29 << true;
586 QTest::newRow("()the|characters")
587 << standard[0] << 0 << 0 << QSGTextInput::SelectCharacters << 0 << 0 << true;
588 QTest::newRow("dog()|characters")
589 << standard[0] << 44 << 44 << QSGTextInput::SelectCharacters << 44 << 44 << true;
590 QTest::newRow("jum()ped|characters")
591 << standard[0] << 23 << 23 << QSGTextInput::SelectCharacters << 23 << 23 << true;
593 QTest::newRow("<(t)he>|words")
594 << standard[0] << 0 << 1 << QSGTextInput::SelectWords << 0 << 3 << true;
595 QTest::newRow("<do(g)>|words")
596 << standard[0] << 43 << 44 << QSGTextInput::SelectWords << 41 << 44 << true;
597 QTest::newRow("<jum(p)ed>|words")
598 << standard[0] << 23 << 24 << QSGTextInput::SelectWords << 20 << 26 << true;
599 QTest::newRow("<jumped( )>over|words,ltr")
600 << standard[0] << 26 << 27 << QSGTextInput::SelectWords << 20 << 27 << false;
601 QTest::newRow("jumped<( )over>|words,rtl")
602 << standard[0] << 27 << 26 << QSGTextInput::SelectWords << 26 << 31 << false;
603 QTest::newRow("<(the )>quick|words,ltr")
604 << standard[0] << 0 << 4 << QSGTextInput::SelectWords << 0 << 4 << false;
605 QTest::newRow("<(the )quick>|words,rtl")
606 << standard[0] << 4 << 0 << QSGTextInput::SelectWords << 0 << 9 << false;
607 QTest::newRow("<lazy( dog)>|words,ltr")
608 << standard[0] << 40 << 44 << QSGTextInput::SelectWords << 36 << 44 << false;
609 QTest::newRow("lazy<( dog)>|words,rtl")
610 << standard[0] << 44 << 40 << QSGTextInput::SelectWords << 40 << 44 << false;
611 QTest::newRow("<fox( jumped )>over|words,ltr")
612 << standard[0] << 19 << 27 << QSGTextInput::SelectWords << 16 << 27 << false;
613 QTest::newRow("fox<( jumped )over>|words,rtl")
614 << standard[0] << 27 << 19 << QSGTextInput::SelectWords << 19 << 31 << false;
615 QTest::newRow("<th(e qu)ick>|words")
616 << standard[0] << 2 << 6 << QSGTextInput::SelectWords << 0 << 9 << true;
617 QTest::newRow("<la(zy d)og|words>")
618 << standard[0] << 38 << 42 << QSGTextInput::SelectWords << 36 << 44 << true;
619 QTest::newRow("<jum(ped ov)er>|words")
620 << standard[0] << 23 << 29 << QSGTextInput::SelectWords << 20 << 31 << true;
621 QTest::newRow("<()>the|words")
622 << standard[0] << 0 << 0 << QSGTextInput::SelectWords << 0 << 0 << true;
623 QTest::newRow("dog<()>|words")
624 << standard[0] << 44 << 44 << QSGTextInput::SelectWords << 44 << 44 << true;
625 QTest::newRow("jum<()>ped|words")
626 << standard[0] << 23 << 23 << QSGTextInput::SelectWords << 23 << 23 << true;
628 QTest::newRow("Hello<(,)> |words")
629 << standard[2] << 5 << 6 << QSGTextInput::SelectWords << 5 << 6 << true;
630 QTest::newRow("Hello<(, )>world|words,ltr")
631 << standard[2] << 5 << 7 << QSGTextInput::SelectWords << 5 << 7 << false;
632 QTest::newRow("Hello<(, )world>|words,rtl")
633 << standard[2] << 7 << 5 << QSGTextInput::SelectWords << 5 << 12 << false;
634 QTest::newRow("<Hel(lo, )>world|words,ltr")
635 << standard[2] << 3 << 7 << QSGTextInput::SelectWords << 0 << 7 << false;
636 QTest::newRow("<Hel(lo, )world>|words,rtl")
637 << standard[2] << 7 << 3 << QSGTextInput::SelectWords << 0 << 12 << false;
638 QTest::newRow("<Hel(lo)>,|words")
639 << standard[2] << 3 << 5 << QSGTextInput::SelectWords << 0 << 5 << true;
640 QTest::newRow("Hello<()>,|words")
641 << standard[2] << 5 << 5 << QSGTextInput::SelectWords << 5 << 5 << true;
642 QTest::newRow("Hello,<()>|words")
643 << standard[2] << 6 << 6 << QSGTextInput::SelectWords << 6 << 6 << true;
644 QTest::newRow("Hello<,( )>world|words,ltr")
645 << standard[2] << 6 << 7 << QSGTextInput::SelectWords << 5 << 7 << false;
646 QTest::newRow("Hello,<( )world>|words,rtl")
647 << standard[2] << 7 << 6 << QSGTextInput::SelectWords << 6 << 12 << false;
648 QTest::newRow("Hello<,( world)>|words,ltr")
649 << standard[2] << 6 << 12 << QSGTextInput::SelectWords << 5 << 12 << false;
650 QTest::newRow("Hello,<( world)>|words,rtl")
651 << standard[2] << 12 << 6 << QSGTextInput::SelectWords << 6 << 12 << false;
652 QTest::newRow("Hello<,( world!)>|words,ltr")
653 << standard[2] << 6 << 13 << QSGTextInput::SelectWords << 5 << 13 << false;
654 QTest::newRow("Hello,<( world!)>|words,rtl")
655 << standard[2] << 13 << 6 << QSGTextInput::SelectWords << 6 << 13 << false;
656 QTest::newRow("Hello<(, world!)>|words")
657 << standard[2] << 5 << 13 << QSGTextInput::SelectWords << 5 << 13 << true;
658 // Fails due to an issue with QTextBoundaryFinder and punctuation at the end of strings.
660 // QTest::newRow("world<(!)>|words")
661 // << standard[2] << 12 << 13 << QSGTextInput::SelectWords << 12 << 13 << true;
662 QTest::newRow("world!<()>)|words")
663 << standard[2] << 13 << 13 << QSGTextInput::SelectWords << 13 << 13 << true;
664 QTest::newRow("world<()>!)|words")
665 << standard[2] << 12 << 12 << QSGTextInput::SelectWords << 12 << 12 << true;
667 QTest::newRow("<(,)>olleH |words")
668 << standard[3] << 7 << 8 << QSGTextInput::SelectWords << 7 << 8 << true;
669 QTest::newRow("<dlrow( ,)>olleH|words,ltr")
670 << standard[3] << 6 << 8 << QSGTextInput::SelectWords << 1 << 8 << false;
671 QTest::newRow("dlrow<( ,)>olleH|words,rtl")
672 << standard[3] << 8 << 6 << QSGTextInput::SelectWords << 6 << 8 << false;
673 QTest::newRow("<dlrow( ,ol)leH>|words,ltr")
674 << standard[3] << 6 << 10 << QSGTextInput::SelectWords << 1 << 13 << false;
675 QTest::newRow("dlrow<( ,ol)leH>|words,rtl")
676 << standard[3] << 10 << 6 << QSGTextInput::SelectWords << 6 << 13 << false;
677 QTest::newRow(",<(ol)leH>,|words")
678 << standard[3] << 8 << 10 << QSGTextInput::SelectWords << 8 << 13 << true;
679 QTest::newRow(",<()>olleH|words")
680 << standard[3] << 8 << 8 << QSGTextInput::SelectWords << 8 << 8 << true;
681 QTest::newRow("<()>,olleH|words")
682 << standard[3] << 7 << 7 << QSGTextInput::SelectWords << 7 << 7 << true;
683 QTest::newRow("<dlrow( )>,olleH|words,ltr")
684 << standard[3] << 6 << 7 << QSGTextInput::SelectWords << 1 << 7 << false;
685 QTest::newRow("dlrow<( ),>olleH|words,rtl")
686 << standard[3] << 7 << 6 << QSGTextInput::SelectWords << 6 << 8 << false;
687 QTest::newRow("<(dlrow )>,olleH|words,ltr")
688 << standard[3] << 1 << 7 << QSGTextInput::SelectWords << 1 << 7 << false;
689 QTest::newRow("<(dlrow ),>olleH|words,rtl")
690 << standard[3] << 7 << 1 << QSGTextInput::SelectWords << 1 << 8 << false;
691 QTest::newRow("<(!dlrow )>,olleH|words,ltr")
692 << standard[3] << 0 << 7 << QSGTextInput::SelectWords << 0 << 7 << false;
693 QTest::newRow("<(!dlrow ),>olleH|words,rtl")
694 << standard[3] << 7 << 0 << QSGTextInput::SelectWords << 0 << 8 << false;
695 QTest::newRow("(!dlrow ,)olleH|words")
696 << standard[3] << 0 << 8 << QSGTextInput::SelectWords << 0 << 8 << true;
697 QTest::newRow("<(!)>dlrow|words")
698 << standard[3] << 0 << 1 << QSGTextInput::SelectWords << 0 << 1 << true;
699 QTest::newRow("<()>!dlrow|words")
700 << standard[3] << 0 << 0 << QSGTextInput::SelectWords << 0 << 0 << true;
701 QTest::newRow("!<()>dlrow|words")
702 << standard[3] << 1 << 1 << QSGTextInput::SelectWords << 1 << 1 << true;
704 QTest::newRow(" <s(pac)ey> text |words")
705 << standard[4] << 1 << 4 << QSGTextInput::SelectWords << 1 << 7 << true;
706 QTest::newRow(" spacey <t(ex)t> |words")
707 << standard[4] << 11 << 13 << QSGTextInput::SelectWords << 10 << 14 << false; // Should be reversible. QTBUG-11365
708 QTest::newRow("<( )>spacey text |words|ltr")
709 << standard[4] << 0 << 1 << QSGTextInput::SelectWords << 0 << 1 << false;
710 QTest::newRow("<( )spacey> text |words|rtl")
711 << standard[4] << 1 << 0 << QSGTextInput::SelectWords << 0 << 7 << false;
712 QTest::newRow("spacey <text( )>|words|ltr")
713 << standard[4] << 14 << 15 << QSGTextInput::SelectWords << 10 << 15 << false;
715 // QTest::newRow("spacey text<( )>|words|rtl")
716 // << standard[4] << 15 << 14 << QSGTextInput::SelectWords << 14 << 15 << false;
717 QTest::newRow("<()> spacey text |words")
718 << standard[4] << 0 << 0 << QSGTextInput::SelectWords << 0 << 0 << false;
719 QTest::newRow(" spacey text <()>|words")
720 << standard[4] << 15 << 15 << QSGTextInput::SelectWords << 15 << 15 << false;
723 void tst_qsgtextinput::moveCursorSelection()
725 QFETCH(QString, testStr);
726 QFETCH(int, cursorPosition);
727 QFETCH(int, movePosition);
728 QFETCH(QSGTextInput::SelectionMode, mode);
729 QFETCH(int, selectionStart);
730 QFETCH(int, selectionEnd);
731 QFETCH(bool, reversible);
733 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
734 QDeclarativeComponent textinputComponent(&engine);
735 textinputComponent.setData(componentStr.toLatin1(), QUrl());
736 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
737 QVERIFY(textinputObject != 0);
739 textinputObject->setCursorPosition(cursorPosition);
740 textinputObject->moveCursorSelection(movePosition, mode);
742 QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
743 QCOMPARE(textinputObject->selectionStart(), selectionStart);
744 QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
747 textinputObject->setCursorPosition(movePosition);
748 textinputObject->moveCursorSelection(cursorPosition, mode);
750 QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
751 QCOMPARE(textinputObject->selectionStart(), selectionStart);
752 QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
755 delete textinputObject;
758 void tst_qsgtextinput::moveCursorSelectionSequence_data()
760 QTest::addColumn<QString>("testStr");
761 QTest::addColumn<int>("cursorPosition");
762 QTest::addColumn<int>("movePosition1");
763 QTest::addColumn<int>("movePosition2");
764 QTest::addColumn<int>("selection1Start");
765 QTest::addColumn<int>("selection1End");
766 QTest::addColumn<int>("selection2Start");
767 QTest::addColumn<int>("selection2End");
769 // () contains the text selected by the cursor.
770 // <> contains the actual selection.
771 // ^ is the revised cursor position.
772 // {} contains the revised selection.
774 QTest::newRow("the {<quick( bro)wn> f^ox} jumped|ltr")
779 QTest::newRow("the quick<( {bro)wn> f^ox} jumped|rtl")
784 QTest::newRow("the {<quick( bro)wn> ^}fox jumped|ltr")
789 QTest::newRow("the quick<( {bro)wn> ^}fox jumped|rtl")
794 QTest::newRow("the {<quick( bro)wn^>} fox jumped|ltr")
799 QTest::newRow("the quick<( {bro)wn^>} f^ox jumped|rtl")
804 QTest::newRow("the {<quick() ^}bro)wn> fox|ltr")
809 QTest::newRow("the quick<( {^bro)wn>} fox|rtl")
814 QTest::newRow("the {<quick^}( bro)wn> fox|ltr")
819 QTest::newRow("the quick{<(^ bro)wn>} fox|rtl")
824 QTest::newRow("the {<qui^ck}( bro)wn> fox|ltr")
829 QTest::newRow("the {<qui^ck}( bro)wn> fox|rtl")
834 QTest::newRow("the {<^quick}( bro)wn> fox|ltr")
839 QTest::newRow("the {<^quick}( bro)wn> fox|rtl")
844 QTest::newRow("the{^ <quick}( bro)wn> fox|ltr")
849 QTest::newRow("the{^ <quick}( bro)wn> fox|rtl")
854 QTest::newRow("{t^he <quick}( bro)wn> fox|ltr")
859 QTest::newRow("{t^he <quick}( bro)wn> fox|rtl")
865 QTest::newRow("{<He(ll)o>, w^orld}!|ltr")
870 QTest::newRow("{<He(ll)o>, w^orld}!|rtl")
876 QTest::newRow("!{dlro^w ,<o(ll)eH>}|ltr")
881 QTest::newRow("!{dlro^w ,<o(ll)eH>}|rtl")
887 QTest::newRow("{<(^} sp)acey> text |ltr")
892 QTest::newRow("{<( ^}sp)acey> text |ltr")
897 QTest::newRow("<( {s^p)acey>} text |rtl")
902 QTest::newRow("<( {^sp)acey>} text |rtl")
908 QTest::newRow(" spacey <te(xt {^)>}|rtl")
914 // QTest::newRow(" spacey <te(xt{^ )>}|rtl")
919 QTest::newRow(" spacey {<te(x^t} )>|ltr")
925 // QTest::newRow(" spacey {<te(xt^} )>|ltr")
932 void tst_qsgtextinput::moveCursorSelectionSequence()
934 QFETCH(QString, testStr);
935 QFETCH(int, cursorPosition);
936 QFETCH(int, movePosition1);
937 QFETCH(int, movePosition2);
938 QFETCH(int, selection1Start);
939 QFETCH(int, selection1End);
940 QFETCH(int, selection2Start);
941 QFETCH(int, selection2End);
943 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
944 QDeclarativeComponent textinputComponent(&engine);
945 textinputComponent.setData(componentStr.toLatin1(), QUrl());
946 QSGTextInput *textinputObject = qobject_cast<QSGTextInput*>(textinputComponent.create());
947 QVERIFY(textinputObject != 0);
949 textinputObject->setCursorPosition(cursorPosition);
951 textinputObject->moveCursorSelection(movePosition1, QSGTextInput::SelectWords);
952 QCOMPARE(textinputObject->selectedText(), testStr.mid(selection1Start, selection1End - selection1Start));
953 QCOMPARE(textinputObject->selectionStart(), selection1Start);
954 QCOMPARE(textinputObject->selectionEnd(), selection1End);
956 textinputObject->moveCursorSelection(movePosition2, QSGTextInput::SelectWords);
957 QCOMPARE(textinputObject->selectedText(), testStr.mid(selection2Start, selection2End - selection2Start));
958 QCOMPARE(textinputObject->selectionStart(), selection2Start);
959 QCOMPARE(textinputObject->selectionEnd(), selection2End);
961 delete textinputObject;
964 void tst_qsgtextinput::dragMouseSelection()
966 QString qmlfile = SRCDIR "/data/mouseselection_true.qml";
968 QSGView canvas(QUrl::fromLocalFile(qmlfile));
971 canvas.requestActivateWindow();
972 QTest::qWaitForWindowShown(&canvas);
974 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
975 QTRY_COMPARE(canvas.windowState(), Qt::WindowActive);
977 QVERIFY(canvas.rootObject() != 0);
978 QSGTextInput *textInputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
979 QVERIFY(textInputObject != 0);
981 // press-and-drag-and-release from x1 to x2
984 int y = textInputObject->height()/2;
985 QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
986 QTest::mouseMove(&canvas, QPoint(x2, y));
987 QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
990 QVERIFY((str1 = textInputObject->selectedText()).length() > 3);
991 QVERIFY(str1.length() > 3);
993 // press and drag the current selection.
996 QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
997 QTest::mouseMove(&canvas, QPoint(x2, y));
998 QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
1000 QString str2 = textInputObject->selectedText();
1001 QVERIFY(str2.length() > 3);
1003 QVERIFY(str1 != str2);
1006 void tst_qsgtextinput::mouseSelectionMode_data()
1008 QTest::addColumn<QString>("qmlfile");
1009 QTest::addColumn<bool>("selectWords");
1012 QTest::newRow("SelectWords") << SRCDIR "/data/mouseselectionmode_words.qml" << true;
1013 QTest::newRow("SelectCharacters") << SRCDIR "/data/mouseselectionmode_characters.qml" << false;
1014 QTest::newRow("default") << SRCDIR "/data/mouseselectionmode_default.qml" << false;
1017 void tst_qsgtextinput::mouseSelectionMode()
1019 QFETCH(QString, qmlfile);
1020 QFETCH(bool, selectWords);
1022 QString text = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1024 QSGView canvas(QUrl::fromLocalFile(qmlfile));
1027 canvas.requestActivateWindow();
1028 QTest::qWaitForWindowShown(&canvas);
1029 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
1030 QTRY_COMPARE(canvas.windowState(), Qt::WindowActive);
1032 QVERIFY(canvas.rootObject() != 0);
1033 QSGTextInput *textInputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
1034 QVERIFY(textInputObject != 0);
1036 // press-and-drag-and-release from x1 to x2
1039 int y = textInputObject->height()/2;
1040 QTest::mousePress(&canvas, Qt::LeftButton, 0, QPoint(x1,y));
1041 QTest::mouseMove(&canvas, QPoint(x2,y)); // doesn't work
1042 QTest::mouseRelease(&canvas, Qt::LeftButton, 0, QPoint(x2,y));
1045 QTRY_COMPARE(textInputObject->selectedText(), text);
1047 QTRY_VERIFY(textInputObject->selectedText().length() > 3);
1048 QVERIFY(textInputObject->selectedText() != text);
1052 void tst_qsgtextinput::horizontalAlignment_data()
1054 QTest::addColumn<int>("hAlign");
1055 QTest::addColumn<QString>("expectfile");
1057 QTest::newRow("L") << int(Qt::AlignLeft) << "halign_left";
1058 QTest::newRow("R") << int(Qt::AlignRight) << "halign_right";
1059 QTest::newRow("C") << int(Qt::AlignHCenter) << "halign_center";
1062 void tst_qsgtextinput::horizontalAlignment()
1064 QFETCH(int, hAlign);
1065 QFETCH(QString, expectfile);
1067 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/horizontalAlignment.qml"));
1070 canvas.requestActivateWindow();
1071 QTest::qWaitForWindowShown(&canvas);
1072 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
1073 QTRY_COMPARE(canvas.windowState(), Qt::WindowActive);
1074 QObject *ob = canvas.rootObject();
1076 ob->setProperty("horizontalAlignment",hAlign);
1077 QImage actual = canvas.grabFrameBuffer();
1079 expectfile = createExpectedFileIfNotFound(expectfile, actual);
1081 QImage expect(expectfile);
1083 QCOMPARE(actual,expect);
1086 void tst_qsgtextinput::horizontalAlignment_RightToLeft()
1088 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/horizontalAlignment_RightToLeft.qml"));
1089 QSGTextInput *textInput = canvas.rootObject()->findChild<QSGTextInput*>("text");
1090 QVERIFY(textInput != 0);
1093 const QString rtlText = textInput->text();
1095 QSGTextInputPrivate *textInputPrivate = QSGTextInputPrivate::get(textInput);
1096 QVERIFY(textInputPrivate != 0);
1097 QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1099 // implicit alignment should follow the reading direction of RTL text
1100 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1101 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1102 QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1104 // explicitly left aligned
1105 textInput->setHAlign(QSGTextInput::AlignLeft);
1106 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignLeft);
1107 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1108 QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1110 // explicitly right aligned
1111 textInput->setHAlign(QSGTextInput::AlignRight);
1112 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1113 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1114 QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1116 // explicitly center aligned
1117 textInput->setHAlign(QSGTextInput::AlignHCenter);
1118 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1119 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignHCenter);
1120 QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1121 QVERIFY(-textInputPrivate->hscroll + textInputPrivate->width > canvas.width()/2);
1123 // reseted alignment should go back to following the text reading direction
1124 textInput->resetHAlign();
1125 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1126 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1127 QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1129 // mirror the text item
1130 QSGItemPrivate::get(textInput)->setLayoutMirror(true);
1132 // mirrored implicit alignment should continue to follow the reading direction of the text
1133 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1134 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1135 QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1137 // explicitly right aligned behaves as left aligned
1138 textInput->setHAlign(QSGTextInput::AlignRight);
1139 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1140 QCOMPARE(textInput->effectiveHAlign(), QSGTextInput::AlignLeft);
1141 QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1143 // mirrored explicitly left aligned behaves as right aligned
1144 textInput->setHAlign(QSGTextInput::AlignLeft);
1145 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignLeft);
1146 QCOMPARE(textInput->effectiveHAlign(), QSGTextInput::AlignRight);
1147 QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1149 // disable mirroring
1150 QSGItemPrivate::get(textInput)->setLayoutMirror(false);
1151 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1152 textInput->resetHAlign();
1154 // English text should be implicitly left aligned
1155 textInput->setText("Hello world!");
1156 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignLeft);
1157 QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1159 canvas.requestActivateWindow();
1160 QTest::qWaitForWindowShown(&canvas);
1161 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
1162 QTRY_COMPARE(canvas.windowState(), Qt::WindowActive);
1164 // If there is no commited text, the preedit text should determine the alignment.
1165 textInput->setText(QString());
1166 { QInputMethodEvent ev(rtlText, QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(&canvas, &ev); }
1167 QEXPECT_FAIL("", "QTBUG-21691", Continue);
1168 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1169 { QInputMethodEvent ev("Hello world!", QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(&canvas, &ev); }
1170 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignLeft);
1172 #ifndef Q_OS_MAC // QTBUG-18040
1173 // empty text with implicit alignment follows the system locale-based
1174 // keyboard input direction from QGuiApplication::keyboardInputDirection
1175 textInput->setText("");
1176 QCOMPARE(textInput->hAlign(), QGuiApplication::keyboardInputDirection() == Qt::LeftToRight ?
1177 QSGTextInput::AlignLeft : QSGTextInput::AlignRight);
1178 if (QGuiApplication::keyboardInputDirection() == Qt::LeftToRight)
1179 QVERIFY(-textInputPrivate->hscroll < canvas.width()/2);
1181 QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1182 textInput->setHAlign(QSGTextInput::AlignRight);
1183 QCOMPARE(textInput->hAlign(), QSGTextInput::AlignRight);
1184 QVERIFY(-textInputPrivate->hscroll > canvas.width()/2);
1187 #ifndef Q_OS_MAC // QTBUG-18040
1188 // alignment of TextInput with no text set to it
1189 QString componentStr = "import QtQuick 2.0\nTextInput {}";
1190 QDeclarativeComponent textComponent(&engine);
1191 textComponent.setData(componentStr.toLatin1(), QUrl::fromLocalFile(""));
1192 QSGTextInput *textObject = qobject_cast<QSGTextInput*>(textComponent.create());
1193 QCOMPARE(textObject->hAlign(), QGuiApplication::keyboardInputDirection() == Qt::LeftToRight ?
1194 QSGTextInput::AlignLeft : QSGTextInput::AlignRight);
1199 void tst_qsgtextinput::positionAt()
1201 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/positionAt.qml"));
1202 QVERIFY(canvas.rootObject() != 0);
1204 canvas.requestActivateWindow();
1205 QTest::qWaitForWindowShown(&canvas);
1207 QSGTextInput *textinputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
1208 QVERIFY(textinputObject != 0);
1210 // Check autoscrolled...
1211 QFontMetrics fm(textinputObject->font());
1213 int pos = textinputObject->positionAt(textinputObject->width()/2);
1215 int textLeftWidth = 0;
1216 if (!qmlDisableDistanceField()) {
1218 QTextLayout layout(textinputObject->text().left(pos));
1222 option.setUseDesignMetrics(true);
1223 layout.setTextOption(option);
1226 layout.beginLayout();
1227 QTextLine line = layout.createLine();
1230 textLeftWidth = ceil(line.horizontalAdvance());
1233 QTextLayout layout(textinputObject->text());
1237 option.setUseDesignMetrics(true);
1238 layout.setTextOption(option);
1241 layout.beginLayout();
1242 QTextLine line = layout.createLine();
1245 textWidth = ceil(line.horizontalAdvance());
1248 textWidth = fm.width(textinputObject->text());
1249 textLeftWidth = fm.width(textinputObject->text().left(pos));
1252 int diff = abs(textWidth - (textLeftWidth+textinputObject->width()/2));
1254 // some tollerance for different fonts.
1255 QEXPECT_FAIL("", "QTBUG-21690", Abort);
1262 int x = textinputObject->positionToRectangle(pos + 1).x() - 1;
1263 QCOMPARE(textinputObject->positionAt(x, QSGTextInput::CursorBetweenCharacters), pos + 1);
1264 QCOMPARE(textinputObject->positionAt(x, QSGTextInput::CursorOnCharacter), pos);
1266 // Check without autoscroll...
1267 textinputObject->setAutoScroll(false);
1268 pos = textinputObject->positionAt(textinputObject->width()/2);
1270 if (!qmlDisableDistanceField()) {
1272 QTextLayout layout(textinputObject->text().left(pos));
1276 option.setUseDesignMetrics(true);
1277 layout.setTextOption(option);
1280 layout.beginLayout();
1281 QTextLine line = layout.createLine();
1284 textLeftWidth = ceil(line.horizontalAdvance());
1287 textLeftWidth = fm.width(textinputObject->text().left(pos));
1290 diff = abs(int(textLeftWidth-textinputObject->width()/2));
1292 // some tollerance for different fonts.
1299 x = textinputObject->positionToRectangle(pos + 1).x() - 1;
1300 QCOMPARE(textinputObject->positionAt(x, QSGTextInput::CursorBetweenCharacters), pos + 1);
1301 QCOMPARE(textinputObject->positionAt(x, QSGTextInput::CursorOnCharacter), pos);
1303 const qreal x0 = textinputObject->positionToRectangle(pos).x();
1304 const qreal x1 = textinputObject->positionToRectangle(pos + 1).x();
1306 QString preeditText = textinputObject->text().mid(0, pos);
1307 textinputObject->setText(textinputObject->text().mid(pos));
1308 textinputObject->setCursorPosition(0);
1310 QInputMethodEvent inputEvent(preeditText, QList<QInputMethodEvent::Attribute>());
1311 QGuiApplication::sendEvent(&canvas, &inputEvent);
1313 // Check all points within the preedit text return the same position.
1314 QCOMPARE(textinputObject->positionAt(0), 0);
1315 QCOMPARE(textinputObject->positionAt(x0 / 2), 0);
1316 QCOMPARE(textinputObject->positionAt(x0), 0);
1318 // Verify positioning returns to normal after the preedit text.
1319 QCOMPARE(textinputObject->positionAt(x1), 1);
1320 QCOMPARE(textinputObject->positionToRectangle(1).x(), x1);
1323 void tst_qsgtextinput::maxLength()
1325 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/maxLength.qml"));
1326 QVERIFY(canvas.rootObject() != 0);
1328 canvas.requestActivateWindow();
1329 QTest::qWaitForWindowShown(&canvas);
1331 QSGTextInput *textinputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
1332 QVERIFY(textinputObject != 0);
1333 QVERIFY(textinputObject->text().isEmpty());
1334 QVERIFY(textinputObject->maxLength() == 10);
1335 foreach(const QString &str, standard){
1336 QVERIFY(textinputObject->text().length() <= 10);
1337 textinputObject->setText(str);
1338 QVERIFY(textinputObject->text().length() <= 10);
1341 textinputObject->setText("");
1342 QTRY_VERIFY(textinputObject->hasActiveFocus() == true);
1343 for(int i=0; i<20; i++){
1344 QCOMPARE(textinputObject->text().length(), qMin(i,10));
1345 //simulateKey(&canvas, Qt::Key_A);
1346 QTest::keyPress(&canvas, Qt::Key_A);
1347 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1348 QEXPECT_FAIL("", "QTBUG-21690", Abort);
1352 void tst_qsgtextinput::masks()
1354 //Not a comprehensive test of the possible masks, that's done elsewhere (QLineEdit)
1355 //QString componentStr = "import QtQuick 2.0\nTextInput { inputMask: 'HHHHhhhh'; }";
1356 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/masks.qml"));
1358 canvas.requestActivateWindow();
1359 QVERIFY(canvas.rootObject() != 0);
1360 QSGTextInput *textinputObject = qobject_cast<QSGTextInput *>(canvas.rootObject());
1361 QVERIFY(textinputObject != 0);
1362 QTRY_VERIFY(textinputObject->hasActiveFocus() == true);
1363 QVERIFY(textinputObject->text().length() == 0);
1364 QCOMPARE(textinputObject->inputMask(), QString("HHHHhhhh; "));
1365 for(int i=0; i<10; i++){
1366 QCOMPARE(qMin(i,8), textinputObject->text().length());
1367 QCOMPARE(i>=4, textinputObject->hasAcceptableInput());
1368 //simulateKey(&canvas, Qt::Key_A);
1369 QTest::keyPress(&canvas, Qt::Key_A);
1370 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1371 QEXPECT_FAIL("", "QTBUG-21690", Abort);
1375 void tst_qsgtextinput::validators()
1377 // Note that this test assumes that the validators are working properly
1378 // so you may need to run their tests first. All validators are checked
1379 // here to ensure that their exposure to QML is working.
1381 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/validators.qml"));
1383 canvas.requestActivateWindow();
1385 QVERIFY(canvas.rootObject() != 0);
1387 QSGTextInput *intInput = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("intInput")));
1389 intInput->setFocus(true);
1390 QTRY_VERIFY(intInput->hasActiveFocus());
1391 QTest::keyPress(&canvas, Qt::Key_1);
1392 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1393 QEXPECT_FAIL("", "QTBUG-21690", Abort);
1394 QCOMPARE(intInput->text(), QLatin1String("1"));
1395 QCOMPARE(intInput->hasAcceptableInput(), false);
1396 QTest::keyPress(&canvas, Qt::Key_2);
1397 QTest::keyRelease(&canvas, Qt::Key_2, Qt::NoModifier ,10);
1398 QCOMPARE(intInput->text(), QLatin1String("1"));
1399 QCOMPARE(intInput->hasAcceptableInput(), false);
1400 QTest::keyPress(&canvas, Qt::Key_1);
1401 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1402 QCOMPARE(intInput->text(), QLatin1String("11"));
1403 QCOMPARE(intInput->hasAcceptableInput(), true);
1404 QTest::keyPress(&canvas, Qt::Key_0);
1405 QTest::keyRelease(&canvas, Qt::Key_0, Qt::NoModifier ,10);
1406 QCOMPARE(intInput->text(), QLatin1String("11"));
1407 QCOMPARE(intInput->hasAcceptableInput(), true);
1409 QSGTextInput *dblInput = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("dblInput")));
1410 QTRY_VERIFY(dblInput);
1411 dblInput->setFocus(true);
1412 QVERIFY(dblInput->hasActiveFocus() == true);
1413 QTest::keyPress(&canvas, Qt::Key_1);
1414 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1415 QCOMPARE(dblInput->text(), QLatin1String("1"));
1416 QCOMPARE(dblInput->hasAcceptableInput(), false);
1417 QTest::keyPress(&canvas, Qt::Key_2);
1418 QTest::keyRelease(&canvas, Qt::Key_2, Qt::NoModifier ,10);
1419 QCOMPARE(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 QCOMPARE(dblInput->text(), QLatin1String("12."));
1424 QCOMPARE(dblInput->hasAcceptableInput(), true);
1425 QTest::keyPress(&canvas, Qt::Key_1);
1426 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1427 QCOMPARE(dblInput->text(), QLatin1String("12.1"));
1428 QCOMPARE(dblInput->hasAcceptableInput(), true);
1429 QTest::keyPress(&canvas, Qt::Key_1);
1430 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1431 QCOMPARE(dblInput->text(), QLatin1String("12.11"));
1432 QCOMPARE(dblInput->hasAcceptableInput(), true);
1433 QTest::keyPress(&canvas, Qt::Key_1);
1434 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1435 QCOMPARE(dblInput->text(), QLatin1String("12.11"));
1436 QCOMPARE(dblInput->hasAcceptableInput(), true);
1438 QSGTextInput *strInput = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("strInput")));
1439 QTRY_VERIFY(strInput);
1440 strInput->setFocus(true);
1441 QVERIFY(strInput->hasActiveFocus() == true);
1442 QTest::keyPress(&canvas, Qt::Key_1);
1443 QTest::keyRelease(&canvas, Qt::Key_1, Qt::NoModifier ,10);
1444 QCOMPARE(strInput->text(), QLatin1String(""));
1445 QCOMPARE(strInput->hasAcceptableInput(), false);
1446 QTest::keyPress(&canvas, Qt::Key_A);
1447 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1448 QCOMPARE(strInput->text(), QLatin1String("a"));
1449 QCOMPARE(strInput->hasAcceptableInput(), false);
1450 QTest::keyPress(&canvas, Qt::Key_A);
1451 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1452 QCOMPARE(strInput->text(), QLatin1String("aa"));
1453 QCOMPARE(strInput->hasAcceptableInput(), true);
1454 QTest::keyPress(&canvas, Qt::Key_A);
1455 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1456 QCOMPARE(strInput->text(), QLatin1String("aaa"));
1457 QCOMPARE(strInput->hasAcceptableInput(), true);
1458 QTest::keyPress(&canvas, Qt::Key_A);
1459 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1460 QCOMPARE(strInput->text(), QLatin1String("aaaa"));
1461 QCOMPARE(strInput->hasAcceptableInput(), true);
1462 QTest::keyPress(&canvas, Qt::Key_A);
1463 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1464 QCOMPARE(strInput->text(), QLatin1String("aaaa"));
1465 QCOMPARE(strInput->hasAcceptableInput(), true);
1468 void tst_qsgtextinput::inputMethods()
1470 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/inputmethods.qml"));
1472 canvas.requestActivateWindow();
1473 QTest::qWaitForWindowShown(&canvas);
1475 // test input method hints
1476 QVERIFY(canvas.rootObject() != 0);
1477 QSGTextInput *input = qobject_cast<QSGTextInput *>(canvas.rootObject());
1478 QVERIFY(input != 0);
1479 QVERIFY(input->inputMethodHints() & Qt::ImhNoPredictiveText);
1480 input->setInputMethodHints(Qt::ImhUppercaseOnly);
1481 QVERIFY(input->inputMethodHints() & Qt::ImhUppercaseOnly);
1483 input->setFocus(true);
1484 QVERIFY(input->hasActiveFocus() == true);
1485 // test that input method event is committed
1486 QInputMethodEvent event;
1487 event.setCommitString( "My ", -12, 0);
1488 QGuiApplication::sendEvent(&canvas, &event);
1489 QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
1490 QCOMPARE(input->text(), QString("My Hello world!"));
1492 input->setCursorPosition(2);
1493 event.setCommitString("Your", -2, 2);
1494 QGuiApplication::sendEvent(&canvas, &event);
1495 QCOMPARE(input->text(), QString("Your Hello world!"));
1496 QCOMPARE(input->cursorPosition(), 4);
1498 input->setCursorPosition(7);
1499 event.setCommitString("Goodbye", -2, 5);
1500 QGuiApplication::sendEvent(&canvas, &event);
1501 QCOMPARE(input->text(), QString("Your Goodbye world!"));
1502 QCOMPARE(input->cursorPosition(), 12);
1504 input->setCursorPosition(8);
1505 event.setCommitString("Our", -8, 4);
1506 QGuiApplication::sendEvent(&canvas, &event);
1507 QCOMPARE(input->text(), QString("Our Goodbye world!"));
1508 QCOMPARE(input->cursorPosition(), 7);
1512 TextInput element should only handle left/right keys until the cursor reaches
1513 the extent of the text, then they should ignore the keys.
1516 void tst_qsgtextinput::navigation()
1518 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/navigation.qml"));
1520 canvas.requestActivateWindow();
1522 QVERIFY(canvas.rootObject() != 0);
1524 QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1526 QVERIFY(input != 0);
1527 input->setCursorPosition(0);
1528 QTRY_VERIFY(input->hasActiveFocus() == true);
1529 simulateKey(&canvas, Qt::Key_Left);
1530 QVERIFY(input->hasActiveFocus() == false);
1531 simulateKey(&canvas, Qt::Key_Right);
1532 QVERIFY(input->hasActiveFocus() == true);
1533 //QT-2944: If text is selected, ensure we deselect upon cursor motion
1534 input->setCursorPosition(input->text().length());
1535 input->select(0,input->text().length());
1536 QVERIFY(input->selectionStart() != input->selectionEnd());
1537 simulateKey(&canvas, Qt::Key_Right);
1538 QVERIFY(input->selectionStart() == input->selectionEnd());
1539 QVERIFY(input->selectionStart() == input->text().length());
1540 QVERIFY(input->hasActiveFocus() == true);
1541 simulateKey(&canvas, Qt::Key_Right);
1542 QVERIFY(input->hasActiveFocus() == false);
1543 simulateKey(&canvas, Qt::Key_Left);
1544 QVERIFY(input->hasActiveFocus() == true);
1546 // Up and Down should NOT do Home/End, even on Mac OS X (QTBUG-10438).
1547 input->setCursorPosition(2);
1548 QCOMPARE(input->cursorPosition(),2);
1549 simulateKey(&canvas, Qt::Key_Up);
1550 QCOMPARE(input->cursorPosition(),2);
1551 simulateKey(&canvas, Qt::Key_Down);
1552 QCOMPARE(input->cursorPosition(),2);
1555 void tst_qsgtextinput::navigation_RTL()
1557 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/navigation.qml"));
1559 canvas.requestActivateWindow();
1561 QVERIFY(canvas.rootObject() != 0);
1563 QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1565 QVERIFY(input != 0);
1566 const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
1567 input->setText(QString::fromUtf16(arabic_str, 11));
1569 input->setCursorPosition(0);
1570 QTRY_VERIFY(input->hasActiveFocus() == true);
1573 simulateKey(&canvas, Qt::Key_Right);
1574 QVERIFY(input->hasActiveFocus() == false);
1577 simulateKey(&canvas, Qt::Key_Left);
1578 QVERIFY(input->hasActiveFocus() == true);
1580 input->setCursorPosition(input->text().length());
1581 QVERIFY(input->hasActiveFocus() == true);
1584 simulateKey(&canvas, Qt::Key_Left);
1585 QVERIFY(input->hasActiveFocus() == false);
1588 simulateKey(&canvas, Qt::Key_Right);
1589 QVERIFY(input->hasActiveFocus() == true);
1592 void tst_qsgtextinput::copyAndPaste() {
1593 #ifndef QT_NO_CLIPBOARD
1597 PasteboardRef pasteboard;
1598 OSStatus status = PasteboardCreate(0, &pasteboard);
1599 if (status == noErr)
1600 CFRelease(pasteboard);
1602 QSKIP("This machine doesn't support the clipboard", SkipAll);
1606 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
1607 QDeclarativeComponent textInputComponent(&engine);
1608 textInputComponent.setData(componentStr.toLatin1(), QUrl());
1609 QSGTextInput *textInput = qobject_cast<QSGTextInput*>(textInputComponent.create());
1610 QVERIFY(textInput != 0);
1613 QCOMPARE(textInput->text().length(), 12);
1614 textInput->select(0, textInput->text().length());;
1616 QCOMPARE(textInput->selectedText(), QString("Hello world!"));
1617 QCOMPARE(textInput->selectedText().length(), 12);
1618 textInput->setCursorPosition(0);
1619 QVERIFY(textInput->canPaste());
1621 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
1622 QCOMPARE(textInput->text().length(), 24);
1625 QVERIFY(textInput->canPaste());
1626 textInput->setReadOnly(true);
1627 QVERIFY(!textInput->canPaste());
1628 textInput->setReadOnly(false);
1629 QVERIFY(textInput->canPaste());
1632 textInput->setCursorPosition(0);
1633 textInput->selectWord();
1634 QCOMPARE(textInput->selectedText(), QString("Hello"));
1636 // select all and cut
1637 textInput->selectAll();
1639 QCOMPARE(textInput->text().length(), 0);
1641 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
1642 QCOMPARE(textInput->text().length(), 24);
1644 // clear copy buffer
1645 QClipboard *clipboard = QGuiApplication::clipboard();
1648 QVERIFY(!textInput->canPaste());
1650 // test that copy functionality is disabled
1651 // when echo mode is set to hide text/password mode
1654 QSGTextInput::EchoMode echoMode = QSGTextInput::EchoMode(index);
1655 textInput->setEchoMode(echoMode);
1656 textInput->setText("My password");
1657 textInput->select(0, textInput->text().length());;
1659 if (echoMode == QSGTextInput::Normal) {
1660 QVERIFY(!clipboard->text().isEmpty());
1661 QCOMPARE(clipboard->text(), QString("My password"));
1664 QVERIFY(clipboard->text().isEmpty());
1673 void tst_qsgtextinput::canPasteEmpty() {
1674 #ifndef QT_NO_CLIPBOARD
1676 QGuiApplication::clipboard()->clear();
1678 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
1679 QDeclarativeComponent textInputComponent(&engine);
1680 textInputComponent.setData(componentStr.toLatin1(), QUrl());
1681 QSGTextInput *textInput = qobject_cast<QSGTextInput*>(textInputComponent.create());
1682 QVERIFY(textInput != 0);
1685 bool cp = !lc.isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
1686 QCOMPARE(textInput->canPaste(), cp);
1691 void tst_qsgtextinput::canPaste() {
1692 #ifndef QT_NO_CLIPBOARD
1694 QGuiApplication::clipboard()->setText("Some text");
1696 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
1697 QDeclarativeComponent textInputComponent(&engine);
1698 textInputComponent.setData(componentStr.toLatin1(), QUrl());
1699 QSGTextInput *textInput = qobject_cast<QSGTextInput*>(textInputComponent.create());
1700 QVERIFY(textInput != 0);
1703 bool cp = !lc.isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
1704 QCOMPARE(textInput->canPaste(), cp);
1709 void tst_qsgtextinput::passwordCharacter()
1711 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\"; font.family: \"Helvetica\"; echoMode: TextInput.Password }";
1712 QDeclarativeComponent textInputComponent(&engine);
1713 textInputComponent.setData(componentStr.toLatin1(), QUrl());
1714 QSGTextInput *textInput = qobject_cast<QSGTextInput*>(textInputComponent.create());
1715 QVERIFY(textInput != 0);
1717 textInput->setPasswordCharacter("X");
1718 qreal implicitWidth = textInput->implicitWidth();
1719 textInput->setPasswordCharacter(".");
1721 // QTBUG-12383 content is updated and redrawn
1722 QVERIFY(textInput->implicitWidth() < implicitWidth);
1727 void tst_qsgtextinput::cursorDelegate()
1729 QSGView view(QUrl::fromLocalFile(SRCDIR "/data/cursorTest.qml"));
1731 view.requestActivateWindow();
1732 QSGTextInput *textInputObject = view.rootObject()->findChild<QSGTextInput*>("textInputObject");
1733 QVERIFY(textInputObject != 0);
1734 QVERIFY(textInputObject->findChild<QSGItem*>("cursorInstance"));
1735 //Test Delegate gets created
1736 textInputObject->setFocus(true);
1737 QSGItem* delegateObject = textInputObject->findChild<QSGItem*>("cursorInstance");
1738 QVERIFY(delegateObject);
1739 //Test Delegate gets moved
1740 for(int i=0; i<= textInputObject->text().length(); i++){
1741 textInputObject->setCursorPosition(i);
1742 QCOMPARE(textInputObject->cursorRectangle().x(), qRound(delegateObject->x()));
1743 QCOMPARE(textInputObject->cursorRectangle().y(), qRound(delegateObject->y()));
1745 textInputObject->setCursorPosition(0);
1746 QCOMPARE(textInputObject->cursorRectangle().x(), qRound(delegateObject->x()));
1747 QCOMPARE(textInputObject->cursorRectangle().y(), qRound(delegateObject->y()));
1748 //Test Delegate gets deleted
1749 textInputObject->setCursorDelegate(0);
1750 QVERIFY(!textInputObject->findChild<QSGItem*>("cursorInstance"));
1753 void tst_qsgtextinput::cursorVisible()
1755 QSGView view(QUrl::fromLocalFile(SRCDIR "/data/cursorVisible.qml"));
1757 view.requestActivateWindow();
1758 QTest::qWaitForWindowShown(&view);
1759 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
1760 QTRY_COMPARE(view.windowState(), Qt::WindowActive);
1763 QSignalSpy spy(&input, SIGNAL(cursorVisibleChanged(bool)));
1765 QCOMPARE(input.isCursorVisible(), false);
1767 input.setCursorVisible(true);
1768 QCOMPARE(input.isCursorVisible(), true);
1769 QCOMPARE(spy.count(), 1);
1771 input.setCursorVisible(false);
1772 QCOMPARE(input.isCursorVisible(), false);
1773 QCOMPARE(spy.count(), 2);
1775 input.setFocus(true);
1776 QCOMPARE(input.isCursorVisible(), false);
1777 QCOMPARE(spy.count(), 2);
1779 input.setParentItem(view.rootObject());
1780 QCOMPARE(input.isCursorVisible(), true);
1781 QCOMPARE(spy.count(), 3);
1783 input.setFocus(false);
1784 QCOMPARE(input.isCursorVisible(), false);
1785 QCOMPARE(spy.count(), 4);
1787 input.setFocus(true);
1788 QCOMPARE(input.isCursorVisible(), true);
1789 QCOMPARE(spy.count(), 5);
1791 view.setWindowState(Qt::WindowNoState);
1792 QEXPECT_FAIL("", "Most likely a side-effect of QTBUG-21489", Abort);
1793 QCOMPARE(input.isCursorVisible(), false);
1794 QCOMPARE(spy.count(), 6);
1796 view.requestActivateWindow();
1797 QCOMPARE(input.isCursorVisible(), true);
1798 QCOMPARE(spy.count(), 7);
1800 // on mac, setActiveWindow(0) on mac does not deactivate the current application
1801 // (you have to switch to a different app or hide the current app to trigger this)
1802 #if !defined(Q_WS_MAC)
1803 // QGuiApplication has no equivalent of setActiveWindow(0). Is this different to clearing the
1804 // active state of the window or can it be removed?
1805 // QApplication::setActiveWindow(0);
1806 // QTRY_COMPARE(QApplication::activeWindow(), static_cast<QWidget *>(0));
1807 // QCOMPARE(input.isCursorVisible(), false);
1808 // QCOMPARE(spy.count(), 8);
1810 // view.requestActivateWindow();
1811 // QTRY_COMPARE(view.windowState(), Qt::WindowActive);
1812 // QCOMPARE(input.isCursorVisible(), true);
1813 // QCOMPARE(spy.count(), 9);
1817 void tst_qsgtextinput::cursorRectangle()
1819 QSKIP("QTBUG-21689", SkipAll);
1821 QString text = "Hello World!";
1824 input.setText(text);
1825 QFontMetricsF fm(input.font());
1826 input.setWidth(fm.width(text.mid(0, 5)));
1830 // some tolerance for different fonts.
1832 const int error = 2;
1834 const int error = 5;
1838 for (int i = 0; i <= 5; ++i) {
1839 input.setCursorPosition(i);
1840 r = input.cursorRectangle();
1841 int textWidth = fm.width(text.mid(0, i));
1843 QVERIFY(r.left() < textWidth + error);
1844 QVERIFY(r.right() > textWidth - error);
1845 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRect(), r);
1848 // Check the cursor rectangle remains within the input bounding rect when auto scrolling.
1849 QVERIFY(r.left() < input.boundingRect().width());
1850 QVERIFY(r.right() >= input.width() - error);
1852 for (int i = 6; i < text.length(); ++i) {
1853 input.setCursorPosition(i);
1854 QCOMPARE(r, input.cursorRectangle());
1855 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRect(), r);
1858 for (int i = text.length() - 2; i >= 0; --i) {
1859 input.setCursorPosition(i);
1860 r = input.cursorRectangle();
1861 QVERIFY(r.right() >= 0);
1862 QCOMPARE(input.inputMethodQuery(Qt::ImCursorRectangle).toRect(), r);
1865 input.setText("Hi!");
1866 input.setHAlign(QSGTextInput::AlignRight);
1867 r = input.cursorRectangle();
1868 QVERIFY(r.left() < input.boundingRect().width());
1869 QVERIFY(r.right() >= input.width() - error);
1872 void tst_qsgtextinput::readOnly()
1874 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/readOnly.qml"));
1876 canvas.requestActivateWindow();
1878 QVERIFY(canvas.rootObject() != 0);
1880 QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1882 QVERIFY(input != 0);
1883 QTRY_VERIFY(input->hasActiveFocus() == true);
1884 QVERIFY(input->isReadOnly() == true);
1885 QString initial = input->text();
1886 for(int k=Qt::Key_0; k<=Qt::Key_Z; k++)
1887 simulateKey(&canvas, k);
1888 simulateKey(&canvas, Qt::Key_Return);
1889 simulateKey(&canvas, Qt::Key_Space);
1890 simulateKey(&canvas, Qt::Key_Escape);
1891 QCOMPARE(input->text(), initial);
1893 input->setCursorPosition(3);
1894 input->setReadOnly(false);
1895 QCOMPARE(input->isReadOnly(), false);
1896 QCOMPARE(input->cursorPosition(), input->text().length());
1899 void tst_qsgtextinput::echoMode()
1901 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/echoMode.qml"));
1903 canvas.requestActivateWindow();
1904 QTest::qWaitForWindowShown(&canvas);
1905 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
1906 QTRY_COMPARE(canvas.windowState(), Qt::WindowActive);
1908 QVERIFY(canvas.rootObject() != 0);
1910 QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1912 QVERIFY(input != 0);
1913 QTRY_VERIFY(input->hasActiveFocus() == true);
1914 QString initial = input->text();
1915 Qt::InputMethodHints ref;
1916 QCOMPARE(initial, QLatin1String("ABCDefgh"));
1917 QCOMPARE(input->echoMode(), QSGTextInput::Normal);
1918 QCOMPARE(input->displayText(), input->text());
1920 ref &= ~Qt::ImhHiddenText;
1921 ref &= ~(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
1922 QEXPECT_FAIL("", "QTBUG-21686", Abort);
1923 QCOMPARE(input->inputMethodHints(), ref);
1924 input->setEchoMode(QSGTextInput::NoEcho);
1925 QCOMPARE(input->text(), initial);
1926 QCOMPARE(input->displayText(), QLatin1String(""));
1927 QCOMPARE(input->passwordCharacter(), QLatin1String("*"));
1929 ref |= Qt::ImhHiddenText;
1930 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
1931 QCOMPARE(input->inputMethodHints(), ref);
1932 input->setEchoMode(QSGTextInput::Password);
1934 ref |= Qt::ImhHiddenText;
1935 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
1936 QCOMPARE(input->text(), initial);
1937 QCOMPARE(input->displayText(), QLatin1String("********"));
1938 QCOMPARE(input->inputMethodHints(), ref);
1939 input->setPasswordCharacter(QChar('Q'));
1940 QCOMPARE(input->passwordCharacter(), QLatin1String("Q"));
1941 QCOMPARE(input->text(), initial);
1942 QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
1943 input->setEchoMode(QSGTextInput::PasswordEchoOnEdit);
1944 //PasswordEchoOnEdit
1945 ref &= ~Qt::ImhHiddenText;
1946 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText);
1947 QCOMPARE(input->inputMethodHints(), ref);
1948 QCOMPARE(input->text(), initial);
1949 QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
1950 QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("QQQQQQQQ"));
1951 QTest::keyPress(&canvas, Qt::Key_A);//Clearing previous entry is part of PasswordEchoOnEdit
1952 QTest::keyRelease(&canvas, Qt::Key_A, Qt::NoModifier ,10);
1953 QCOMPARE(input->text(), QLatin1String("a"));
1954 QCOMPARE(input->displayText(), QLatin1String("a"));
1955 QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("a"));
1956 input->setFocus(false);
1957 QVERIFY(input->hasActiveFocus() == false);
1958 QCOMPARE(input->displayText(), QLatin1String("Q"));
1959 QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), QLatin1String("Q"));
1960 input->setFocus(true);
1961 QInputMethodEvent inputEvent;
1962 inputEvent.setCommitString(initial);
1963 QGuiApplication::sendEvent(&canvas, &inputEvent);
1964 QCOMPARE(input->text(), initial);
1965 QCOMPARE(input->displayText(), initial);
1966 QCOMPARE(input->inputMethodQuery(Qt::ImSurroundingText).toString(), initial);
1969 #ifdef QT_GUI_PASSWORD_ECHO_DELAY
1970 void tst_qdeclarativetextinput::passwordEchoDelay()
1972 QSGView canvas(QUrl::fromLocalFile(SRCDIR "/data/echoMode.qml"));
1975 QGuiApplication::setActiveWindow(&canvas);
1976 QTest::qWaitForWindowShown(&canvas);
1977 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
1978 QTRY_COMPARE(canvas.windowState(), Qt::WindowActive);
1980 QVERIFY(canvas.rootObject() != 0);
1982 QSGTextInput *input = qobject_cast<QSGTextInput *>(qvariant_cast<QObject *>(canvas.rootObject()->property("myInput")));
1984 QChar fillChar = QLatin1Char('*');
1986 input->setEchoMode(QDeclarativeTextInput::Password);
1987 QCOMPARE(input->displayText(), QString(8, fillChar));
1988 input->setText(QString());
1989 QCOMPARE(input->displayText(), QString());
1991 QTest::keyPress(&canvas, '0');
1992 QTest::keyPress(&canvas, '1');
1993 QTest::keyPress(&canvas, '2');
1994 QCOMPARE(input->displayText(), QString(2, fillChar) + QLatin1Char('2'));
1995 QTest::keyPress(&canvas, '3');
1996 QTest::keyPress(&canvas, '4');
1997 QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
1998 QTest::keyPress(&canvas, Qt::Key_Backspace);
1999 QCOMPARE(input->displayText(), QString(4, fillChar));
2000 QTest::keyPress(&canvas, '4');
2001 QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
2002 QTest::qWait(QT_GUI_PASSWORD_ECHO_DELAY);
2003 QTRY_COMPARE(input->displayText(), QString(5, fillChar));
2004 QTest::keyPress(&canvas, '5');
2005 QCOMPARE(input->displayText(), QString(5, fillChar) + QLatin1Char('5'));
2006 input->setFocus(false);
2007 QVERIFY(!input->hasFocus());
2008 QCOMPARE(input->displayText(), QString(6, fillChar));
2009 input->setFocus(true);
2010 QTRY_VERIFY(input->hasFocus());
2011 QCOMPARE(input->displayText(), QString(6, fillChar));
2012 QTest::keyPress(&canvas, '6');
2013 QCOMPARE(input->displayText(), QString(6, fillChar) + QLatin1Char('6'));
2015 QInputMethodEvent ev;
2016 ev.setCommitString(QLatin1String("7"));
2017 QGuiApplication::sendEvent(&canvas, &ev);
2018 QCOMPARE(input->displayText(), QString(7, fillChar) + QLatin1Char('7'));
2023 void tst_qsgtextinput::simulateKey(QSGView *view, int key)
2025 QKeyEvent press(QKeyEvent::KeyPress, key, 0);
2026 QKeyEvent release(QKeyEvent::KeyRelease, key, 0);
2028 QGuiApplication::sendEvent(view, &press);
2029 QGuiApplication::sendEvent(view, &release);
2033 class MyInputContext : public QInputContext
2036 MyInputContext() : updateReceived(false), eventType(QEvent::None) {}
2037 ~MyInputContext() {}
2039 QString identifierName() { return QString(); }
2040 QString language() { return QString(); }
2044 bool isComposing() const { return false; }
2046 void update() { updateReceived = true; }
2048 void mouseHandler(int x, QMouseEvent *event)
2051 eventType = event->type();
2052 eventPosition = event->pos();
2053 eventGlobalPosition = event->globalPos();
2054 eventButton = event->button();
2055 eventButtons = event->buttons();
2056 eventModifiers = event->modifiers();
2059 void sendPreeditText(const QString &text, int cursor)
2061 QList<QInputMethodEvent::Attribute> attributes;
2062 attributes.append(QInputMethodEvent::Attribute(
2063 QInputMethodEvent::Cursor, cursor, text.length(), QVariant()));
2065 QInputMethodEvent event(text, attributes);
2069 bool updateReceived;
2071 QEvent::Type eventType;
2072 QPoint eventPosition;
2073 QPoint eventGlobalPosition;
2074 Qt::MouseButton eventButton;
2075 Qt::MouseButtons eventButtons;
2076 Qt::KeyboardModifiers eventModifiers;
2080 void tst_qsgtextinput::openInputPanel()
2082 QSGView view(QUrl::fromLocalFile(SRCDIR "/data/openInputPanel.qml"));
2084 view.requestActivateWindow();
2085 QTest::qWaitForWindowShown(&view);
2086 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
2087 QTRY_COMPARE(view.windowState(), Qt::WindowActive);
2089 QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2092 // check default values
2093 QVERIFY(input->focusOnPress());
2094 QVERIFY(!input->hasActiveFocus());
2095 qDebug() << &input << qApp->inputPanel()->inputItem();
2096 QCOMPARE(qApp->inputPanel()->inputItem(), static_cast<QObject*>(0));
2097 QCOMPARE(qApp->inputPanel()->visible(), false);
2099 // input panel should open on focus
2100 QPoint centerPoint(view.width()/2, view.height()/2);
2101 Qt::KeyboardModifiers noModifiers = 0;
2102 QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
2103 QGuiApplication::processEvents();
2104 QVERIFY(input->hasActiveFocus());
2105 QCOMPARE(qApp->inputPanel()->inputItem(), input);
2106 QCOMPARE(qApp->inputPanel()->visible(), true);
2107 QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
2109 // input panel should be re-opened when pressing already focused TextInput
2110 qApp->inputPanel()->hide();
2111 QCOMPARE(qApp->inputPanel()->visible(), false);
2112 QVERIFY(input->hasActiveFocus());
2113 QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
2114 QGuiApplication::processEvents();
2115 QCOMPARE(qApp->inputPanel()->visible(), true);
2116 QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
2118 // input panel should stay visible if focus is lost to another text inputor
2119 QSignalSpy inputPanelVisibilitySpy(qApp->inputPanel(), SIGNAL(visibleChanged()));
2120 QSGTextInput anotherInput;
2121 anotherInput.setParentItem(view.rootObject());
2122 anotherInput.setFocus(true);
2123 QCOMPARE(qApp->inputPanel()->visible(), true);
2124 QCOMPARE(qApp->inputPanel()->inputItem(), qobject_cast<QObject*>(&anotherInput));
2125 QCOMPARE(inputPanelVisibilitySpy.count(), 0);
2127 anotherInput.setFocus(false);
2128 QCOMPARE(qApp->inputPanel()->inputItem(), static_cast<QObject*>(0));
2129 QCOMPARE(view.activeFocusItem(), view.rootItem());
2130 anotherInput.setFocus(true);
2132 // input item should be null if focus is lost to an item that doesn't accept inputs
2134 item.setParentItem(view.rootObject());
2135 item.setFocus(true);
2136 QCOMPARE(qApp->inputPanel()->inputItem(), static_cast<QObject*>(0));
2137 QCOMPARE(view.activeFocusItem(), &item);
2139 qApp->inputPanel()->hide();
2141 // input panel should not be opened if TextInput is read only
2142 input->setReadOnly(true);
2143 input->setFocus(true);
2144 QCOMPARE(qApp->inputPanel()->visible(), false);
2145 QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
2146 QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
2147 QGuiApplication::processEvents();
2148 QCOMPARE(qApp->inputPanel()->visible(), false);
2150 // input panel should not be opened if focusOnPress is set to false
2151 input->setFocusOnPress(false);
2152 input->setFocus(false);
2153 input->setFocus(true);
2154 QCOMPARE(qApp->inputPanel()->visible(), false);
2155 QTest::mousePress(&view, Qt::LeftButton, noModifiers, centerPoint);
2156 QTest::mouseRelease(&view, Qt::LeftButton, noModifiers, centerPoint);
2157 QCOMPARE(qApp->inputPanel()->visible(), false);
2159 // input panel should open when openSoftwareInputPanel is called
2160 input->openSoftwareInputPanel();
2161 QCOMPARE(qApp->inputPanel()->visible(), true);
2163 // input panel should close when closeSoftwareInputPanel is called
2164 input->closeSoftwareInputPanel();
2165 QCOMPARE(qApp->inputPanel()->visible(), false);
2168 class MyTextInput : public QSGTextInput
2171 MyTextInput(QSGItem *parent = 0) : QSGTextInput(parent)
2175 virtual QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data)
2178 return QSGTextInput::updatePaintNode(node, data);
2183 void tst_qsgtextinput::setHAlignClearCache()
2187 input.setText("Hello world");
2188 input.setParentItem(view.rootItem());
2190 view.requestActivateWindow();
2191 QTest::qWaitForWindowShown(&view);
2192 QTRY_COMPARE(input.nbPaint, 1);
2193 input.setHAlign(QSGTextInput::AlignRight);
2194 //Changing the alignment should trigger a repaint
2195 QTRY_COMPARE(input.nbPaint, 2);
2198 void tst_qsgtextinput::focusOutClearSelection()
2202 QSGTextInput input2;
2203 input.setText(QLatin1String("Hello world"));
2204 input.setFocus(true);
2205 input2.setParentItem(view.rootItem());
2206 input.setParentItem(view.rootItem());
2208 view.requestActivateWindow();
2209 QTest::qWaitForWindowShown(&view);
2211 //The selection should work
2212 QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
2213 input2.setFocus(true);
2214 QGuiApplication::processEvents();
2215 //The input lost the focus selection should be cleared
2216 QTRY_COMPARE(input.selectedText(), QLatin1String(""));
2219 void tst_qsgtextinput::geometrySignals()
2221 QDeclarativeComponent component(&engine, SRCDIR "/data/geometrySignals.qml");
2222 QObject *o = component.create();
2224 QCOMPARE(o->property("bindingWidth").toInt(), 400);
2225 QCOMPARE(o->property("bindingHeight").toInt(), 500);
2229 void tst_qsgtextinput::testQtQuick11Attributes()
2231 QFETCH(QString, code);
2232 QFETCH(QString, warning);
2233 QFETCH(QString, error);
2235 QDeclarativeEngine engine;
2238 QDeclarativeComponent valid(&engine);
2239 valid.setData("import QtQuick 2.0; TextInput { " + code.toUtf8() + " }", QUrl(""));
2240 obj = valid.create();
2242 QVERIFY(valid.errorString().isEmpty());
2245 QDeclarativeComponent invalid(&engine);
2246 invalid.setData("import QtQuick 1.0; TextInput { " + code.toUtf8() + " }", QUrl(""));
2247 QTest::ignoreMessage(QtWarningMsg, warning.toUtf8());
2248 obj = invalid.create();
2249 QCOMPARE(invalid.errorString(), error);
2253 void tst_qsgtextinput::testQtQuick11Attributes_data()
2255 QTest::addColumn<QString>("code");
2256 QTest::addColumn<QString>("warning");
2257 QTest::addColumn<QString>("error");
2259 QTest::newRow("canPaste") << "property bool foo: canPaste"
2260 << "<Unknown File>:1: ReferenceError: Can't find variable: canPaste"
2263 QTest::newRow("moveCursorSelection") << "Component.onCompleted: moveCursorSelection(0, TextEdit.SelectCharacters)"
2264 << "<Unknown File>:1: ReferenceError: Can't find variable: moveCursorSelection"
2267 QTest::newRow("deselect") << "Component.onCompleted: deselect()"
2268 << "<Unknown File>:1: ReferenceError: Can't find variable: deselect"
2272 void tst_qsgtextinput::preeditAutoScroll()
2275 QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
2278 QString preeditText = "califragisiticexpialidocious!";
2280 QSGView view(QUrl::fromLocalFile(SRCDIR "/data/preeditAutoScroll.qml"));
2282 // QSGCanvas won't set the Qt::WA_InputMethodEnabled flag unless a suitable item has active focus
2283 // and QWidget won't allow an input context to be set when the flag is not set.
2284 view.setAttribute(Qt::WA_InputMethodEnabled, true);
2285 view.setInputContext(&ic);
2286 view.setAttribute(Qt::WA_InputMethodEnabled, false);
2288 view.requestActivateWindow();
2289 QTest::qWaitForWindowShown(&view);
2290 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
2291 QTRY_COMPARE(view.windowState(), Qt::WindowActive);
2292 QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2295 QSignalSpy cursorRectangleSpy(input, SIGNAL(cursorRectangleChanged()));
2296 int cursorRectangleChanges = 0;
2298 QFontMetricsF fm(input->font());
2299 input->setWidth(fm.width(input->text()));
2301 // test the text is scrolled so the preedit is visible.
2302 ic.sendPreeditText(preeditText.mid(0, 3), 1);
2303 QVERIFY(input->positionAt(0) != 0);
2304 QVERIFY(input->cursorRectangle().left() < input->boundingRect().width());
2305 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2307 // test the text is scrolled back when the preedit is removed.
2308 ic.sendEvent(QInputMethodEvent());
2309 QCOMPARE(input->positionAt(0), 0);
2310 QCOMPARE(input->positionAt(input->width()), 5);
2311 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2313 // some tolerance for different fonts.
2315 const int error = 2;
2317 const int error = 5;
2320 // test if the preedit is larger than the text input that the
2321 // character preceding the cursor is still visible.
2322 qreal x = input->positionToRectangle(0).x();
2323 for (int i = 0; i < 3; ++i) {
2324 ic.sendPreeditText(preeditText, i + 1);
2325 QVERIFY(input->cursorRectangle().right() >= fm.width(preeditText.at(i)) - error);
2326 QVERIFY(input->positionToRectangle(0).x() < x);
2327 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2328 x = input->positionToRectangle(0).x();
2330 for (int i = 1; i >= 0; --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();
2338 // Test incrementing the preedit cursor doesn't cause further
2339 // scrolling when right most text is visible.
2340 ic.sendPreeditText(preeditText, preeditText.length() - 3);
2341 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2342 x = input->positionToRectangle(0).x();
2343 for (int i = 2; i >= 0; --i) {
2344 ic.sendPreeditText(preeditText, preeditText.length() - i);
2345 QCOMPARE(input->positionToRectangle(0).x(), x);
2346 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2348 for (int i = 1; i < 3; ++i) {
2349 ic.sendPreeditText(preeditText, preeditText.length() - i);
2350 QCOMPARE(input->positionToRectangle(0).x(), x);
2351 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
2354 // Test disabling auto scroll.
2355 ic.sendEvent(QInputMethodEvent());
2357 input->setAutoScroll(false);
2358 ic.sendPreeditText(preeditText.mid(0, 3), 1);
2359 QCOMPARE(input->positionAt(0), 0);
2360 QCOMPARE(input->positionAt(input->width()), 5);
2364 void tst_qsgtextinput::preeditMicroFocus()
2367 QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
2370 QString preeditText = "super";
2372 QSGView view(QUrl::fromLocalFile(SRCDIR "/data/inputMethodEvent.qml"));
2374 // QSGCanvas won't set the Qt::WA_InputMethodEnabled flag unless a suitable item has active focus
2375 // and QWidget won't allow an input context to be set when the flag is not set.
2376 view.setAttribute(Qt::WA_InputMethodEnabled, true);
2377 view.setInputContext(&ic);
2378 view.setAttribute(Qt::WA_InputMethodEnabled, false);
2380 view.requestActivateWindow();
2381 QTest::qWaitForWindowShown(&view);
2382 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
2383 QTRY_COMPARE(view.windowState(), Qt::WindowActive);
2384 QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2388 QRect previousRect = input->inputMethodQuery(Qt::ImCursorRectangle).toRect();
2390 // Verify that the micro focus rect is positioned the same for position 0 as
2391 // it would be if there was no preedit text.
2392 ic.updateReceived = false;
2393 ic.sendPreeditText(preeditText, 0);
2394 currentRect = input->inputMethodQuery(Qt::ImCursorRectangle).toRect();
2395 QCOMPARE(currentRect, previousRect);
2396 #if defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)
2397 QCOMPARE(ic.updateReceived, true);
2400 // Verify that the micro focus rect moves to the left as the cursor position
2402 for (int i = 1; i <= 5; ++i) {
2403 ic.updateReceived = false;
2404 ic.sendPreeditText(preeditText, i);
2405 currentRect = input->inputMethodQuery(Qt::ImCursorRectangle).toRect();
2406 QVERIFY(previousRect.left() < currentRect.left());
2407 #if defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)
2408 QCOMPARE(ic.updateReceived, true);
2410 previousRect = currentRect;
2413 // Verify that if there is no preedit cursor then the micro focus rect is the
2414 // same as it would be if it were positioned at the end of the preedit text.
2415 ic.sendPreeditText(preeditText, 0);
2416 ic.updateReceived = false;
2417 ic.sendEvent(QInputMethodEvent(preeditText, QList<QInputMethodEvent::Attribute>()));
2418 currentRect = input->inputMethodQuery(Qt::ImCursorRectangle).toRect();
2419 QCOMPARE(currentRect, previousRect);
2420 #if defined(Q_WS_X11) || defined(Q_WS_QWS) || defined(Q_OS_SYMBIAN)
2421 QCOMPARE(ic.updateReceived, true);
2426 void tst_qsgtextinput::inputContextMouseHandler()
2429 QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
2432 QString text = "supercalifragisiticexpialidocious!";
2434 QSGView view(QUrl::fromLocalFile(SRCDIR "/data/inputContext.qml"));
2436 // QSGCanvas won't set the Qt::WA_InputMethodEnabled flag unless a suitable item has active focus
2437 // and QWidget won't allow an input context to be set when the flag is not set.
2438 view.setAttribute(Qt::WA_InputMethodEnabled, true);
2439 view.setInputContext(&ic);
2440 view.setAttribute(Qt::WA_InputMethodEnabled, false);
2442 view.requestActivateWindow();
2443 QTest::qWaitForWindowShown(&view);
2444 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
2445 QTRY_COMPARE(view.windowState(), Qt::WindowActive);
2446 QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2449 QFontMetricsF fm(input->font());
2450 const qreal y = fm.height() / 2;
2452 QPoint position2 = input->mapToScene(QPointF(fm.width(text.mid(0, 2)), y)).toPoint();
2453 QPoint position8 = input->mapToScene(QPointF(fm.width(text.mid(0, 8)), y)).toPoint();
2454 QPoint position20 = input->mapToScene(QPointF(fm.width(text.mid(0, 20)), y)).toPoint();
2455 QPoint position27 = input->mapToScene(QPointF(fm.width(text.mid(0, 27)), y)).toPoint();
2456 QPoint globalPosition2 = view.mapToGlobal(position2);
2457 QPoint globalposition8 = view.mapToGlobal(position8);
2458 QPoint globalposition20 = view.mapToGlobal(position20);
2459 QPoint globalposition27 = view.mapToGlobal(position27);
2461 ic.sendEvent(QInputMethodEvent(text.mid(12), QList<QInputMethodEvent::Attribute>()));
2463 QTest::mouseDClick(&view, Qt::LeftButton, Qt::NoModifier, position2);
2464 QCOMPARE(ic.eventType, QEvent::MouseButtonDblClick);
2465 QCOMPARE(ic.eventPosition, position2);
2466 QCOMPARE(ic.eventGlobalPosition, globalPosition2);
2467 QCOMPARE(ic.eventButton, Qt::LeftButton);
2468 QCOMPARE(ic.eventModifiers, Qt::NoModifier);
2469 QVERIFY(ic.cursor < 0);
2470 ic.eventType = QEvent::None;
2472 QTest::mousePress(&view, Qt::LeftButton, Qt::NoModifier, position2);
2473 QCOMPARE(ic.eventType, QEvent::MouseButtonPress);
2474 QCOMPARE(ic.eventPosition, position2);
2475 QCOMPARE(ic.eventGlobalPosition, globalPosition2);
2476 QCOMPARE(ic.eventButton, Qt::LeftButton);
2477 QCOMPARE(ic.eventModifiers, Qt::NoModifier);
2478 QVERIFY(ic.cursor < 0);
2479 ic.eventType = QEvent::None;
2481 { QMouseEvent mv(QEvent::MouseMove, position8, globalposition8, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
2482 QGuiApplication::sendEvent(&view, &mv); }
2483 QCOMPARE(ic.eventType, QEvent::None);
2485 { QMouseEvent mv(QEvent::MouseMove, position27, globalposition27, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
2486 QGuiApplication::sendEvent(&view, &mv); }
2487 QCOMPARE(ic.eventType, QEvent::MouseMove);
2488 QCOMPARE(ic.eventPosition, position27);
2489 QCOMPARE(ic.eventGlobalPosition, globalposition27);
2490 QCOMPARE(ic.eventButton, Qt::LeftButton);
2491 QCOMPARE(ic.eventModifiers, Qt::NoModifier);
2492 QVERIFY(ic.cursor >= 14 && ic.cursor <= 16); // 15 is expected but some platforms may be off by one.
2493 ic.eventType = QEvent::None;
2495 QTest::mouseRelease(&view, Qt::LeftButton, Qt::NoModifier, position27);
2496 QCOMPARE(ic.eventType, QEvent::MouseButtonRelease);
2497 QCOMPARE(ic.eventPosition, position27);
2498 QCOMPARE(ic.eventGlobalPosition, globalposition27);
2499 QCOMPARE(ic.eventButton, Qt::LeftButton);
2500 QCOMPARE(ic.eventModifiers, Qt::NoModifier);
2501 QVERIFY(ic.cursor >= 14 && ic.cursor <= 16);
2502 ic.eventType = QEvent::None;
2504 // And in the other direction.
2505 QTest::mouseDClick(&view, Qt::LeftButton, Qt::ControlModifier, position27);
2506 QCOMPARE(ic.eventType, QEvent::MouseButtonDblClick);
2507 QCOMPARE(ic.eventPosition, position27);
2508 QCOMPARE(ic.eventGlobalPosition, globalposition27);
2509 QCOMPARE(ic.eventButton, Qt::LeftButton);
2510 QCOMPARE(ic.eventModifiers, Qt::ControlModifier);
2511 QVERIFY(ic.cursor >= 14 && ic.cursor <= 16);
2512 ic.eventType = QEvent::None;
2514 QTest::mousePress(&view, Qt::RightButton, Qt::ControlModifier, position27);
2515 QCOMPARE(ic.eventType, QEvent::MouseButtonPress);
2516 QCOMPARE(ic.eventPosition, position27);
2517 QCOMPARE(ic.eventGlobalPosition, globalposition27);
2518 QCOMPARE(ic.eventButton, Qt::RightButton);
2519 QCOMPARE(ic.eventModifiers, Qt::ControlModifier);
2520 QVERIFY(ic.cursor >= 14 && ic.cursor <= 16);
2521 ic.eventType = QEvent::None;
2523 { QMouseEvent mv(QEvent::MouseMove, position20, globalposition20, Qt::RightButton, Qt::RightButton,Qt::ControlModifier);
2524 QGuiApplication::sendEvent(&view, &mv); }
2525 QCOMPARE(ic.eventType, QEvent::MouseMove);
2526 QCOMPARE(ic.eventPosition, position20);
2527 QCOMPARE(ic.eventGlobalPosition, globalposition20);
2528 QCOMPARE(ic.eventButton, Qt::RightButton);
2529 QCOMPARE(ic.eventModifiers, Qt::ControlModifier);
2530 QVERIFY(ic.cursor >= 7 && ic.cursor <= 9);
2531 ic.eventType = QEvent::None;
2533 { QMouseEvent mv(QEvent::MouseMove, position2, globalPosition2, Qt::RightButton, Qt::RightButton,Qt::ControlModifier);
2534 QGuiApplication::sendEvent(&view, &mv); }
2535 QCOMPARE(ic.eventType, QEvent::None);
2537 QTest::mouseRelease(&view, Qt::RightButton, Qt::ControlModifier, position2);
2538 QCOMPARE(ic.eventType, QEvent::MouseButtonRelease);
2539 QCOMPARE(ic.eventPosition, position2);
2540 QCOMPARE(ic.eventGlobalPosition, globalPosition2);
2541 QCOMPARE(ic.eventButton, Qt::RightButton);
2542 QCOMPARE(ic.eventModifiers, Qt::ControlModifier);
2543 QVERIFY(ic.cursor < 0);
2544 ic.eventType = QEvent::None;
2548 void tst_qsgtextinput::inputMethodComposing()
2550 QString text = "supercalifragisiticexpialidocious!";
2552 QSGView view(QUrl::fromLocalFile(SRCDIR "/data/inputContext.qml"));
2554 view.requestActivateWindow();
2555 QTest::qWaitForWindowShown(&view);
2556 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
2557 QTRY_COMPARE(view.windowState(), Qt::WindowActive);
2558 QSGTextInput *input = qobject_cast<QSGTextInput *>(view.rootObject());
2560 QSignalSpy spy(input, SIGNAL(inputMethodComposingChanged()));
2562 QCOMPARE(input->isInputMethodComposing(), false);
2564 QInputMethodEvent event(text.mid(3), QList<QInputMethodEvent::Attribute>());
2565 QGuiApplication::sendEvent(&view, &event);
2567 QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
2568 QCOMPARE(input->isInputMethodComposing(), true);
2569 QCOMPARE(spy.count(), 1);
2572 QInputMethodEvent event(text.mid(12), QList<QInputMethodEvent::Attribute>());
2573 QGuiApplication::sendEvent(&view, &event);
2575 QCOMPARE(spy.count(), 1);
2578 QInputMethodEvent event;
2579 QGuiApplication::sendEvent(&view, &event);
2581 QCOMPARE(input->isInputMethodComposing(), false);
2582 QCOMPARE(spy.count(), 2);
2585 void tst_qsgtextinput::cursorRectangleSize()
2588 QEXPECT_FAIL("", QTBUG_21691_MESSAGE, Abort);
2591 QSGView *canvas = new QSGView(QUrl::fromLocalFile(SRCDIR "/data/positionAt.qml"));
2592 QVERIFY(canvas->rootObject() != 0);
2595 QGuiApplication::setActiveWindow(canvas);
2596 QTest::qWaitForWindowShown(canvas);
2598 QSGTextInput *textInput = qobject_cast<QSGTextInput *>(canvas->rootObject());
2599 QVERIFY(textInput != 0);
2600 textInput->setFocus(Qt::OtherFocusReason);
2601 QRectF cursorRect = textInput->positionToRectangle(textInput->cursorPosition());
2602 QRectF microFocusFromScene = canvas->inputMethodQuery(Qt::ImCursorRectangle).toRectF();
2603 QRectF microFocusFromApp= QGuiApplication::focusWidget()->inputMethodQuery(Qt::ImCursorRectangle).toRectF();
2605 QCOMPARE(microFocusFromScene.size(), cursorRect.size());
2606 QCOMPARE(microFocusFromApp.size(), cursorRect.size());
2612 void tst_qsgtextinput::tripleClickSelectsAll()
2614 QString qmlfile = SRCDIR "/data/positionAt.qml";
2615 QSGView view(QUrl::fromLocalFile(qmlfile));
2616 view.requestActivateWindow();
2618 QTest::qWaitForWindowShown(&view);
2620 QEXPECT_FAIL("", QTBUG_21489_MESSAGE, Continue);
2621 QTRY_COMPARE(view.windowState(), Qt::WindowActive);
2623 QSGTextInput* input = qobject_cast<QSGTextInput*>(view.rootObject());
2626 QLatin1String hello("Hello world!");
2627 input->setSelectByMouse(true);
2628 input->setText(hello);
2630 // Clicking on the same point inside TextInput three times in a row
2631 // should trigger a triple click, thus selecting all the text.
2632 QPoint pointInside = input->pos().toPoint() + QPoint(2,2);
2633 QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
2634 QTest::mouseClick(&view, Qt::LeftButton, 0, pointInside);
2635 QGuiApplication::processEvents();
2636 QCOMPARE(input->selectedText(), hello);
2638 // Now it simulates user moving the mouse between the second and the third click.
2639 // In this situation, we don't expect a triple click.
2640 QPoint pointInsideButFar = QPoint(input->width(),input->height()) - QPoint(2,2);
2641 QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
2642 QTest::mouseClick(&view, Qt::LeftButton, 0, pointInsideButFar);
2643 QGuiApplication::processEvents();
2644 QVERIFY(input->selectedText().isEmpty());
2646 // And now we press the third click too late, so no triple click event is triggered.
2647 QTest::mouseDClick(&view, Qt::LeftButton, 0, pointInside);
2648 QGuiApplication::processEvents();
2649 QTest::qWait(QApplication::doubleClickInterval() + 1);
2650 QTest::mouseClick(&view, Qt::LeftButton, 0, pointInside);
2651 QGuiApplication::processEvents();
2652 QVERIFY(input->selectedText().isEmpty());
2655 QTEST_MAIN(tst_qsgtextinput)
2657 #include "tst_qsgtextinput.moc"