1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28#include <qtest.h>
29#include <QtTest/QSignalSpy>
30#include "../../shared/util.h"
31#include "../../shared/testhttpserver.h"
32#include <private/qinputmethod_p.h>
33#include <QtQml/qqmlengine.h>
34#include <QtQml/qqmlexpression.h>
35#include <QFile>
36#include <QtQuick/qquickview.h>
37#include <QtGui/qguiapplication.h>
38#include <QtGui/qstylehints.h>
39#include <QtGui/qvalidator.h>
40#include <QInputMethod>
41#include <private/qquicktextinput_p.h>
42#include <private/qquicktextinput_p_p.h>
43#include <private/qquickvalidator_p.h>
44#include <QDebug>
45#include <QDir>
46#include <math.h>
47#include <qmath.h>
48
49#ifdef Q_OS_OSX
50#include <Carbon/Carbon.h>
51#endif
52
53#include "qplatformdefs.h"
54#include "../../shared/platformquirks.h"
55#include "../../shared/platforminputcontext.h"
56
57Q_DECLARE_METATYPE(QQuickTextInput::SelectionMode)
58Q_DECLARE_METATYPE(QQuickTextInput::EchoMode)
59Q_DECLARE_METATYPE(Qt::Key)
60
61DEFINE_BOOL_CONFIG_OPTION(qmlDisableDistanceField, QML_DISABLE_DISTANCEFIELD)
62
63QString createExpectedFileIfNotFound(const QString& filebasename, const QImage& actual)
64{
65 // XXX This will be replaced by some clever persistent platform image store.
66 QString persistent_dir = QQmlDataTest::instance()->dataDirectory();
67 QString arch = "unknown-architecture"; // QTest needs to help with this.
68
69 QString expectfile = persistent_dir + QDir::separator() + filebasename + QLatin1Char('-') + arch + ".png";
70
71 if (!QFile::exists(fileName: expectfile)) {
72 actual.save(fileName: expectfile);
73 qWarning() << "created" << expectfile;
74 }
75
76 return expectfile;
77}
78
79template <typename T> static T evaluate(QObject *scope, const QString &expression)
80{
81 QQmlExpression expr(qmlContext(scope), scope, expression);
82 T result = expr.evaluate().value<T>();
83 if (expr.hasError())
84 qWarning() << expr.error().toString();
85 return result;
86}
87
88template<typename T, int N> int lengthOf(const T (&)[N]) { return N; }
89
90typedef QPair<int, QChar> Key;
91
92class tst_qquicktextinput : public QQmlDataTest
93
94{
95 Q_OBJECT
96public:
97 tst_qquicktextinput();
98
99private slots:
100 void cleanup();
101 void text();
102 void width();
103 void font();
104 void color();
105 void wrap();
106 void selection();
107 void persistentSelection();
108 void overwriteMode();
109 void isRightToLeft_data();
110 void isRightToLeft();
111 void moveCursorSelection_data();
112 void moveCursorSelection();
113 void moveCursorSelectionSequence_data();
114 void moveCursorSelectionSequence();
115 void dragMouseSelection();
116 void mouseSelectionMode_data();
117 void mouseSelectionMode();
118 void mouseSelectionMode_accessors();
119 void selectByMouse();
120 void renderType();
121 void tripleClickSelectsAll();
122
123 void horizontalAlignment_RightToLeft();
124 void verticalAlignment();
125
126 void clipRect();
127 void boundingRect();
128
129 void positionAt();
130
131 void maxLength();
132 void masks();
133 void validators();
134 void inputMethods();
135
136 void signal_accepted();
137 void signal_editingfinished();
138 void signal_textEdited();
139
140 void passwordCharacter();
141 void cursorDelegate_data();
142 void cursorDelegate();
143 void remoteCursorDelegate();
144 void cursorVisible();
145 void cursorRectangle_data();
146 void cursorRectangle();
147 void navigation();
148 void navigation_RTL();
149#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
150 void copyAndPaste();
151 void copyAndPasteKeySequence();
152 void canPasteEmpty();
153 void canPaste();
154 void middleClickPaste();
155#endif
156 void readOnly();
157 void focusOnPress();
158 void focusOnPressOnlyOneItem();
159
160 void openInputPanel();
161 void setHAlignClearCache();
162 void focusOutClearSelection();
163 void focusOutNotClearSelection();
164
165 void echoMode();
166 void passwordEchoDelay();
167 void geometrySignals();
168 void contentSize();
169
170 void preeditAutoScroll();
171 void preeditCursorRectangle();
172 void inputContextMouseHandler();
173 void inputMethodComposing();
174 void inputMethodUpdate();
175 void cursorRectangleSize();
176
177 void getText_data();
178 void getText();
179 void insert_data();
180 void insert();
181 void remove_data();
182 void remove();
183
184#if QT_CONFIG(shortcut)
185 void keySequence_data();
186 void keySequence();
187#endif
188
189 void undo_data();
190 void undo();
191 void redo_data();
192 void redo();
193#if QT_CONFIG(shortcut)
194 void undo_keypressevents_data();
195 void undo_keypressevents();
196#endif
197 void clear();
198
199 void backspaceSurrogatePairs();
200
201 void QTBUG_19956();
202 void QTBUG_19956_data();
203 void QTBUG_19956_regexp();
204
205 void implicitSize_data();
206 void implicitSize();
207 void implicitSizeBinding_data();
208 void implicitSizeBinding();
209 void implicitResize_data();
210 void implicitResize();
211
212 void negativeDimensions();
213
214
215 void setInputMask_data();
216 void setInputMask();
217 void inputMask_data();
218 void inputMask();
219 void clearInputMask();
220 void keypress_inputMask_data();
221 void keypress_inputMask();
222 void keypress_inputMethod_inputMask();
223 void keypress_inputMask_withValidator_data();
224 void keypress_inputMask_withValidator();
225 void hasAcceptableInputMask_data();
226 void hasAcceptableInputMask();
227 void maskCharacter_data();
228 void maskCharacter();
229 void fixup();
230 void baselineOffset_data();
231 void baselineOffset();
232
233 void ensureVisible();
234 void padding();
235
236 void QTBUG_51115_readOnlyResetsSelection();
237 void QTBUG_77814_InsertRemoveNoSelection();
238
239 void checkCursorDelegateWhenPaddingChanged();
240
241 void focusReason();
242private:
243 void simulateKey(QWindow *, int key);
244
245 void simulateKeys(QWindow *window, const QList<Key> &keys);
246#if QT_CONFIG(shortcut)
247 void simulateKeys(QWindow *window, const QKeySequence &sequence);
248#endif
249
250 QQmlEngine engine;
251 QStringList standard;
252 QStringList colorStrings;
253};
254
255typedef QList<int> IntList;
256Q_DECLARE_METATYPE(IntList)
257
258typedef QList<Key> KeyList;
259Q_DECLARE_METATYPE(KeyList)
260
261void tst_qquicktextinput::simulateKeys(QWindow *window, const QList<Key> &keys)
262{
263 for (int i = 0; i < keys.count(); ++i) {
264 const int key = keys.at(i).first & ~Qt::KeyboardModifierMask;
265 const int modifiers = keys.at(i).first & Qt::KeyboardModifierMask;
266 const QString text = !keys.at(i).second.isNull() ? QString(keys.at(i).second) : QString();
267
268 QKeyEvent press(QEvent::KeyPress, Qt::Key(key), Qt::KeyboardModifiers(modifiers), text);
269 QKeyEvent release(QEvent::KeyRelease, Qt::Key(key), Qt::KeyboardModifiers(modifiers), text);
270
271 QGuiApplication::sendEvent(receiver: window, event: &press);
272 QGuiApplication::sendEvent(receiver: window, event: &release);
273 }
274}
275
276#if QT_CONFIG(shortcut)
277
278void tst_qquicktextinput::simulateKeys(QWindow *window, const QKeySequence &sequence)
279{
280 for (int i = 0; i < sequence.count(); ++i) {
281 const int key = sequence[i];
282 const int modifiers = key & Qt::KeyboardModifierMask;
283
284 QTest::keyClick(window, key: Qt::Key(key & ~modifiers), modifier: Qt::KeyboardModifiers(modifiers));
285 }
286}
287
288QList<Key> &operator <<(QList<Key> &keys, const QKeySequence &sequence)
289{
290 for (int i = 0; i < sequence.count(); ++i)
291 keys << Key(sequence[i], QChar());
292 return keys;
293}
294
295#endif // QT_CONFIG(shortcut)
296
297template <int N> QList<Key> &operator <<(QList<Key> &keys, const char (&characters)[N])
298{
299 for (int i = 0; i < N - 1; ++i) {
300 int key = QTest::asciiToKey(ascii: characters[i]);
301 QChar character = QLatin1Char(characters[i]);
302 keys << Key(key, character);
303 }
304 return keys;
305}
306
307QList<Key> &operator <<(QList<Key> &keys, Qt::Key key)
308{
309 keys << Key(key, QChar());
310 return keys;
311}
312
313void tst_qquicktextinput::cleanup()
314{
315 // ensure not even skipped tests with custom input context leave it dangling
316 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
317 inputMethodPrivate->testContext = nullptr;
318}
319
320tst_qquicktextinput::tst_qquicktextinput()
321{
322 standard << "the quick brown fox jumped over the lazy dog"
323 << "It's supercalifragisiticexpialidocious!"
324 << "Hello, world!"
325 << "!dlrow ,olleH"
326 << " spacey text ";
327
328 colorStrings << "aliceblue"
329 << "antiquewhite"
330 << "aqua"
331 << "darkkhaki"
332 << "darkolivegreen"
333 << "dimgray"
334 << "palevioletred"
335 << "lightsteelblue"
336 << "#000000"
337 << "#AAAAAA"
338 << "#FFFFFF"
339 << "#2AC05F";
340}
341
342void tst_qquicktextinput::text()
343{
344 {
345 QQmlComponent textinputComponent(&engine);
346 textinputComponent.setData("import QtQuick 2.0\nTextInput { text: \"\" }", baseUrl: QUrl());
347 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
348
349 QVERIFY(textinputObject != nullptr);
350 QCOMPARE(textinputObject->text(), QString(""));
351 QCOMPARE(textinputObject->length(), 0);
352
353 delete textinputObject;
354 }
355
356 for (int i = 0; i < standard.size(); i++)
357 {
358 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
359 QQmlComponent textinputComponent(&engine);
360 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
361 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
362
363 QVERIFY(textinputObject != nullptr);
364 QCOMPARE(textinputObject->text(), standard.at(i));
365 QCOMPARE(textinputObject->length(), standard.at(i).length());
366
367 delete textinputObject;
368 }
369
370}
371
372void tst_qquicktextinput::width()
373{
374 // uses Font metrics to find the width for standard
375 {
376 QQmlComponent textinputComponent(&engine);
377 textinputComponent.setData("import QtQuick 2.0\nTextInput { text: \"\" }", baseUrl: QUrl());
378 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
379
380 QVERIFY(textinputObject != nullptr);
381 QCOMPARE(textinputObject->width(), 0.0);
382
383 delete textinputObject;
384 }
385
386 bool requiresUnhintedMetrics = !qmlDisableDistanceField();
387
388 for (int i = 0; i < standard.size(); i++)
389 {
390 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + standard.at(i) + "\" }";
391 QQmlComponent textinputComponent(&engine);
392 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
393 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
394
395 QString s = standard.at(i);
396 s.replace(before: QLatin1Char('\n'), after: QChar::LineSeparator);
397
398 QTextLayout layout(s);
399 layout.setFont(textinputObject->font());
400 layout.setFlags(Qt::TextExpandTabs | Qt::TextShowMnemonic);
401 if (requiresUnhintedMetrics) {
402 QTextOption option;
403 option.setUseDesignMetrics(true);
404 layout.setTextOption(option);
405 }
406
407 layout.beginLayout();
408 forever {
409 QTextLine line = layout.createLine();
410 if (!line.isValid())
411 break;
412 }
413
414 layout.endLayout();
415
416 qreal metricWidth = ceil(x: layout.boundingRect().width());
417
418 QVERIFY(textinputObject != nullptr);
419 int delta = abs(x: int(int(textinputObject->width()) - metricWidth));
420 QVERIFY(delta <= 3.0); // As best as we can hope for cross-platform.
421
422 delete textinputObject;
423 }
424}
425
426void tst_qquicktextinput::font()
427{
428 //test size, then bold, then italic, then family
429 {
430 QString componentStr = "import QtQuick 2.0\nTextInput { font.pointSize: 40; text: \"Hello World\" }";
431 QQmlComponent textinputComponent(&engine);
432 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
433 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
434
435 QVERIFY(textinputObject != nullptr);
436 QCOMPARE(textinputObject->font().pointSize(), 40);
437 QCOMPARE(textinputObject->font().bold(), false);
438 QCOMPARE(textinputObject->font().italic(), false);
439
440 delete textinputObject;
441 }
442
443 {
444 QString componentStr = "import QtQuick 2.0\nTextInput { font.bold: true; text: \"Hello World\" }";
445 QQmlComponent textinputComponent(&engine);
446 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
447 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
448
449 QVERIFY(textinputObject != nullptr);
450 QCOMPARE(textinputObject->font().bold(), true);
451 QCOMPARE(textinputObject->font().italic(), false);
452
453 delete textinputObject;
454 }
455
456 {
457 QString componentStr = "import QtQuick 2.0\nTextInput { font.italic: true; text: \"Hello World\" }";
458 QQmlComponent textinputComponent(&engine);
459 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
460 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
461
462 QVERIFY(textinputObject != nullptr);
463 QCOMPARE(textinputObject->font().italic(), true);
464 QCOMPARE(textinputObject->font().bold(), false);
465
466 delete textinputObject;
467 }
468
469 {
470 QString componentStr = "import QtQuick 2.0\nTextInput { font.family: \"Helvetica\"; text: \"Hello World\" }";
471 QQmlComponent textinputComponent(&engine);
472 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
473 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
474
475 QVERIFY(textinputObject != nullptr);
476 QCOMPARE(textinputObject->font().family(), QString("Helvetica"));
477 QCOMPARE(textinputObject->font().bold(), false);
478 QCOMPARE(textinputObject->font().italic(), false);
479
480 delete textinputObject;
481 }
482
483 {
484 QString componentStr = "import QtQuick 2.0\nTextInput { font.family: \"\"; text: \"Hello World\" }";
485 QQmlComponent textinputComponent(&engine);
486 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
487 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
488
489 QVERIFY(textinputObject != nullptr);
490 QCOMPARE(textinputObject->font().family(), QString(""));
491
492 delete textinputObject;
493 }
494}
495
496void tst_qquicktextinput::color()
497{
498 //test initial color
499 {
500 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello World\" }";
501 QQmlComponent texteditComponent(&engine);
502 texteditComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
503 QScopedPointer<QObject> object(texteditComponent.create());
504 QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput*>(object: object.data());
505
506 QVERIFY(textInputObject);
507 QCOMPARE(textInputObject->color(), QColor("black"));
508 QCOMPARE(textInputObject->selectionColor(), QColor::fromRgba(0xFF000080));
509 QCOMPARE(textInputObject->selectedTextColor(), QColor("white"));
510
511 QSignalSpy colorSpy(textInputObject, SIGNAL(colorChanged()));
512 QSignalSpy selectionColorSpy(textInputObject, SIGNAL(selectionColorChanged()));
513 QSignalSpy selectedTextColorSpy(textInputObject, SIGNAL(selectedTextColorChanged()));
514
515 textInputObject->setColor(QColor("white"));
516 QCOMPARE(textInputObject->color(), QColor("white"));
517 QCOMPARE(colorSpy.count(), 1);
518
519 textInputObject->setSelectionColor(QColor("black"));
520 QCOMPARE(textInputObject->selectionColor(), QColor("black"));
521 QCOMPARE(selectionColorSpy.count(), 1);
522
523 textInputObject->setSelectedTextColor(QColor("blue"));
524 QCOMPARE(textInputObject->selectedTextColor(), QColor("blue"));
525 QCOMPARE(selectedTextColorSpy.count(), 1);
526
527 textInputObject->setColor(QColor("white"));
528 QCOMPARE(colorSpy.count(), 1);
529
530 textInputObject->setSelectionColor(QColor("black"));
531 QCOMPARE(selectionColorSpy.count(), 1);
532
533 textInputObject->setSelectedTextColor(QColor("blue"));
534 QCOMPARE(selectedTextColorSpy.count(), 1);
535
536 textInputObject->setColor(QColor("black"));
537 QCOMPARE(textInputObject->color(), QColor("black"));
538 QCOMPARE(colorSpy.count(), 2);
539
540 textInputObject->setSelectionColor(QColor("blue"));
541 QCOMPARE(textInputObject->selectionColor(), QColor("blue"));
542 QCOMPARE(selectionColorSpy.count(), 2);
543
544 textInputObject->setSelectedTextColor(QColor("white"));
545 QCOMPARE(textInputObject->selectedTextColor(), QColor("white"));
546 QCOMPARE(selectedTextColorSpy.count(), 2);
547 }
548
549 //test color
550 for (int i = 0; i < colorStrings.size(); i++)
551 {
552 QString componentStr = "import QtQuick 2.0\nTextInput { color: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
553 QQmlComponent textinputComponent(&engine);
554 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
555 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
556 QVERIFY(textinputObject != nullptr);
557 QCOMPARE(textinputObject->color(), QColor(colorStrings.at(i)));
558
559 delete textinputObject;
560 }
561
562 //test selection color
563 for (int i = 0; i < colorStrings.size(); i++)
564 {
565 QString componentStr = "import QtQuick 2.0\nTextInput { selectionColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
566 QQmlComponent textinputComponent(&engine);
567 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
568 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
569 QVERIFY(textinputObject != nullptr);
570 QCOMPARE(textinputObject->selectionColor(), QColor(colorStrings.at(i)));
571
572 delete textinputObject;
573 }
574
575 //test selected text color
576 for (int i = 0; i < colorStrings.size(); i++)
577 {
578 QString componentStr = "import QtQuick 2.0\nTextInput { selectedTextColor: \"" + colorStrings.at(i) + "\"; text: \"Hello World\" }";
579 QQmlComponent textinputComponent(&engine);
580 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
581 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
582 QVERIFY(textinputObject != nullptr);
583 QCOMPARE(textinputObject->selectedTextColor(), QColor(colorStrings.at(i)));
584
585 delete textinputObject;
586 }
587
588 {
589 QString colorStr = "#AA001234";
590 QColor testColor("#001234");
591 testColor.setAlpha(170);
592
593 QString componentStr = "import QtQuick 2.0\nTextInput { color: \"" + colorStr + "\"; text: \"Hello World\" }";
594 QQmlComponent textinputComponent(&engine);
595 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
596 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
597
598 QVERIFY(textinputObject != nullptr);
599 QCOMPARE(textinputObject->color(), testColor);
600
601 delete textinputObject;
602 }
603}
604
605void tst_qquicktextinput::wrap()
606{
607 int textHeight = 0;
608 // for specified width and wrap set true
609 {
610 QQmlComponent textComponent(&engine);
611 textComponent.setData("import QtQuick 2.0\nTextInput { text: \"Hello\"; wrapMode: Text.WrapAnywhere; width: 300 }", baseUrl: QUrl::fromLocalFile(localfile: ""));
612 QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(object: textComponent.create());
613 textHeight = textObject->height();
614
615 QVERIFY(textObject != nullptr);
616 QCOMPARE(textObject->wrapMode(), QQuickTextInput::WrapAnywhere);
617 QCOMPARE(textObject->width(), 300.);
618
619 delete textObject;
620 }
621
622 for (int i = 0; i < standard.count(); i++) {
623 QString componentStr = "import QtQuick 2.0\nTextInput { wrapMode: Text.WrapAnywhere; width: 30; text: \"" + standard.at(i) + "\" }";
624 QQmlComponent textComponent(&engine);
625 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
626 QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(object: textComponent.create());
627
628 QVERIFY(textObject != nullptr);
629 QCOMPARE(textObject->width(), 30.);
630 QVERIFY(textObject->height() > textHeight);
631
632 int oldHeight = textObject->height();
633 textObject->setWidth(100);
634 QVERIFY(textObject->height() < oldHeight);
635
636 delete textObject;
637 }
638
639 {
640 QQmlComponent component(&engine);
641 component.setData("import QtQuick 2.0\n TextInput {}", baseUrl: QUrl());
642 QScopedPointer<QObject> object(component.create());
643 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: object.data());
644 QVERIFY(input);
645
646 QSignalSpy spy(input, SIGNAL(wrapModeChanged()));
647
648 QCOMPARE(input->wrapMode(), QQuickTextInput::NoWrap);
649
650 input->setWrapMode(QQuickTextInput::Wrap);
651 QCOMPARE(input->wrapMode(), QQuickTextInput::Wrap);
652 QCOMPARE(spy.count(), 1);
653
654 input->setWrapMode(QQuickTextInput::Wrap);
655 QCOMPARE(spy.count(), 1);
656
657 input->setWrapMode(QQuickTextInput::NoWrap);
658 QCOMPARE(input->wrapMode(), QQuickTextInput::NoWrap);
659 QCOMPARE(spy.count(), 2);
660 }
661}
662
663void tst_qquicktextinput::selection()
664{
665 QString testStr = standard[0];
666 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
667 QQmlComponent textinputComponent(&engine);
668 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
669 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
670 QVERIFY(textinputObject != nullptr);
671
672
673 //Test selection follows cursor
674 for (int i=0; i<= testStr.size(); i++) {
675 textinputObject->setCursorPosition(i);
676 QCOMPARE(textinputObject->cursorPosition(), i);
677 QCOMPARE(textinputObject->selectionStart(), i);
678 QCOMPARE(textinputObject->selectionEnd(), i);
679 QVERIFY(textinputObject->selectedText().isNull());
680 }
681
682 textinputObject->setCursorPosition(0);
683 QCOMPARE(textinputObject->cursorPosition(), 0);
684 QCOMPARE(textinputObject->selectionStart(), 0);
685 QCOMPARE(textinputObject->selectionEnd(), 0);
686 QVERIFY(textinputObject->selectedText().isNull());
687
688 // Verify invalid positions are ignored.
689 textinputObject->setCursorPosition(-1);
690 QCOMPARE(textinputObject->cursorPosition(), 0);
691 QCOMPARE(textinputObject->selectionStart(), 0);
692 QCOMPARE(textinputObject->selectionEnd(), 0);
693 QVERIFY(textinputObject->selectedText().isNull());
694
695 textinputObject->setCursorPosition(textinputObject->text().count()+1);
696 QCOMPARE(textinputObject->cursorPosition(), 0);
697 QCOMPARE(textinputObject->selectionStart(), 0);
698 QCOMPARE(textinputObject->selectionEnd(), 0);
699 QVERIFY(textinputObject->selectedText().isNull());
700
701 //Test selection
702 for (int i=0; i<= testStr.size(); i++) {
703 textinputObject->select(start: 0,end: i);
704 QCOMPARE(testStr.mid(0,i), textinputObject->selectedText());
705 }
706 for (int i=0; i<= testStr.size(); i++) {
707 textinputObject->select(start: i,end: testStr.size());
708 QCOMPARE(testStr.mid(i,testStr.size()-i), textinputObject->selectedText());
709 }
710
711 textinputObject->setCursorPosition(0);
712 QCOMPARE(textinputObject->cursorPosition(), 0);
713 QCOMPARE(textinputObject->selectionStart(), 0);
714 QCOMPARE(textinputObject->selectionEnd(), 0);
715 QVERIFY(textinputObject->selectedText().isNull());
716
717 //Test Error Ignoring behaviour
718 textinputObject->setCursorPosition(0);
719 QVERIFY(textinputObject->selectedText().isNull());
720 textinputObject->select(start: -10,end: 0);
721 QVERIFY(textinputObject->selectedText().isNull());
722 textinputObject->select(start: 100,end: 110);
723 QVERIFY(textinputObject->selectedText().isNull());
724 textinputObject->select(start: 0,end: -10);
725 QVERIFY(textinputObject->selectedText().isNull());
726 textinputObject->select(start: 0,end: 100);
727 QVERIFY(textinputObject->selectedText().isNull());
728 textinputObject->select(start: 0,end: 10);
729 QCOMPARE(textinputObject->selectedText().size(), 10);
730 textinputObject->select(start: -10,end: 10);
731 QCOMPARE(textinputObject->selectedText().size(), 10);
732 textinputObject->select(start: 100,end: 101);
733 QCOMPARE(textinputObject->selectedText().size(), 10);
734 textinputObject->select(start: 0,end: -10);
735 QCOMPARE(textinputObject->selectedText().size(), 10);
736 textinputObject->select(start: 0,end: 100);
737 QCOMPARE(textinputObject->selectedText().size(), 10);
738
739 textinputObject->deselect();
740 QVERIFY(textinputObject->selectedText().isNull());
741 textinputObject->select(start: 0,end: 10);
742 QCOMPARE(textinputObject->selectedText().size(), 10);
743 textinputObject->deselect();
744 QVERIFY(textinputObject->selectedText().isNull());
745
746 // test input method selection
747 QSignalSpy selectionSpy(textinputObject, SIGNAL(selectedTextChanged()));
748 textinputObject->setFocus(true);
749 {
750 QList<QInputMethodEvent::Attribute> attributes;
751 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 12, 5, QVariant());
752 QInputMethodEvent event("", attributes);
753 QGuiApplication::sendEvent(receiver: textinputObject, event: &event);
754 }
755 QCOMPARE(selectionSpy.count(), 1);
756 QCOMPARE(textinputObject->selectionStart(), 12);
757 QCOMPARE(textinputObject->selectionEnd(), 17);
758
759 delete textinputObject;
760}
761
762void tst_qquicktextinput::persistentSelection()
763{
764 QQuickView window(testFileUrl(fileName: "persistentSelection.qml"));
765 window.show();
766 window.requestActivate();
767 QVERIFY(QTest::qWaitForWindowActive(&window));
768
769 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: window.rootObject());
770 QVERIFY(input);
771 QVERIFY(input->hasActiveFocus());
772
773 QSignalSpy spy(input, SIGNAL(persistentSelectionChanged()));
774
775 QCOMPARE(input->persistentSelection(), false);
776
777 input->setPersistentSelection(false);
778 QCOMPARE(input->persistentSelection(), false);
779 QCOMPARE(spy.count(), 0);
780
781 input->select(start: 1, end: 4);
782 QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
783
784 input->setFocus(false);
785 QCOMPARE(input->property("selected").toString(), QString());
786
787 input->setFocus(true);
788 QCOMPARE(input->property("selected").toString(), QString());
789
790 input->setPersistentSelection(true);
791 QCOMPARE(input->persistentSelection(), true);
792 QCOMPARE(spy.count(), 1);
793
794 input->select(start: 1, end: 4);
795 QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
796
797 input->setFocus(false);
798 QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
799
800 input->setFocus(true);
801 QCOMPARE(input->property("selected").toString(), QLatin1String("ell"));
802}
803
804void tst_qquicktextinput::overwriteMode()
805{
806 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; }";
807 QQmlComponent textInputComponent(&engine);
808 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
809 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
810 QVERIFY(textInput != nullptr);
811
812 QSignalSpy spy(textInput, SIGNAL(overwriteModeChanged(bool)));
813
814 QQuickWindow window;
815 textInput->setParentItem(window.contentItem());
816 window.show();
817 window.requestActivate();
818 QVERIFY(QTest::qWaitForWindowActive(&window));
819
820 QVERIFY(textInput->hasActiveFocus());
821
822 textInput->setOverwriteMode(true);
823 QCOMPARE(spy.count(), 1);
824 QCOMPARE(true, textInput->overwriteMode());
825 textInput->setOverwriteMode(false);
826 QCOMPARE(spy.count(), 2);
827 QCOMPARE(false, textInput->overwriteMode());
828
829 QVERIFY(!textInput->overwriteMode());
830 QString insertString = "Some first text";
831 for (int j = 0; j < insertString.length(); j++)
832 QTest::keyClick(window: &window, key: insertString.at(i: j).toLatin1());
833
834 QCOMPARE(textInput->text(), QString("Some first text"));
835
836 textInput->setOverwriteMode(true);
837 QCOMPARE(spy.count(), 3);
838 textInput->setCursorPosition(5);
839
840 insertString = "shiny";
841 for (int j = 0; j < insertString.length(); j++)
842 QTest::keyClick(window: &window, key: insertString.at(i: j).toLatin1());
843 QCOMPARE(textInput->text(), QString("Some shiny text"));
844}
845
846void tst_qquicktextinput::isRightToLeft_data()
847{
848 QTest::addColumn<QString>(name: "text");
849 QTest::addColumn<bool>(name: "emptyString");
850 QTest::addColumn<bool>(name: "firstCharacter");
851 QTest::addColumn<bool>(name: "lastCharacter");
852 QTest::addColumn<bool>(name: "middleCharacter");
853 QTest::addColumn<bool>(name: "startString");
854 QTest::addColumn<bool>(name: "midString");
855 QTest::addColumn<bool>(name: "endString");
856
857 const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
858 QTest::newRow(dataTag: "Empty") << "" << false << false << false << false << false << false << false;
859 QTest::newRow(dataTag: "Neutral") << "23244242" << false << false << false << false << false << false << false;
860 QTest::newRow(dataTag: "LTR") << "Hello world" << false << false << false << false << false << false << false;
861 QTest::newRow(dataTag: "RTL") << QString::fromUtf16(arabic_str, size: 11) << false << true << true << true << true << true << true;
862 QTest::newRow(dataTag: "Bidi RTL + LTR + RTL") << QString::fromUtf16(arabic_str, size: 11) + QString("Hello world") + QString::fromUtf16(arabic_str, size: 11) << false << true << true << false << true << true << true;
863 QTest::newRow(dataTag: "Bidi LTR + RTL + LTR") << QString("Hello world") + QString::fromUtf16(arabic_str, size: 11) + QString("Hello world") << false << false << false << true << false << false << false;
864}
865
866void tst_qquicktextinput::isRightToLeft()
867{
868 QFETCH(QString, text);
869 QFETCH(bool, emptyString);
870 QFETCH(bool, firstCharacter);
871 QFETCH(bool, lastCharacter);
872 QFETCH(bool, middleCharacter);
873 QFETCH(bool, startString);
874 QFETCH(bool, midString);
875 QFETCH(bool, endString);
876
877 QQuickTextInput textInput;
878 textInput.setText(text);
879
880 // first test that the right string is delivered to the QString::isRightToLeft()
881 QCOMPARE(textInput.isRightToLeft(0,0), text.mid(0,0).isRightToLeft());
882 QCOMPARE(textInput.isRightToLeft(0,1), text.mid(0,1).isRightToLeft());
883 QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), text.mid(text.count()-2, text.count()-1).isRightToLeft());
884 QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), text.mid(text.count()/2, text.count()/2 + 1).isRightToLeft());
885 QCOMPARE(textInput.isRightToLeft(0,text.count()/4), text.mid(0,text.count()/4).isRightToLeft());
886 QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), text.mid(text.count()/4,3*text.count()/4).isRightToLeft());
887 if (text.isEmpty())
888 QTest::ignoreMessage(type: QtWarningMsg, message: "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
889 QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), text.mid(3*text.count()/4,text.count()-1).isRightToLeft());
890
891 // then test that the feature actually works
892 QCOMPARE(textInput.isRightToLeft(0,0), emptyString);
893 QCOMPARE(textInput.isRightToLeft(0,1), firstCharacter);
894 QCOMPARE(textInput.isRightToLeft(text.count()-2, text.count()-1), lastCharacter);
895 QCOMPARE(textInput.isRightToLeft(text.count()/2, text.count()/2 + 1), middleCharacter);
896 QCOMPARE(textInput.isRightToLeft(0,text.count()/4), startString);
897 QCOMPARE(textInput.isRightToLeft(text.count()/4,3*text.count()/4), midString);
898 if (text.isEmpty())
899 QTest::ignoreMessage(type: QtWarningMsg, message: "<Unknown File>: QML TextInput: isRightToLeft(start, end) called with the end property being smaller than the start.");
900 QCOMPARE(textInput.isRightToLeft(3*text.count()/4,text.count()-1), endString);
901}
902
903void tst_qquicktextinput::moveCursorSelection_data()
904{
905 QTest::addColumn<QString>(name: "testStr");
906 QTest::addColumn<int>(name: "cursorPosition");
907 QTest::addColumn<int>(name: "movePosition");
908 QTest::addColumn<QQuickTextInput::SelectionMode>(name: "mode");
909 QTest::addColumn<int>(name: "selectionStart");
910 QTest::addColumn<int>(name: "selectionEnd");
911 QTest::addColumn<bool>(name: "reversible");
912
913 // () contains the text selected by the cursor.
914 // <> contains the actual selection.
915
916 QTest::newRow(dataTag: "(t)he|characters")
917 << standard[0] << 0 << 1 << QQuickTextInput::SelectCharacters << 0 << 1 << true;
918 QTest::newRow(dataTag: "do(g)|characters")
919 << standard[0] << 43 << 44 << QQuickTextInput::SelectCharacters << 43 << 44 << true;
920 QTest::newRow(dataTag: "jum(p)ed|characters")
921 << standard[0] << 23 << 24 << QQuickTextInput::SelectCharacters << 23 << 24 << true;
922 QTest::newRow(dataTag: "jumped( )over|characters")
923 << standard[0] << 26 << 27 << QQuickTextInput::SelectCharacters << 26 << 27 << true;
924 QTest::newRow(dataTag: "(the )|characters")
925 << standard[0] << 0 << 4 << QQuickTextInput::SelectCharacters << 0 << 4 << true;
926 QTest::newRow(dataTag: "( dog)|characters")
927 << standard[0] << 40 << 44 << QQuickTextInput::SelectCharacters << 40 << 44 << true;
928 QTest::newRow(dataTag: "( jumped )|characters")
929 << standard[0] << 19 << 27 << QQuickTextInput::SelectCharacters << 19 << 27 << true;
930 QTest::newRow(dataTag: "th(e qu)ick|characters")
931 << standard[0] << 2 << 6 << QQuickTextInput::SelectCharacters << 2 << 6 << true;
932 QTest::newRow(dataTag: "la(zy d)og|characters")
933 << standard[0] << 38 << 42 << QQuickTextInput::SelectCharacters << 38 << 42 << true;
934 QTest::newRow(dataTag: "jum(ped ov)er|characters")
935 << standard[0] << 23 << 29 << QQuickTextInput::SelectCharacters << 23 << 29 << true;
936 QTest::newRow(dataTag: "()the|characters")
937 << standard[0] << 0 << 0 << QQuickTextInput::SelectCharacters << 0 << 0 << true;
938 QTest::newRow(dataTag: "dog()|characters")
939 << standard[0] << 44 << 44 << QQuickTextInput::SelectCharacters << 44 << 44 << true;
940 QTest::newRow(dataTag: "jum()ped|characters")
941 << standard[0] << 23 << 23 << QQuickTextInput::SelectCharacters << 23 << 23 << true;
942
943 QTest::newRow(dataTag: "<(t)he>|words")
944 << standard[0] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 3 << true;
945 QTest::newRow(dataTag: "<do(g)>|words")
946 << standard[0] << 43 << 44 << QQuickTextInput::SelectWords << 41 << 44 << true;
947 QTest::newRow(dataTag: "<jum(p)ed>|words")
948 << standard[0] << 23 << 24 << QQuickTextInput::SelectWords << 20 << 26 << true;
949 QTest::newRow(dataTag: "<jumped( )>over|words,ltr")
950 << standard[0] << 26 << 27 << QQuickTextInput::SelectWords << 20 << 27 << false;
951 QTest::newRow(dataTag: "jumped<( )over>|words,rtl")
952 << standard[0] << 27 << 26 << QQuickTextInput::SelectWords << 26 << 31 << false;
953 QTest::newRow(dataTag: "<(the )>quick|words,ltr")
954 << standard[0] << 0 << 4 << QQuickTextInput::SelectWords << 0 << 4 << false;
955 QTest::newRow(dataTag: "<(the )quick>|words,rtl")
956 << standard[0] << 4 << 0 << QQuickTextInput::SelectWords << 0 << 9 << false;
957 QTest::newRow(dataTag: "<lazy( dog)>|words,ltr")
958 << standard[0] << 40 << 44 << QQuickTextInput::SelectWords << 36 << 44 << false;
959 QTest::newRow(dataTag: "lazy<( dog)>|words,rtl")
960 << standard[0] << 44 << 40 << QQuickTextInput::SelectWords << 40 << 44 << false;
961 QTest::newRow(dataTag: "<fox( jumped )>over|words,ltr")
962 << standard[0] << 19 << 27 << QQuickTextInput::SelectWords << 16 << 27 << false;
963 QTest::newRow(dataTag: "fox<( jumped )over>|words,rtl")
964 << standard[0] << 27 << 19 << QQuickTextInput::SelectWords << 19 << 31 << false;
965 QTest::newRow(dataTag: "<th(e qu)ick>|words")
966 << standard[0] << 2 << 6 << QQuickTextInput::SelectWords << 0 << 9 << true;
967 QTest::newRow(dataTag: "<la(zy d)og|words>")
968 << standard[0] << 38 << 42 << QQuickTextInput::SelectWords << 36 << 44 << true;
969 QTest::newRow(dataTag: "<jum(ped ov)er>|words")
970 << standard[0] << 23 << 29 << QQuickTextInput::SelectWords << 20 << 31 << true;
971 QTest::newRow(dataTag: "<()>the|words")
972 << standard[0] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << true;
973 QTest::newRow(dataTag: "dog<()>|words")
974 << standard[0] << 44 << 44 << QQuickTextInput::SelectWords << 44 << 44 << true;
975 QTest::newRow(dataTag: "jum<()>ped|words")
976 << standard[0] << 23 << 23 << QQuickTextInput::SelectWords << 23 << 23 << true;
977
978 QTest::newRow(dataTag: "<Hello(,)> |words,ltr")
979 << standard[2] << 5 << 6 << QQuickTextInput::SelectWords << 0 << 6 << false;
980 QTest::newRow(dataTag: "Hello<(,)> |words,rtl")
981 << standard[2] << 6 << 5 << QQuickTextInput::SelectWords << 5 << 6 << false;
982 QTest::newRow(dataTag: "<Hello(, )>world|words,ltr")
983 << standard[2] << 5 << 7 << QQuickTextInput::SelectWords << 0 << 7 << false;
984 QTest::newRow(dataTag: "Hello<(, )world>|words,rtl")
985 << standard[2] << 7 << 5 << QQuickTextInput::SelectWords << 5 << 12 << false;
986 QTest::newRow(dataTag: "<Hel(lo, )>world|words,ltr")
987 << standard[2] << 3 << 7 << QQuickTextInput::SelectWords << 0 << 7 << false;
988 QTest::newRow(dataTag: "<Hel(lo, )world>|words,rtl")
989 << standard[2] << 7 << 3 << QQuickTextInput::SelectWords << 0 << 12 << false;
990 QTest::newRow(dataTag: "<Hel(lo)>,|words")
991 << standard[2] << 3 << 5 << QQuickTextInput::SelectWords << 0 << 5 << true;
992 QTest::newRow(dataTag: "Hello<()>,|words")
993 << standard[2] << 5 << 5 << QQuickTextInput::SelectWords << 5 << 5 << true;
994 QTest::newRow(dataTag: "Hello,<()>|words")
995 << standard[2] << 6 << 6 << QQuickTextInput::SelectWords << 6 << 6 << true;
996 QTest::newRow(dataTag: "Hello,<( )>world|words,ltr")
997 << standard[2] << 6 << 7 << QQuickTextInput::SelectWords << 6 << 7 << false;
998 QTest::newRow(dataTag: "Hello,<( )world>|words,rtl")
999 << standard[2] << 7 << 6 << QQuickTextInput::SelectWords << 6 << 12 << false;
1000 QTest::newRow(dataTag: "Hello,<( world)>|words")
1001 << standard[2] << 6 << 12 << QQuickTextInput::SelectWords << 6 << 12 << true;
1002 QTest::newRow(dataTag: "Hello,<( world!)>|words")
1003 << standard[2] << 6 << 13 << QQuickTextInput::SelectWords << 6 << 13 << true;
1004 QTest::newRow(dataTag: "<Hello(, world!)>|words,ltr")
1005 << standard[2] << 5 << 13 << QQuickTextInput::SelectWords << 0 << 13 << false;
1006 QTest::newRow(dataTag: "Hello<(, world!)>|words,rtl")
1007 << standard[2] << 13 << 5 << QQuickTextInput::SelectWords << 5 << 13 << false;
1008 QTest::newRow(dataTag: "<world(!)>|words,ltr")
1009 << standard[2] << 12 << 13 << QQuickTextInput::SelectWords << 7 << 13 << false;
1010 QTest::newRow(dataTag: "world<(!)>|words,rtl")
1011 << standard[2] << 13 << 12 << QQuickTextInput::SelectWords << 12 << 13 << false;
1012 QTest::newRow(dataTag: "world!<()>)|words")
1013 << standard[2] << 13 << 13 << QQuickTextInput::SelectWords << 13 << 13 << true;
1014 QTest::newRow(dataTag: "world<()>!)|words")
1015 << standard[2] << 12 << 12 << QQuickTextInput::SelectWords << 12 << 12 << true;
1016
1017 QTest::newRow(dataTag: "<(,)>olleH |words,ltr")
1018 << standard[3] << 7 << 8 << QQuickTextInput::SelectWords << 7 << 8 << false;
1019 QTest::newRow(dataTag: "<(,)olleH> |words,rtl")
1020 << standard[3] << 8 << 7 << QQuickTextInput::SelectWords << 7 << 13 << false;
1021 QTest::newRow(dataTag: "<dlrow( ,)>olleH|words,ltr")
1022 << standard[3] << 6 << 8 << QQuickTextInput::SelectWords << 1 << 8 << false;
1023 QTest::newRow(dataTag: "dlrow<( ,)olleH>|words,rtl")
1024 << standard[3] << 8 << 6 << QQuickTextInput::SelectWords << 6 << 13 << false;
1025 QTest::newRow(dataTag: "<dlrow( ,ol)leH>|words,ltr")
1026 << standard[3] << 6 << 10 << QQuickTextInput::SelectWords << 1 << 13 << false;
1027 QTest::newRow(dataTag: "dlrow<( ,ol)leH>|words,rtl")
1028 << standard[3] << 10 << 6 << QQuickTextInput::SelectWords << 6 << 13 << false;
1029 QTest::newRow(dataTag: ",<(ol)leH>,|words")
1030 << standard[3] << 8 << 10 << QQuickTextInput::SelectWords << 8 << 13 << true;
1031 QTest::newRow(dataTag: ",<()>olleH|words")
1032 << standard[3] << 8 << 8 << QQuickTextInput::SelectWords << 8 << 8 << true;
1033 QTest::newRow(dataTag: "<()>,olleH|words")
1034 << standard[3] << 7 << 7 << QQuickTextInput::SelectWords << 7 << 7 << true;
1035 QTest::newRow(dataTag: "<dlrow( )>,olleH|words,ltr")
1036 << standard[3] << 6 << 7 << QQuickTextInput::SelectWords << 1 << 7 << false;
1037 QTest::newRow(dataTag: "dlrow<( )>,olleH|words,rtl")
1038 << standard[3] << 7 << 6 << QQuickTextInput::SelectWords << 6 << 7 << false;
1039 QTest::newRow(dataTag: "<(dlrow )>,olleH|words")
1040 << standard[3] << 1 << 7 << QQuickTextInput::SelectWords << 1 << 7 << true;
1041 QTest::newRow(dataTag: "<(!dlrow )>,olleH|words")
1042 << standard[3] << 0 << 7 << QQuickTextInput::SelectWords << 0 << 7 << true;
1043 QTest::newRow(dataTag: "<(!dlrow ,)>olleH|words,ltr")
1044 << standard[3] << 0 << 8 << QQuickTextInput::SelectWords << 0 << 8 << false;
1045 QTest::newRow(dataTag: "<(!dlrow ,)olleH>|words,rtl")
1046 << standard[3] << 8 << 0 << QQuickTextInput::SelectWords << 0 << 13 << false;
1047 QTest::newRow(dataTag: "<(!)>dlrow|words,ltr")
1048 << standard[3] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 1 << false;
1049 QTest::newRow(dataTag: "<(!)dlrow|words,rtl")
1050 << standard[3] << 1 << 0 << QQuickTextInput::SelectWords << 0 << 6 << false;
1051 QTest::newRow(dataTag: "<()>!dlrow|words")
1052 << standard[3] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << true;
1053 QTest::newRow(dataTag: "!<()>dlrow|words")
1054 << standard[3] << 1 << 1 << QQuickTextInput::SelectWords << 1 << 1 << true;
1055
1056 QTest::newRow(dataTag: " <s(pac)ey> text |words")
1057 << standard[4] << 1 << 4 << QQuickTextInput::SelectWords << 1 << 7 << true;
1058 QTest::newRow(dataTag: " spacey <t(ex)t> |words")
1059 << standard[4] << 11 << 13 << QQuickTextInput::SelectWords << 10 << 14 << true;
1060 QTest::newRow(dataTag: "<( )>spacey text |words|ltr")
1061 << standard[4] << 0 << 1 << QQuickTextInput::SelectWords << 0 << 1 << false;
1062 QTest::newRow(dataTag: "<( )spacey> text |words|rtl")
1063 << standard[4] << 1 << 0 << QQuickTextInput::SelectWords << 0 << 7 << false;
1064 QTest::newRow(dataTag: "spacey <text( )>|words|ltr")
1065 << standard[4] << 14 << 15 << QQuickTextInput::SelectWords << 10 << 15 << false;
1066 QTest::newRow(dataTag: "spacey text<( )>|words|rtl")
1067 << standard[4] << 15 << 14 << QQuickTextInput::SelectWords << 14 << 15 << false;
1068 QTest::newRow(dataTag: "<()> spacey text |words")
1069 << standard[4] << 0 << 0 << QQuickTextInput::SelectWords << 0 << 0 << false;
1070 QTest::newRow(dataTag: " spacey text <()>|words")
1071 << standard[4] << 15 << 15 << QQuickTextInput::SelectWords << 15 << 15 << false;
1072}
1073
1074void tst_qquicktextinput::moveCursorSelection()
1075{
1076 QFETCH(QString, testStr);
1077 QFETCH(int, cursorPosition);
1078 QFETCH(int, movePosition);
1079 QFETCH(QQuickTextInput::SelectionMode, mode);
1080 QFETCH(int, selectionStart);
1081 QFETCH(int, selectionEnd);
1082 QFETCH(bool, reversible);
1083
1084 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
1085 QQmlComponent textinputComponent(&engine);
1086 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
1087 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
1088 QVERIFY(textinputObject != nullptr);
1089
1090 textinputObject->setCursorPosition(cursorPosition);
1091 textinputObject->moveCursorSelection(pos: movePosition, mode);
1092
1093 QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
1094 QCOMPARE(textinputObject->selectionStart(), selectionStart);
1095 QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
1096
1097 if (reversible) {
1098 textinputObject->setCursorPosition(movePosition);
1099 textinputObject->moveCursorSelection(pos: cursorPosition, mode);
1100
1101 QCOMPARE(textinputObject->selectedText(), testStr.mid(selectionStart, selectionEnd - selectionStart));
1102 QCOMPARE(textinputObject->selectionStart(), selectionStart);
1103 QCOMPARE(textinputObject->selectionEnd(), selectionEnd);
1104 }
1105
1106 delete textinputObject;
1107}
1108
1109void tst_qquicktextinput::moveCursorSelectionSequence_data()
1110{
1111 QTest::addColumn<QString>(name: "testStr");
1112 QTest::addColumn<int>(name: "cursorPosition");
1113 QTest::addColumn<int>(name: "movePosition1");
1114 QTest::addColumn<int>(name: "movePosition2");
1115 QTest::addColumn<int>(name: "selection1Start");
1116 QTest::addColumn<int>(name: "selection1End");
1117 QTest::addColumn<int>(name: "selection2Start");
1118 QTest::addColumn<int>(name: "selection2End");
1119
1120 // () contains the text selected by the cursor.
1121 // <> contains the actual selection.
1122 // ^ is the revised cursor position.
1123 // {} contains the revised selection.
1124
1125 QTest::newRow(dataTag: "the {<quick( bro)wn> f^ox} jumped|ltr")
1126 << standard[0]
1127 << 9 << 13 << 17
1128 << 4 << 15
1129 << 4 << 19;
1130 QTest::newRow(dataTag: "the quick<( {bro)wn> f^ox} jumped|rtl")
1131 << standard[0]
1132 << 13 << 9 << 17
1133 << 9 << 15
1134 << 10 << 19;
1135 QTest::newRow(dataTag: "the {<quick( bro)wn> ^}fox jumped|ltr")
1136 << standard[0]
1137 << 9 << 13 << 16
1138 << 4 << 15
1139 << 4 << 16;
1140 QTest::newRow(dataTag: "the quick<( {bro)wn> ^}fox jumped|rtl")
1141 << standard[0]
1142 << 13 << 9 << 16
1143 << 9 << 15
1144 << 10 << 16;
1145 QTest::newRow(dataTag: "the {<quick( bro)wn^>} fox jumped|ltr")
1146 << standard[0]
1147 << 9 << 13 << 15
1148 << 4 << 15
1149 << 4 << 15;
1150 QTest::newRow(dataTag: "the quick<( {bro)wn^>} f^ox jumped|rtl")
1151 << standard[0]
1152 << 13 << 9 << 15
1153 << 9 << 15
1154 << 10 << 15;
1155 QTest::newRow(dataTag: "the {<quick() ^}bro)wn> fox|ltr")
1156 << standard[0]
1157 << 9 << 13 << 10
1158 << 4 << 15
1159 << 4 << 10;
1160 QTest::newRow(dataTag: "the quick<( {^bro)wn>} fox|rtl")
1161 << standard[0]
1162 << 13 << 9 << 10
1163 << 9 << 15
1164 << 10 << 15;
1165 QTest::newRow(dataTag: "the {<quick^}( bro)wn> fox|ltr")
1166 << standard[0]
1167 << 9 << 13 << 9
1168 << 4 << 15
1169 << 4 << 9;
1170 QTest::newRow(dataTag: "the quick{<(^ bro)wn>} fox|rtl")
1171 << standard[0]
1172 << 13 << 9 << 9
1173 << 9 << 15
1174 << 9 << 15;
1175 QTest::newRow(dataTag: "the {<qui^ck}( bro)wn> fox|ltr")
1176 << standard[0]
1177 << 9 << 13 << 7
1178 << 4 << 15
1179 << 4 << 9;
1180 QTest::newRow(dataTag: "the {<qui^ck}( bro)wn> fox|rtl")
1181 << standard[0]
1182 << 13 << 9 << 7
1183 << 9 << 15
1184 << 4 << 15;
1185 QTest::newRow(dataTag: "the {<^quick}( bro)wn> fox|ltr")
1186 << standard[0]
1187 << 9 << 13 << 4
1188 << 4 << 15
1189 << 4 << 9;
1190 QTest::newRow(dataTag: "the {<^quick}( bro)wn> fox|rtl")
1191 << standard[0]
1192 << 13 << 9 << 4
1193 << 9 << 15
1194 << 4 << 15;
1195 QTest::newRow(dataTag: "the{^ <quick}( bro)wn> fox|ltr")
1196 << standard[0]
1197 << 9 << 13 << 3
1198 << 4 << 15
1199 << 3 << 9;
1200 QTest::newRow(dataTag: "the{^ <quick}( bro)wn> fox|rtl")
1201 << standard[0]
1202 << 13 << 9 << 3
1203 << 9 << 15
1204 << 3 << 15;
1205 QTest::newRow(dataTag: "{t^he <quick}( bro)wn> fox|ltr")
1206 << standard[0]
1207 << 9 << 13 << 1
1208 << 4 << 15
1209 << 0 << 9;
1210 QTest::newRow(dataTag: "{t^he <quick}( bro)wn> fox|rtl")
1211 << standard[0]
1212 << 13 << 9 << 1
1213 << 9 << 15
1214 << 0 << 15;
1215
1216 QTest::newRow(dataTag: "{<He(ll)o>, w^orld}!|ltr")
1217 << standard[2]
1218 << 2 << 4 << 8
1219 << 0 << 5
1220 << 0 << 12;
1221 QTest::newRow(dataTag: "{<He(ll)o>, w^orld}!|rtl")
1222 << standard[2]
1223 << 4 << 2 << 8
1224 << 0 << 5
1225 << 0 << 12;
1226
1227 QTest::newRow(dataTag: "!{dlro^w ,<o(ll)eH>}|ltr")
1228 << standard[3]
1229 << 9 << 11 << 5
1230 << 8 << 13
1231 << 1 << 13;
1232 QTest::newRow(dataTag: "!{dlro^w ,<o(ll)eH>}|rtl")
1233 << standard[3]
1234 << 11 << 9 << 5
1235 << 8 << 13
1236 << 1 << 13;
1237
1238 QTest::newRow(dataTag: "{<(^} sp)acey> text |ltr")
1239 << standard[4]
1240 << 0 << 3 << 0
1241 << 0 << 7
1242 << 0 << 0;
1243 QTest::newRow(dataTag: "{<( ^}sp)acey> text |ltr")
1244 << standard[4]
1245 << 0 << 3 << 1
1246 << 0 << 7
1247 << 0 << 1;
1248 QTest::newRow(dataTag: "<( {s^p)acey>} text |rtl")
1249 << standard[4]
1250 << 3 << 0 << 2
1251 << 0 << 7
1252 << 1 << 7;
1253 QTest::newRow(dataTag: "<( {^sp)acey>} text |rtl")
1254 << standard[4]
1255 << 3 << 0 << 1
1256 << 0 << 7
1257 << 1 << 7;
1258
1259 QTest::newRow(dataTag: " spacey <te(xt {^)>}|rtl")
1260 << standard[4]
1261 << 15 << 12 << 15
1262 << 10 << 15
1263 << 15 << 15;
1264 QTest::newRow(dataTag: " spacey <te(xt{^ )>}|rtl")
1265 << standard[4]
1266 << 15 << 12 << 14
1267 << 10 << 15
1268 << 14 << 15;
1269 QTest::newRow(dataTag: " spacey {<te(x^t} )>|ltr")
1270 << standard[4]
1271 << 12 << 15 << 13
1272 << 10 << 15
1273 << 10 << 14;
1274 QTest::newRow(dataTag: " spacey {<te(xt^} )>|ltr")
1275 << standard[4]
1276 << 12 << 15 << 14
1277 << 10 << 15
1278 << 10 << 14;
1279}
1280
1281void tst_qquicktextinput::moveCursorSelectionSequence()
1282{
1283 QFETCH(QString, testStr);
1284 QFETCH(int, cursorPosition);
1285 QFETCH(int, movePosition1);
1286 QFETCH(int, movePosition2);
1287 QFETCH(int, selection1Start);
1288 QFETCH(int, selection1End);
1289 QFETCH(int, selection2Start);
1290 QFETCH(int, selection2End);
1291
1292 QString componentStr = "import QtQuick 2.0\nTextInput { text: \""+ testStr +"\"; }";
1293 QQmlComponent textinputComponent(&engine);
1294 textinputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
1295 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput*>(object: textinputComponent.create());
1296 QVERIFY(textinputObject != nullptr);
1297
1298 textinputObject->setCursorPosition(cursorPosition);
1299
1300 textinputObject->moveCursorSelection(pos: movePosition1, mode: QQuickTextInput::SelectWords);
1301 QCOMPARE(textinputObject->selectedText(), testStr.mid(selection1Start, selection1End - selection1Start));
1302 QCOMPARE(textinputObject->selectionStart(), selection1Start);
1303 QCOMPARE(textinputObject->selectionEnd(), selection1End);
1304
1305 textinputObject->moveCursorSelection(pos: movePosition2, mode: QQuickTextInput::SelectWords);
1306 QCOMPARE(textinputObject->selectedText(), testStr.mid(selection2Start, selection2End - selection2Start));
1307 QCOMPARE(textinputObject->selectionStart(), selection2Start);
1308 QCOMPARE(textinputObject->selectionEnd(), selection2End);
1309
1310 delete textinputObject;
1311}
1312
1313void tst_qquicktextinput::dragMouseSelection()
1314{
1315 QString qmlfile = testFile(fileName: "mouseselection_true.qml");
1316
1317 QQuickView window(QUrl::fromLocalFile(localfile: qmlfile));
1318
1319 window.show();
1320 window.requestActivate();
1321 QVERIFY(QTest::qWaitForWindowActive(&window));
1322
1323 QVERIFY(window.rootObject() != nullptr);
1324 QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(object: window.rootObject());
1325 QVERIFY(textInputObject != nullptr);
1326
1327 // press-and-drag-and-release from x1 to x2
1328 int x1 = 10;
1329 int x2 = 70;
1330 int y = textInputObject->height()/2;
1331 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(x1,y));
1332 QTest::mouseMove(window: &window, pos: QPoint(x2, y));
1333 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(x2,y));
1334 QString str1;
1335 QTRY_VERIFY((str1 = textInputObject->selectedText()).length() > 3);
1336 QTRY_VERIFY(str1.length() > 3);
1337
1338 // press and drag the current selection.
1339 x1 = 40;
1340 x2 = 100;
1341 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(x1,y));
1342 QTest::mouseMove(window: &window, pos: QPoint(x2, y));
1343 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(x2,y));
1344 QString str2 = textInputObject->selectedText();
1345 QTRY_VERIFY(str2.length() > 3);
1346
1347 QTRY_VERIFY(str1 != str2);
1348}
1349
1350void tst_qquicktextinput::mouseSelectionMode_data()
1351{
1352 QTest::addColumn<QString>(name: "qmlfile");
1353 QTest::addColumn<bool>(name: "selectWords");
1354 QTest::addColumn<bool>(name: "focus");
1355 QTest::addColumn<bool>(name: "focusOnPress");
1356
1357 // import installed
1358 QTest::newRow(dataTag: "SelectWords focused") << testFile(fileName: "mouseselectionmode_words.qml") << true << true << true;
1359 QTest::newRow(dataTag: "SelectCharacters focused") << testFile(fileName: "mouseselectionmode_characters.qml") << false << true << true;
1360 QTest::newRow(dataTag: "default focused") << testFile(fileName: "mouseselectionmode_default.qml") << false << true << true;
1361 QTest::newRow(dataTag: "SelectWords unfocused") << testFile(fileName: "mouseselectionmode_words.qml") << true << false << false;
1362 QTest::newRow(dataTag: "SelectCharacters unfocused") << testFile(fileName: "mouseselectionmode_characters.qml") << false << false << false;
1363 QTest::newRow(dataTag: "default unfocused") << testFile(fileName: "mouseselectionmode_default.qml") << false << true << false;
1364 QTest::newRow(dataTag: "SelectWords focuss on press") << testFile(fileName: "mouseselectionmode_words.qml") << true << false << true;
1365 QTest::newRow(dataTag: "SelectCharacters focus on press") << testFile(fileName: "mouseselectionmode_characters.qml") << false << false << true;
1366 QTest::newRow(dataTag: "default focus on press") << testFile(fileName: "mouseselectionmode_default.qml") << false << false << true;
1367}
1368
1369void tst_qquicktextinput::mouseSelectionMode()
1370{
1371 QFETCH(QString, qmlfile);
1372 QFETCH(bool, selectWords);
1373 QFETCH(bool, focus);
1374 QFETCH(bool, focusOnPress);
1375
1376 QString text = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1377
1378 QQuickView window(QUrl::fromLocalFile(localfile: qmlfile));
1379
1380 window.show();
1381 window.requestActivate();
1382 QVERIFY(QTest::qWaitForWindowActive(&window));
1383
1384 QVERIFY(window.rootObject() != nullptr);
1385 QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(object: window.rootObject());
1386 QVERIFY(textInputObject != nullptr);
1387
1388 textInputObject->setFocus(focus);
1389 textInputObject->setFocusOnPress(focusOnPress);
1390
1391 // press-and-drag-and-release from x1 to x2
1392 int x1 = 10;
1393 int x2 = 70;
1394 int y = textInputObject->height()/2;
1395 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(x1,y));
1396 QTest::mouseMove(window: &window, pos: QPoint(x2,y)); // doesn't work
1397 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: QPoint(x2,y));
1398 if (selectWords) {
1399 QTRY_COMPARE(textInputObject->selectedText(), text);
1400 } else {
1401 QTRY_VERIFY(textInputObject->selectedText().length() > 3);
1402 QVERIFY(textInputObject->selectedText() != text);
1403 }
1404}
1405
1406void tst_qquicktextinput::mouseSelectionMode_accessors()
1407{
1408 QQmlComponent component(&engine);
1409 component.setData("import QtQuick 2.0\n TextInput {}", baseUrl: QUrl());
1410 QScopedPointer<QObject> object(component.create());
1411 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: object.data());
1412 QVERIFY(input);
1413
1414 QSignalSpy spy(input, &QQuickTextInput::mouseSelectionModeChanged);
1415
1416 QCOMPARE(input->mouseSelectionMode(), QQuickTextInput::SelectCharacters);
1417
1418 input->setMouseSelectionMode(QQuickTextInput::SelectWords);
1419 QCOMPARE(input->mouseSelectionMode(), QQuickTextInput::SelectWords);
1420 QCOMPARE(spy.count(), 1);
1421
1422 input->setMouseSelectionMode(QQuickTextInput::SelectWords);
1423 QCOMPARE(spy.count(), 1);
1424
1425 input->setMouseSelectionMode(QQuickTextInput::SelectCharacters);
1426 QCOMPARE(input->mouseSelectionMode(), QQuickTextInput::SelectCharacters);
1427 QCOMPARE(spy.count(), 2);
1428}
1429
1430void tst_qquicktextinput::selectByMouse()
1431{
1432 QQmlComponent component(&engine);
1433 component.setData("import QtQuick 2.0\n TextInput {}", baseUrl: QUrl());
1434 QScopedPointer<QObject> object(component.create());
1435 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: object.data());
1436 QVERIFY(input);
1437
1438 QSignalSpy spy(input, SIGNAL(selectByMouseChanged(bool)));
1439
1440 QCOMPARE(input->selectByMouse(), false);
1441
1442 input->setSelectByMouse(true);
1443 QCOMPARE(input->selectByMouse(), true);
1444 QCOMPARE(spy.count(), 1);
1445 QCOMPARE(spy.at(0).at(0).toBool(), true);
1446
1447 input->setSelectByMouse(true);
1448 QCOMPARE(spy.count(), 1);
1449
1450 input->setSelectByMouse(false);
1451 QCOMPARE(input->selectByMouse(), false);
1452 QCOMPARE(spy.count(), 2);
1453 QCOMPARE(spy.at(1).at(0).toBool(), false);
1454}
1455
1456void tst_qquicktextinput::renderType()
1457{
1458 QQmlComponent component(&engine);
1459 component.setData("import QtQuick 2.0\n TextInput {}", baseUrl: QUrl());
1460 QScopedPointer<QObject> object(component.create());
1461 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: object.data());
1462 QVERIFY(input);
1463
1464 QSignalSpy spy(input, SIGNAL(renderTypeChanged()));
1465
1466 QCOMPARE(input->renderType(), QQuickTextInput::QtRendering);
1467
1468 input->setRenderType(QQuickTextInput::NativeRendering);
1469 QCOMPARE(input->renderType(), QQuickTextInput::NativeRendering);
1470 QCOMPARE(spy.count(), 1);
1471
1472 input->setRenderType(QQuickTextInput::NativeRendering);
1473 QCOMPARE(spy.count(), 1);
1474
1475 input->setRenderType(QQuickTextInput::QtRendering);
1476 QCOMPARE(input->renderType(), QQuickTextInput::QtRendering);
1477 QCOMPARE(spy.count(), 2);
1478}
1479
1480void tst_qquicktextinput::horizontalAlignment_RightToLeft()
1481{
1482 PlatformInputContext platformInputContext;
1483 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
1484 inputMethodPrivate->testContext = &platformInputContext;
1485
1486 QQuickView window(testFileUrl(fileName: "horizontalAlignment_RightToLeft.qml"));
1487 QQuickTextInput *textInput = window.rootObject()->findChild<QQuickTextInput*>(aName: "text");
1488 QVERIFY(textInput != nullptr);
1489 window.show();
1490
1491 const QString rtlText = textInput->text();
1492
1493 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1494 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1495
1496 // implicit alignment should follow the reading direction of RTL text
1497 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1498 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1499 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1500 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1501
1502 // explicitly left aligned
1503 textInput->setHAlign(QQuickTextInput::AlignLeft);
1504 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1505 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1506 QCOMPARE(textInput->boundingRect().left(), qreal(0));
1507
1508 // explicitly right aligned
1509 textInput->setHAlign(QQuickTextInput::AlignRight);
1510 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1511 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1512 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1513 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1514
1515 // explicitly center aligned
1516 textInput->setHAlign(QQuickTextInput::AlignHCenter);
1517 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1518 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignHCenter);
1519 QVERIFY(textInput->boundingRect().left() > 0);
1520 QVERIFY(textInput->boundingRect().right() < textInput->width());
1521
1522 // reseted alignment should go back to following the text reading direction
1523 textInput->resetHAlign();
1524 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1525 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1526 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1527 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1528
1529 // mirror the text item
1530 QQuickItemPrivate::get(item: textInput)->setLayoutMirror(true);
1531
1532 // mirrored implicit alignment should continue to follow the reading direction of the text
1533 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1534 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1535 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1536 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1537
1538 // explicitly right aligned behaves as left aligned
1539 textInput->setHAlign(QQuickTextInput::AlignRight);
1540 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1541 QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignLeft);
1542 QCOMPARE(textInput->boundingRect().left(), qreal(0));
1543
1544 // mirrored explicitly left aligned behaves as right aligned
1545 textInput->setHAlign(QQuickTextInput::AlignLeft);
1546 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1547 QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignRight);
1548 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1549 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1550
1551 // disable mirroring
1552 QQuickItemPrivate::get(item: textInput)->setLayoutMirror(false);
1553 QCOMPARE(textInput->effectiveHAlign(), textInput->hAlign());
1554 textInput->resetHAlign();
1555
1556 // English text should be implicitly left aligned
1557 textInput->setText("Hello world!");
1558 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1559 QCOMPARE(textInput->boundingRect().left(), qreal(0));
1560
1561 window.requestActivate();
1562 QVERIFY(QTest::qWaitForWindowActive(&window));
1563 QVERIFY(textInput->hasActiveFocus());
1564
1565 // If there is no committed text, the preedit text should determine the alignment.
1566 textInput->setText(QString());
1567 { QInputMethodEvent ev(rtlText, QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(receiver: textInput, event: &ev); }
1568 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1569 { QInputMethodEvent ev("Hello world!", QList<QInputMethodEvent::Attribute>()); QGuiApplication::sendEvent(receiver: textInput, event: &ev); }
1570 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1571
1572 // Clear pre-edit text. TextInput should maybe do this itself on setText, but that may be
1573 // redundant as an actual input method may take care of it.
1574 { QInputMethodEvent ev; QGuiApplication::sendEvent(receiver: textInput, event: &ev); }
1575
1576 // empty text with implicit alignment follows the system locale-based
1577 // keyboard input direction from QInputMethod::inputDirection()
1578 textInput->setText("");
1579 platformInputContext.setInputDirection(Qt::LeftToRight);
1580 QCOMPARE(qApp->inputMethod()->inputDirection(), Qt::LeftToRight);
1581 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignLeft);
1582 QCOMPARE(textInput->boundingRect().left(), qreal(0));
1583
1584 QSignalSpy cursorRectangleSpy(textInput, SIGNAL(cursorRectangleChanged()));
1585 platformInputContext.setInputDirection(Qt::RightToLeft);
1586 QCOMPARE(qApp->inputMethod()->inputDirection(), Qt::RightToLeft);
1587 QCOMPARE(cursorRectangleSpy.count(), 1);
1588 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1589 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1590 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1591
1592 // set input direction while having content
1593 platformInputContext.setInputDirection(Qt::LeftToRight);
1594 textInput->setText("a");
1595 platformInputContext.setInputDirection(Qt::RightToLeft);
1596 QTest::keyClick(window: &window, key: Qt::Key_Backspace);
1597 QVERIFY(textInput->text().isEmpty());
1598 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1599 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1600 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1601
1602 // input direction changed while not having focus
1603 platformInputContext.setInputDirection(Qt::LeftToRight);
1604 textInput->setFocus(false);
1605 platformInputContext.setInputDirection(Qt::RightToLeft);
1606 textInput->setFocus(true);
1607 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1608 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1609 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1610
1611 textInput->setHAlign(QQuickTextInput::AlignRight);
1612 QCOMPARE(textInput->hAlign(), QQuickTextInput::AlignRight);
1613 QVERIFY(textInput->boundingRect().right() >= textInput->width() - 1);
1614 QVERIFY(textInput->boundingRect().right() <= textInput->width() + 1);
1615
1616 // neutral text should fall back to input direction
1617 textInput->setFocus(true);
1618 textInput->resetHAlign();
1619 textInput->setText(" ()((=<>");
1620 platformInputContext.setInputDirection(Qt::LeftToRight);
1621 QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignLeft);
1622 platformInputContext.setInputDirection(Qt::RightToLeft);
1623 QCOMPARE(textInput->effectiveHAlign(), QQuickTextInput::AlignRight);
1624
1625 // changing width keeps right aligned cursor on proper position
1626 textInput->setText("");
1627 textInput->setWidth(500);
1628 QVERIFY(textInput->positionToRectangle(0).x() > textInput->width() / 2);
1629}
1630
1631void tst_qquicktextinput::verticalAlignment()
1632{
1633 QQuickView window(testFileUrl(fileName: "horizontalAlignment.qml"));
1634 QQuickTextInput *textInput = window.rootObject()->findChild<QQuickTextInput*>(aName: "text");
1635 QVERIFY(textInput != nullptr);
1636 window.showNormal();
1637
1638 QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignTop);
1639 QVERIFY(textInput->boundingRect().bottom() < window.height() / 2);
1640 QVERIFY(textInput->cursorRectangle().bottom() < window.height() / 2);
1641 QVERIFY(textInput->positionToRectangle(0).bottom() < window.height() / 2);
1642
1643 // bottom aligned
1644 textInput->setVAlign(QQuickTextInput::AlignBottom);
1645 QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignBottom);
1646 QVERIFY(textInput->boundingRect().top() > window.height() / 2);
1647 QVERIFY(textInput->cursorRectangle().top() > window.height() / 2);
1648 QVERIFY(textInput->positionToRectangle(0).top() > window.height() / 2);
1649
1650 // explicitly center aligned
1651 textInput->setVAlign(QQuickTextInput::AlignVCenter);
1652 QCOMPARE(textInput->vAlign(), QQuickTextInput::AlignVCenter);
1653 QVERIFY(textInput->boundingRect().top() < window.height() / 2);
1654 QVERIFY(textInput->boundingRect().bottom() > window.height() / 2);
1655 QVERIFY(textInput->cursorRectangle().top() < window.height() / 2);
1656 QVERIFY(textInput->cursorRectangle().bottom() > window.height() / 2);
1657 QVERIFY(textInput->positionToRectangle(0).top() < window.height() / 2);
1658 QVERIFY(textInput->positionToRectangle(0).bottom() > window.height() / 2);
1659}
1660
1661void tst_qquicktextinput::clipRect()
1662{
1663 QQmlComponent component(&engine);
1664 component.setData("import QtQuick 2.0\n TextInput {}", baseUrl: QUrl());
1665 QScopedPointer<QObject> object(component.create());
1666 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: object.data());
1667 QVERIFY(input);
1668
1669 QCOMPARE(input->clipRect().x(), qreal(0));
1670 QCOMPARE(input->clipRect().y(), qreal(0));
1671 QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
1672 QCOMPARE(input->clipRect().height(), input->height());
1673
1674 input->setText("Hello World");
1675 QCOMPARE(input->clipRect().x(), qreal(0));
1676 QCOMPARE(input->clipRect().y(), qreal(0));
1677 QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
1678 QCOMPARE(input->clipRect().height(), input->height());
1679
1680 // clip rect shouldn't exceed the size of the item, expect for the cursor width;
1681 input->setWidth(input->width() / 2);
1682 QCOMPARE(input->clipRect().x(), qreal(0));
1683 QCOMPARE(input->clipRect().y(), qreal(0));
1684 QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
1685 QCOMPARE(input->clipRect().height(), input->height());
1686
1687 input->setHeight(input->height() * 2);
1688 QCOMPARE(input->clipRect().x(), qreal(0));
1689 QCOMPARE(input->clipRect().y(), qreal(0));
1690 QCOMPARE(input->clipRect().width(), input->width() + input->cursorRectangle().width());
1691 QCOMPARE(input->clipRect().height(), input->height());
1692
1693 QQmlComponent cursorComponent(&engine);
1694 cursorComponent.setData("import QtQuick 2.0\nRectangle { height: 20; width: 8 }", baseUrl: QUrl());
1695
1696 input->setCursorDelegate(&cursorComponent);
1697 input->setCursorVisible(true);
1698
1699 // If a cursor delegate is used it's size should determine the excess width.
1700 QCOMPARE(input->clipRect().x(), qreal(0));
1701 QCOMPARE(input->clipRect().y(), qreal(0));
1702 QCOMPARE(input->clipRect().width(), input->width() + 8);
1703 QCOMPARE(input->clipRect().height(), input->height());
1704
1705 // Alignment, auto scroll, wrapping all don't affect the clip rect.
1706 input->setAutoScroll(false);
1707 QCOMPARE(input->clipRect().x(), qreal(0));
1708 QCOMPARE(input->clipRect().y(), qreal(0));
1709 QCOMPARE(input->clipRect().width(), input->width() + 8);
1710 QCOMPARE(input->clipRect().height(), input->height());
1711
1712 input->setHAlign(QQuickTextInput::AlignRight);
1713 QCOMPARE(input->clipRect().x(), qreal(0));
1714 QCOMPARE(input->clipRect().y(), qreal(0));
1715 QCOMPARE(input->clipRect().width(), input->width() + 8);
1716 QCOMPARE(input->clipRect().height(), input->height());
1717
1718 input->setWrapMode(QQuickTextInput::Wrap);
1719 QCOMPARE(input->clipRect().x(), qreal(0));
1720 QCOMPARE(input->clipRect().y(), qreal(0));
1721 QCOMPARE(input->clipRect().width(), input->width() + 8);
1722 QCOMPARE(input->clipRect().height(), input->height());
1723
1724 input->setVAlign(QQuickTextInput::AlignBottom);
1725 QCOMPARE(input->clipRect().x(), qreal(0));
1726 QCOMPARE(input->clipRect().y(), qreal(0));
1727 QCOMPARE(input->clipRect().width(), input->width() + 8);
1728 QCOMPARE(input->clipRect().height(), input->height());
1729}
1730
1731void tst_qquicktextinput::boundingRect()
1732{
1733 QQmlComponent component(&engine);
1734 component.setData("import QtQuick 2.0\n TextInput {}", baseUrl: QUrl());
1735 QScopedPointer<QObject> object(component.create());
1736 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: object.data());
1737 QVERIFY(input);
1738
1739 QTextLayout layout;
1740 layout.setFont(input->font());
1741
1742 if (!qmlDisableDistanceField()) {
1743 QTextOption option;
1744 option.setUseDesignMetrics(true);
1745 layout.setTextOption(option);
1746 }
1747 layout.beginLayout();
1748 QTextLine line = layout.createLine();
1749 layout.endLayout();
1750
1751 QCOMPARE(input->boundingRect().x(), qreal(0));
1752 QCOMPARE(input->boundingRect().y(), qreal(0));
1753 QCOMPARE(input->boundingRect().width(), input->cursorRectangle().width());
1754 QCOMPARE(input->boundingRect().height(), line.height());
1755
1756 input->setText("Hello World");
1757
1758 layout.setText(input->text());
1759 layout.beginLayout();
1760 line = layout.createLine();
1761 layout.endLayout();
1762
1763 QCOMPARE(input->boundingRect().x(), qreal(0));
1764 QCOMPARE(input->boundingRect().y(), qreal(0));
1765 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
1766 QCOMPARE(input->boundingRect().height(), line.height());
1767
1768 // the size of the bounding rect shouldn't be bounded by the size of item.
1769 input->setWidth(input->width() / 2);
1770 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
1771 QCOMPARE(input->boundingRect().y(), qreal(0));
1772 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
1773 QCOMPARE(input->boundingRect().height(), line.height());
1774
1775 input->setHeight(input->height() * 2);
1776 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
1777 QCOMPARE(input->boundingRect().y(), qreal(0));
1778 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
1779 QCOMPARE(input->boundingRect().height(), line.height());
1780
1781 QQmlComponent cursorComponent(&engine);
1782 cursorComponent.setData("import QtQuick 2.0\nRectangle { height: 20; width: 8 }", baseUrl: QUrl());
1783
1784 input->setCursorDelegate(&cursorComponent);
1785 input->setCursorVisible(true);
1786
1787 // Don't include the size of a cursor delegate as it has its own bounding rect.
1788 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
1789 QCOMPARE(input->boundingRect().y(), qreal(0));
1790 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
1791 QCOMPARE(input->boundingRect().height(), line.height());
1792
1793 // Bounding rect left aligned when auto scroll is disabled;
1794 input->setAutoScroll(false);
1795 QCOMPARE(input->boundingRect().x(), qreal(0));
1796 QCOMPARE(input->boundingRect().y(), qreal(0));
1797 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
1798 QCOMPARE(input->boundingRect().height(), line.height());
1799
1800 input->setHAlign(QQuickTextInput::AlignRight);
1801 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
1802 QCOMPARE(input->boundingRect().y(), qreal(0));
1803 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth());
1804 QCOMPARE(input->boundingRect().height(), line.height());
1805
1806 input->setWrapMode(QQuickTextInput::Wrap);
1807 QCOMPARE(input->boundingRect().right(), input->width());
1808 QCOMPARE(input->boundingRect().y(), qreal(0));
1809 QVERIFY(input->boundingRect().width() < line.naturalTextWidth());
1810 QVERIFY(input->boundingRect().height() > line.height());
1811
1812 input->setVAlign(QQuickTextInput::AlignBottom);
1813 QCOMPARE(input->boundingRect().right(), input->width());
1814 QCOMPARE(input->boundingRect().bottom(), input->height());
1815 QVERIFY(input->boundingRect().width() < line.naturalTextWidth());
1816 QVERIFY(input->boundingRect().height() > line.height());
1817}
1818
1819void tst_qquicktextinput::positionAt()
1820{
1821 QQuickView window(testFileUrl(fileName: "positionAt.qml"));
1822 QVERIFY(window.rootObject() != nullptr);
1823 window.show();
1824 window.requestActivate();
1825 QVERIFY(QTest::qWaitForWindowActive(&window));
1826
1827 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(object: window.rootObject());
1828 QVERIFY(textinputObject != nullptr);
1829
1830 // Check autoscrolled...
1831
1832 int pos = evaluate<int>(scope: textinputObject, expression: QString("positionAt(%1)").arg(a: textinputObject->width()/2));
1833
1834 QTextLayout layout(textinputObject->text());
1835 layout.setFont(textinputObject->font());
1836
1837 if (!qmlDisableDistanceField()) {
1838 QTextOption option;
1839 option.setUseDesignMetrics(true);
1840 layout.setTextOption(option);
1841 }
1842 layout.beginLayout();
1843 QTextLine line = layout.createLine();
1844 layout.endLayout();
1845
1846 int textLeftWidthBegin = floor(x: line.cursorToX(cursorPos: pos - 1));
1847 int textLeftWidthEnd = ceil(x: line.cursorToX(cursorPos: pos + 1));
1848 int textWidth = floor(x: line.horizontalAdvance());
1849
1850 QVERIFY(textLeftWidthBegin <= textWidth - textinputObject->width() / 2);
1851 QVERIFY(textLeftWidthEnd >= textWidth - textinputObject->width() / 2);
1852
1853 int x = textinputObject->positionToRectangle(pos: pos + 1).x() - 1;
1854 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1);
1855 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos);
1856
1857 // Check without autoscroll...
1858 textinputObject->setAutoScroll(false);
1859 pos = evaluate<int>(scope: textinputObject, expression: QString("positionAt(%1)").arg(a: textinputObject->width() / 2));
1860
1861 textLeftWidthBegin = floor(x: line.cursorToX(cursorPos: pos - 1));
1862 textLeftWidthEnd = ceil(x: line.cursorToX(cursorPos: pos + 1));
1863
1864 QVERIFY(textLeftWidthBegin <= textinputObject->width() / 2);
1865 QVERIFY(textLeftWidthEnd >= textinputObject->width() / 2);
1866
1867 x = textinputObject->positionToRectangle(pos: pos + 1).x() - 1;
1868 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorBetweenCharacters)").arg(x)), pos + 1);
1869 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, 0, TextInput.CursorOnCharacter)").arg(x)), pos);
1870
1871 const qreal x0 = textinputObject->positionToRectangle(pos).x();
1872 const qreal x1 = textinputObject->positionToRectangle(pos: pos + 1).x();
1873
1874 QString preeditText = textinputObject->text().mid(position: 0, n: pos);
1875 textinputObject->setText(textinputObject->text().mid(position: pos));
1876 textinputObject->setCursorPosition(0);
1877
1878 { QInputMethodEvent inputEvent(preeditText, QList<QInputMethodEvent::Attribute>());
1879 QVERIFY(qGuiApp->focusObject());
1880 QGuiApplication::sendEvent(receiver: textinputObject, event: &inputEvent); }
1881
1882 // Check all points within the preedit text return the same position.
1883 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(0)), 0);
1884 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x0 / 2)), 0);
1885 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x0)), 0);
1886
1887 // Verify positioning returns to normal after the preedit text.
1888 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1)").arg(x1)), 1);
1889 QCOMPARE(textinputObject->positionToRectangle(1).x(), x1);
1890
1891 { QInputMethodEvent inputEvent;
1892 QVERIFY(qGuiApp->focusObject());
1893 QGuiApplication::sendEvent(receiver: textinputObject, event: &inputEvent); }
1894
1895 // With wrapping.
1896 textinputObject->setWrapMode(QQuickTextInput::WrapAnywhere);
1897
1898 const qreal y0 = line.height() / 2;
1899 const qreal y1 = line.height() * 3 / 2;
1900
1901 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x0).arg(y0)), pos);
1902 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y0)), pos + 1);
1903
1904 int newLinePos = evaluate<int>(scope: textinputObject, expression: QString("positionAt(%1, %2)").arg(a: x0).arg(a: y1));
1905 QVERIFY(newLinePos > pos);
1906 QCOMPARE(evaluate<int>(textinputObject, QString("positionAt(%1, %2)").arg(x1).arg(y1)), newLinePos + 1);
1907}
1908
1909void tst_qquicktextinput::maxLength()
1910{
1911 QQuickView window(testFileUrl(fileName: "maxLength.qml"));
1912 QVERIFY(window.rootObject() != nullptr);
1913 window.show();
1914 window.requestActivate();
1915 QVERIFY(QTest::qWaitForWindowActive(&window));
1916
1917 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(object: window.rootObject());
1918 QVERIFY(textinputObject != nullptr);
1919 QVERIFY(textinputObject->text().isEmpty());
1920 QCOMPARE(textinputObject->maxLength(), 10);
1921 foreach (const QString &str, standard) {
1922 QVERIFY(textinputObject->text().length() <= 10);
1923 textinputObject->setText(str);
1924 QVERIFY(textinputObject->text().length() <= 10);
1925 }
1926
1927 textinputObject->setText("");
1928 QTRY_VERIFY(textinputObject->hasActiveFocus());
1929 for (int i=0; i<20; i++) {
1930 QTRY_COMPARE(textinputObject->text().length(), qMin(i,10));
1931 //simulateKey(&window, Qt::Key_A);
1932 QTest::keyPress(window: &window, key: Qt::Key_A);
1933 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
1934 }
1935}
1936
1937void tst_qquicktextinput::masks()
1938{
1939 //Not a comprehensive test of the possible masks, that's done elsewhere (QLineEdit)
1940 //QString componentStr = "import QtQuick 2.0\nTextInput { inputMask: 'HHHHhhhh'; }";
1941 QQuickView window(testFileUrl(fileName: "masks.qml"));
1942 window.show();
1943 window.requestActivate();
1944 QVERIFY(window.rootObject() != nullptr);
1945 QQuickTextInput *textinputObject = qobject_cast<QQuickTextInput *>(object: window.rootObject());
1946 QVERIFY(textinputObject != nullptr);
1947 QTRY_VERIFY(textinputObject->hasActiveFocus());
1948 QCOMPARE(textinputObject->text().length(), 0);
1949 QCOMPARE(textinputObject->inputMask(), QString("HHHHhhhh; "));
1950 QCOMPARE(textinputObject->length(), 8);
1951 for (int i=0; i<10; i++) {
1952 QTRY_COMPARE(qMin(i,8), textinputObject->text().length());
1953 QCOMPARE(textinputObject->length(), 8);
1954 QCOMPARE(textinputObject->getText(0, qMin(i, 8)), QString(qMin(i, 8), 'a'));
1955 QCOMPARE(textinputObject->getText(qMin(i, 8), 8), QString(8 - qMin(i, 8), ' '));
1956 QCOMPARE(i>=4, textinputObject->hasAcceptableInput());
1957 //simulateKey(&window, Qt::Key_A);
1958 QTest::keyPress(window: &window, key: Qt::Key_A);
1959 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
1960 }
1961}
1962
1963void tst_qquicktextinput::validators()
1964{
1965 // Note that this test assumes that the validators are working properly
1966 // so you may need to run their tests first. All validators are checked
1967 // here to ensure that their exposure to QML is working.
1968
1969 QLocale::setDefault(QLocale(QStringLiteral("C")));
1970
1971 QQuickView window(testFileUrl(fileName: "validators.qml"));
1972 window.show();
1973 window.requestActivate();
1974 QVERIFY(QTest::qWaitForWindowActive(&window));
1975
1976 QVERIFY(window.rootObject() != nullptr);
1977
1978 QLocale defaultLocale;
1979 QLocale enLocale("en");
1980 QLocale deLocale("de_DE");
1981
1982 QQuickTextInput *intInput = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "intInput")));
1983 QVERIFY(intInput);
1984 QSignalSpy intSpy(intInput, SIGNAL(acceptableInputChanged()));
1985
1986 QQuickIntValidator *intValidator = qobject_cast<QQuickIntValidator *>(object: intInput->validator());
1987 QVERIFY(intValidator);
1988 QCOMPARE(intValidator->localeName(), defaultLocale.name());
1989 QCOMPARE(intInput->validator()->locale(), defaultLocale);
1990 intValidator->setLocaleName(enLocale.name());
1991 QCOMPARE(intValidator->localeName(), enLocale.name());
1992 QCOMPARE(intInput->validator()->locale(), enLocale);
1993 intValidator->resetLocaleName();
1994 QCOMPARE(intValidator->localeName(), defaultLocale.name());
1995 QCOMPARE(intInput->validator()->locale(), defaultLocale);
1996
1997 intInput->setFocus(true);
1998 QTRY_VERIFY(intInput->hasActiveFocus());
1999 QCOMPARE(intInput->hasAcceptableInput(), false);
2000 QCOMPARE(intInput->property("acceptable").toBool(), false);
2001 QTest::keyPress(window: &window, key: Qt::Key_1);
2002 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2003 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
2004 QCOMPARE(intInput->hasAcceptableInput(), false);
2005 QCOMPARE(intInput->property("acceptable").toBool(), false);
2006 QCOMPARE(intSpy.count(), 0);
2007 QCOMPARE(intInput->hasAcceptableInput(), false);
2008 QCOMPARE(intInput->property("acceptable").toBool(), false);
2009 QCOMPARE(intSpy.count(), 0);
2010 QTest::keyPress(window: &window, key: Qt::Key_Period);
2011 QTest::keyRelease(window: &window, key: Qt::Key_Period, modifier: Qt::NoModifier);
2012 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
2013 QCOMPARE(intInput->hasAcceptableInput(), false);
2014 QTest::keyPress(window: &window, key: Qt::Key_Comma);
2015 QTest::keyRelease(window: &window, key: Qt::Key_Comma, modifier: Qt::NoModifier);
2016 QTRY_COMPARE(intInput->text(), QLatin1String("1,"));
2017 QCOMPARE(intInput->hasAcceptableInput(), false);
2018 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2019 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2020 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
2021 QCOMPARE(intInput->hasAcceptableInput(), false);
2022 intValidator->setLocaleName(deLocale.name());
2023 QTest::keyPress(window: &window, key: Qt::Key_Period);
2024 QTest::keyRelease(window: &window, key: Qt::Key_Period, modifier: Qt::NoModifier);
2025 QTRY_COMPARE(intInput->text(), QLatin1String("1."));
2026 QCOMPARE(intInput->hasAcceptableInput(), false);
2027 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2028 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2029 QTRY_COMPARE(intInput->text(), QLatin1String("1"));
2030 QCOMPARE(intInput->hasAcceptableInput(), false);
2031 intValidator->resetLocaleName();
2032 QTest::keyPress(window: &window, key: Qt::Key_1);
2033 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2034 QCOMPARE(intInput->text(), QLatin1String("11"));
2035 QCOMPARE(intInput->hasAcceptableInput(), true);
2036 QCOMPARE(intInput->property("acceptable").toBool(), true);
2037 QCOMPARE(intSpy.count(), 1);
2038 QTest::keyPress(window: &window, key: Qt::Key_0);
2039 QTest::keyRelease(window: &window, key: Qt::Key_0, modifier: Qt::NoModifier);
2040 QCOMPARE(intInput->text(), QLatin1String("11"));
2041 QCOMPARE(intInput->hasAcceptableInput(), true);
2042 QCOMPARE(intInput->property("acceptable").toBool(), true);
2043 QCOMPARE(intSpy.count(), 1);
2044
2045 QQuickTextInput *dblInput = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "dblInput")));
2046 QVERIFY(dblInput);
2047 QSignalSpy dblSpy(dblInput, SIGNAL(acceptableInputChanged()));
2048
2049 QQuickDoubleValidator *dblValidator = qobject_cast<QQuickDoubleValidator *>(object: dblInput->validator());
2050 QVERIFY(dblValidator);
2051 QCOMPARE(dblValidator->localeName(), defaultLocale.name());
2052 QCOMPARE(dblInput->validator()->locale(), defaultLocale);
2053 dblValidator->setLocaleName(enLocale.name());
2054 QCOMPARE(dblValidator->localeName(), enLocale.name());
2055 QCOMPARE(dblInput->validator()->locale(), enLocale);
2056 dblValidator->resetLocaleName();
2057 QCOMPARE(dblValidator->localeName(), defaultLocale.name());
2058 QCOMPARE(dblInput->validator()->locale(), defaultLocale);
2059
2060 dblInput->setFocus(true);
2061 QVERIFY(dblInput->hasActiveFocus());
2062 QCOMPARE(dblInput->hasAcceptableInput(), false);
2063 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2064 QTest::keyPress(window: &window, key: Qt::Key_1);
2065 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2066 QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
2067 QCOMPARE(dblInput->hasAcceptableInput(), false);
2068 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2069 QCOMPARE(dblSpy.count(), 0);
2070 QTest::keyPress(window: &window, key: Qt::Key_2);
2071 QTest::keyRelease(window: &window, key: Qt::Key_2, modifier: Qt::NoModifier);
2072 QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
2073 QCOMPARE(dblInput->hasAcceptableInput(), true);
2074 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2075 QCOMPARE(dblSpy.count(), 1);
2076 QTest::keyPress(window: &window, key: Qt::Key_Comma);
2077 QTest::keyRelease(window: &window, key: Qt::Key_Comma, modifier: Qt::NoModifier);
2078 QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
2079 int extraSignals = 2;
2080 if (dblInput->hasAcceptableInput()) {
2081 // TODO: old behavior of QDoubleValidator - remove when merged from qtbase
2082 QTest::keyPress(window: &window, key: Qt::Key_1);
2083 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2084 QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
2085 QCOMPARE(dblInput->hasAcceptableInput(), true);
2086 extraSignals = 0;
2087 }
2088 dblValidator->setLocaleName(deLocale.name());
2089 QCOMPARE(dblInput->hasAcceptableInput(), true);
2090 QTest::keyPress(window: &window, key: Qt::Key_1);
2091 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2092 QTRY_COMPARE(dblInput->text(), QLatin1String("12,1"));
2093 QCOMPARE(dblInput->hasAcceptableInput(), true);
2094 QTest::keyPress(window: &window, key: Qt::Key_1);
2095 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2096 QTRY_COMPARE(dblInput->text(), QLatin1String("12,11"));
2097 QCOMPARE(dblInput->hasAcceptableInput(), true);
2098 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2099 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2100 QTRY_COMPARE(dblInput->text(), QLatin1String("12,1"));
2101 QCOMPARE(dblInput->hasAcceptableInput(), true);
2102 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2103 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2104 QTRY_COMPARE(dblInput->text(), QLatin1String("12,"));
2105 QCOMPARE(dblInput->hasAcceptableInput(), true);
2106 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2107 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2108 QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
2109 QCOMPARE(dblInput->hasAcceptableInput(), true);
2110 dblValidator->resetLocaleName();
2111 QTest::keyPress(window: &window, key: Qt::Key_Period);
2112 QTest::keyRelease(window: &window, key: Qt::Key_Period, modifier: Qt::NoModifier);
2113 QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
2114 QCOMPARE(dblInput->hasAcceptableInput(), true);
2115 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2116 QCOMPARE(dblSpy.count(), 1 + extraSignals);
2117 QTest::keyPress(window: &window, key: Qt::Key_1);
2118 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2119 QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
2120 QCOMPARE(dblInput->hasAcceptableInput(), true);
2121 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2122 QCOMPARE(dblSpy.count(), 1 + extraSignals);
2123 QTest::keyPress(window: &window, key: Qt::Key_1);
2124 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2125 QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
2126 QCOMPARE(dblInput->hasAcceptableInput(), true);
2127 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2128 QCOMPARE(dblSpy.count(), 1 + extraSignals);
2129 QTest::keyPress(window: &window, key: Qt::Key_1);
2130 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2131 QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
2132 QCOMPARE(dblInput->hasAcceptableInput(), true);
2133 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2134 QCOMPARE(dblSpy.count(), 1 + extraSignals);
2135
2136 // Ensure the validator doesn't prevent characters being removed.
2137 dblInput->setValidator(intInput->validator());
2138 QCOMPARE(dblInput->text(), QLatin1String("12.11"));
2139 QCOMPARE(dblInput->hasAcceptableInput(), false);
2140 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2141 QCOMPARE(dblSpy.count(), 2 + extraSignals);
2142 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2143 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2144 QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
2145 QCOMPARE(dblInput->hasAcceptableInput(), false);
2146 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2147 QCOMPARE(dblSpy.count(), 2 + extraSignals);
2148 // Once unacceptable input is in anything goes until it reaches an acceptable state again.
2149 QTest::keyPress(window: &window, key: Qt::Key_1);
2150 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2151 QTRY_COMPARE(dblInput->text(), QLatin1String("12.11"));
2152 QCOMPARE(dblInput->hasAcceptableInput(), false);
2153 QCOMPARE(dblSpy.count(), 2 + extraSignals);
2154 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2155 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2156 QTRY_COMPARE(dblInput->text(), QLatin1String("12.1"));
2157 QCOMPARE(dblInput->hasAcceptableInput(), false);
2158 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2159 QCOMPARE(dblSpy.count(), 2 + extraSignals);
2160 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2161 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2162 QTRY_COMPARE(dblInput->text(), QLatin1String("12."));
2163 QCOMPARE(dblInput->hasAcceptableInput(), false);
2164 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2165 QCOMPARE(dblSpy.count(), 2 + extraSignals);
2166 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2167 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2168 QTRY_COMPARE(dblInput->text(), QLatin1String("12"));
2169 QCOMPARE(dblInput->hasAcceptableInput(), false);
2170 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2171 QCOMPARE(dblSpy.count(), 2 + extraSignals);
2172 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
2173 QTest::keyRelease(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
2174 QTRY_COMPARE(dblInput->text(), QLatin1String("1"));
2175 QCOMPARE(dblInput->hasAcceptableInput(), false);
2176 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2177 QCOMPARE(dblSpy.count(), 2 + extraSignals);
2178 QTest::keyPress(window: &window, key: Qt::Key_1);
2179 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2180 QCOMPARE(dblInput->text(), QLatin1String("11"));
2181 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2182 QCOMPARE(dblInput->hasAcceptableInput(), true);
2183 QCOMPARE(dblSpy.count(), 3 + extraSignals);
2184
2185 // Changing the validator properties will re-evaluate whether the input is acceptable.
2186 intValidator->setTop(10);
2187 QCOMPARE(dblInput->property("acceptable").toBool(), false);
2188 QCOMPARE(dblInput->hasAcceptableInput(), false);
2189 QCOMPARE(dblSpy.count(), 4 + extraSignals);
2190 intValidator->setTop(12);
2191 QCOMPARE(dblInput->property("acceptable").toBool(), true);
2192 QCOMPARE(dblInput->hasAcceptableInput(), true);
2193 QCOMPARE(dblSpy.count(), 5 + extraSignals);
2194
2195 QQuickTextInput *strInput = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "strInput")));
2196 QVERIFY(strInput);
2197 QSignalSpy strSpy(strInput, SIGNAL(acceptableInputChanged()));
2198 strInput->setFocus(true);
2199 QVERIFY(strInput->hasActiveFocus());
2200 QCOMPARE(strInput->hasAcceptableInput(), false);
2201 QCOMPARE(strInput->property("acceptable").toBool(), false);
2202 QTest::keyPress(window: &window, key: Qt::Key_1);
2203 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2204 QTRY_COMPARE(strInput->text(), QLatin1String(""));
2205 QCOMPARE(strInput->hasAcceptableInput(), false);
2206 QCOMPARE(strInput->property("acceptable").toBool(), false);
2207 QCOMPARE(strSpy.count(), 0);
2208 QTest::keyPress(window: &window, key: Qt::Key_A);
2209 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2210 QTRY_COMPARE(strInput->text(), QLatin1String("a"));
2211 QCOMPARE(strInput->hasAcceptableInput(), false);
2212 QCOMPARE(strInput->property("acceptable").toBool(), false);
2213 QCOMPARE(strSpy.count(), 0);
2214 QTest::keyPress(window: &window, key: Qt::Key_A);
2215 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2216 QTRY_COMPARE(strInput->text(), QLatin1String("aa"));
2217 QCOMPARE(strInput->hasAcceptableInput(), true);
2218 QCOMPARE(strInput->property("acceptable").toBool(), true);
2219 QCOMPARE(strSpy.count(), 1);
2220 QTest::keyPress(window: &window, key: Qt::Key_A);
2221 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2222 QTRY_COMPARE(strInput->text(), QLatin1String("aaa"));
2223 QCOMPARE(strInput->hasAcceptableInput(), true);
2224 QCOMPARE(strInput->property("acceptable").toBool(), true);
2225 QCOMPARE(strSpy.count(), 1);
2226 QTest::keyPress(window: &window, key: Qt::Key_A);
2227 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2228 QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
2229 QCOMPARE(strInput->hasAcceptableInput(), true);
2230 QCOMPARE(strInput->property("acceptable").toBool(), true);
2231 QCOMPARE(strSpy.count(), 1);
2232 QTest::keyPress(window: &window, key: Qt::Key_A);
2233 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2234 QTRY_COMPARE(strInput->text(), QLatin1String("aaaa"));
2235 QCOMPARE(strInput->hasAcceptableInput(), true);
2236 QCOMPARE(strInput->property("acceptable").toBool(), true);
2237 QCOMPARE(strSpy.count(), 1);
2238
2239 QQuickTextInput *unvalidatedInput = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "unvalidatedInput")));
2240 QVERIFY(unvalidatedInput);
2241 QSignalSpy unvalidatedSpy(unvalidatedInput, SIGNAL(acceptableInputChanged()));
2242 unvalidatedInput->setFocus(true);
2243 QVERIFY(unvalidatedInput->hasActiveFocus());
2244 QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
2245 QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
2246 QTest::keyPress(window: &window, key: Qt::Key_1);
2247 QTest::keyRelease(window: &window, key: Qt::Key_1, modifier: Qt::NoModifier);
2248 QTRY_COMPARE(unvalidatedInput->text(), QLatin1String("1"));
2249 QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
2250 QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
2251 QCOMPARE(unvalidatedSpy.count(), 0);
2252 QTest::keyPress(window: &window, key: Qt::Key_A);
2253 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2254 QTRY_COMPARE(unvalidatedInput->text(), QLatin1String("1a"));
2255 QCOMPARE(unvalidatedInput->hasAcceptableInput(), true);
2256 QCOMPARE(unvalidatedInput->property("acceptable").toBool(), true);
2257 QCOMPARE(unvalidatedSpy.count(), 0);
2258}
2259
2260void tst_qquicktextinput::inputMethods()
2261{
2262 QQuickView window(testFileUrl(fileName: "inputmethods.qml"));
2263 window.show();
2264 window.requestActivate();
2265 QVERIFY(QTest::qWaitForWindowActive(&window));
2266
2267 // test input method hints
2268 QVERIFY(window.rootObject() != nullptr);
2269 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: window.rootObject());
2270 QVERIFY(input != nullptr);
2271 QVERIFY(input->inputMethodHints() & Qt::ImhNoPredictiveText);
2272 QSignalSpy inputMethodHintSpy(input, SIGNAL(inputMethodHintsChanged()));
2273 input->setInputMethodHints(Qt::ImhUppercaseOnly);
2274 QVERIFY(input->inputMethodHints() & Qt::ImhUppercaseOnly);
2275 QCOMPARE(inputMethodHintSpy.count(), 1);
2276 input->setInputMethodHints(Qt::ImhUppercaseOnly);
2277 QCOMPARE(inputMethodHintSpy.count(), 1);
2278
2279 // default value
2280 QQuickTextInput plainInput;
2281 QCOMPARE(plainInput.inputMethodHints(), Qt::ImhNone);
2282
2283 input->setFocus(true);
2284 QVERIFY(input->hasActiveFocus());
2285 // test that input method event is committed
2286 QInputMethodEvent event;
2287 event.setCommitString( commitString: "My ", replaceFrom: -12, replaceLength: 0);
2288 QTRY_COMPARE(qGuiApp->focusObject(), input);
2289 QGuiApplication::sendEvent(receiver: input, event: &event);
2290 QCOMPARE(input->text(), QString("My Hello world!"));
2291 QCOMPARE(input->displayText(), QString("My Hello world!"));
2292
2293 input->setCursorPosition(2);
2294 event.setCommitString(commitString: "Your", replaceFrom: -2, replaceLength: 2);
2295 QGuiApplication::sendEvent(receiver: input, event: &event);
2296 QCOMPARE(input->text(), QString("Your Hello world!"));
2297 QCOMPARE(input->displayText(), QString("Your Hello world!"));
2298 QCOMPARE(input->cursorPosition(), 4);
2299
2300 input->setCursorPosition(7);
2301 event.setCommitString(commitString: "Goodbye", replaceFrom: -2, replaceLength: 5);
2302 QGuiApplication::sendEvent(receiver: input, event: &event);
2303 QCOMPARE(input->text(), QString("Your Goodbye world!"));
2304 QCOMPARE(input->displayText(), QString("Your Goodbye world!"));
2305 QCOMPARE(input->cursorPosition(), 12);
2306
2307 input->setCursorPosition(8);
2308 event.setCommitString(commitString: "Our", replaceFrom: -8, replaceLength: 4);
2309 QGuiApplication::sendEvent(receiver: input, event: &event);
2310 QCOMPARE(input->text(), QString("Our Goodbye world!"));
2311 QCOMPARE(input->displayText(), QString("Our Goodbye world!"));
2312 QCOMPARE(input->cursorPosition(), 3);
2313
2314 input->setCursorPosition(7);
2315 QInputMethodEvent preeditEvent("PREEDIT", QList<QInputMethodEvent::Attribute>());
2316 QGuiApplication::sendEvent(receiver: input, event: &preeditEvent);
2317 QCOMPARE(input->text(), QString("Our Goodbye world!"));
2318 QCOMPARE(input->displayText(), QString("Our GooPREEDITdbye world!"));
2319 QCOMPARE(input->preeditText(), QString("PREEDIT"));
2320
2321 QInputMethodEvent preeditEvent2("PREEDIT2", QList<QInputMethodEvent::Attribute>());
2322 QGuiApplication::sendEvent(receiver: input, event: &preeditEvent2);
2323 QCOMPARE(input->text(), QString("Our Goodbye world!"));
2324 QCOMPARE(input->displayText(), QString("Our GooPREEDIT2dbye world!"));
2325 QCOMPARE(input->preeditText(), QString("PREEDIT2"));
2326
2327 QInputMethodEvent preeditEvent3("", QList<QInputMethodEvent::Attribute>());
2328 QGuiApplication::sendEvent(receiver: input, event: &preeditEvent3);
2329 QCOMPARE(input->text(), QString("Our Goodbye world!"));
2330 QCOMPARE(input->displayText(), QString("Our Goodbye world!"));
2331 QCOMPARE(input->preeditText(), QString(""));
2332
2333 // input should reset selection even if replacement parameters are out of bounds
2334 input->setText("text");
2335 input->setCursorPosition(0);
2336 input->moveCursorSelection(pos: input->text().length());
2337 event.setCommitString(commitString: "replacement", replaceFrom: -input->text().length(), replaceLength: input->text().length());
2338 QGuiApplication::sendEvent(receiver: input, event: &event);
2339 QCOMPARE(input->selectionStart(), input->selectionEnd());
2340 QCOMPARE(input->text(), QString("replacement"));
2341 QCOMPARE(input->displayText(), QString("replacement"));
2342
2343 QInputMethodQueryEvent enabledQueryEvent(Qt::ImEnabled);
2344 QGuiApplication::sendEvent(receiver: input, event: &enabledQueryEvent);
2345 QCOMPARE(enabledQueryEvent.value(Qt::ImEnabled).toBool(), true);
2346
2347 input->setReadOnly(true);
2348 QGuiApplication::sendEvent(receiver: input, event: &enabledQueryEvent);
2349 QCOMPARE(enabledQueryEvent.value(Qt::ImEnabled).toBool(), false);
2350}
2351
2352void tst_qquicktextinput::signal_accepted()
2353{
2354 QQuickView window(testFileUrl(fileName: "signal_accepted.qml"));
2355 window.show();
2356 window.requestActivate();
2357 QVERIFY(QTest::qWaitForWindowActive(&window));
2358
2359 QVERIFY(window.rootObject() != nullptr);
2360
2361 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "input")));
2362 QVERIFY(input);
2363 QSignalSpy acceptedSpy(input, SIGNAL(accepted()));
2364 QSignalSpy inputSpy(input, SIGNAL(acceptableInputChanged()));
2365
2366 input->setFocus(true);
2367 QTRY_VERIFY(input->hasActiveFocus());
2368 QCOMPARE(input->hasAcceptableInput(), false);
2369 QCOMPARE(input->property("acceptable").toBool(), false);
2370
2371 QTest::keyPress(window: &window, key: Qt::Key_A);
2372 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2373 QTRY_COMPARE(input->text(), QLatin1String("a"));
2374 QCOMPARE(input->hasAcceptableInput(), false);
2375 QCOMPARE(input->property("acceptable").toBool(), false);
2376 QTRY_COMPARE(inputSpy.count(), 0);
2377
2378 QTest::keyPress(window: &window, key: Qt::Key_Enter);
2379 QTest::keyRelease(window: &window, key: Qt::Key_Enter, modifier: Qt::NoModifier);
2380 QTRY_COMPARE(acceptedSpy.count(), 0);
2381
2382 QTest::keyPress(window: &window, key: Qt::Key_A);
2383 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2384 QTRY_COMPARE(input->text(), QLatin1String("aa"));
2385 QCOMPARE(input->hasAcceptableInput(), true);
2386 QCOMPARE(input->property("acceptable").toBool(), true);
2387 QTRY_COMPARE(inputSpy.count(), 1);
2388
2389 QTest::keyPress(window: &window, key: Qt::Key_Enter);
2390 QTest::keyRelease(window: &window, key: Qt::Key_Enter, modifier: Qt::NoModifier);
2391 QTRY_COMPARE(acceptedSpy.count(), 1);
2392}
2393
2394void tst_qquicktextinput::signal_editingfinished()
2395{
2396 QQuickView window(testFileUrl(fileName: "signal_editingfinished.qml"));
2397 window.show();
2398 window.requestActivate();
2399 QVERIFY(QTest::qWaitForWindowActive(&window));
2400
2401 QVERIFY(window.rootObject() != nullptr);
2402
2403 QQuickTextInput *input1 = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "input1")));
2404 QVERIFY(input1);
2405 QQuickTextInput *input2 = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "input2")));
2406 QVERIFY(input2);
2407 QSignalSpy editingFinished1Spy(input1, SIGNAL(editingFinished()));
2408 QSignalSpy input1Spy(input1, SIGNAL(acceptableInputChanged()));
2409
2410 input1->setFocus(true);
2411 QTRY_VERIFY(input1->hasActiveFocus());
2412 QTRY_VERIFY(!input2->hasActiveFocus());
2413
2414 QTest::keyPress(window: &window, key: Qt::Key_A);
2415 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2416 QTRY_COMPARE(input1->text(), QLatin1String("a"));
2417 QCOMPARE(input1->hasAcceptableInput(), false);
2418 QCOMPARE(input1->property("acceptable").toBool(), false);
2419 QTRY_COMPARE(input1Spy.count(), 0);
2420
2421 QTest::keyPress(window: &window, key: Qt::Key_Enter);
2422 QTest::keyRelease(window: &window, key: Qt::Key_Enter, modifier: Qt::NoModifier);
2423 QTRY_COMPARE(editingFinished1Spy.count(), 0);
2424
2425 QTest::keyPress(window: &window, key: Qt::Key_A);
2426 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2427 QTRY_COMPARE(input1->text(), QLatin1String("aa"));
2428 QCOMPARE(input1->hasAcceptableInput(), true);
2429 QCOMPARE(input1->property("acceptable").toBool(), true);
2430 QTRY_COMPARE(input1Spy.count(), 1);
2431
2432 QTest::keyPress(window: &window, key: Qt::Key_Enter);
2433 QTest::keyRelease(window: &window, key: Qt::Key_Enter, modifier: Qt::NoModifier);
2434 QTRY_COMPARE(editingFinished1Spy.count(), 1);
2435 QTRY_COMPARE(input1Spy.count(), 1);
2436
2437 QSignalSpy editingFinished2Spy(input2, SIGNAL(editingFinished()));
2438 QSignalSpy input2Spy(input2, SIGNAL(acceptableInputChanged()));
2439
2440 input2->setFocus(true);
2441 QTRY_VERIFY(!input1->hasActiveFocus());
2442 QTRY_VERIFY(input2->hasActiveFocus());
2443
2444 QTest::keyPress(window: &window, key: Qt::Key_A);
2445 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2446 QTRY_COMPARE(input2->text(), QLatin1String("a"));
2447 QCOMPARE(input2->hasAcceptableInput(), false);
2448 QCOMPARE(input2->property("acceptable").toBool(), false);
2449 QTRY_COMPARE(input2Spy.count(), 0);
2450
2451 QTest::keyPress(window: &window, key: Qt::Key_A);
2452 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier);
2453 QTRY_COMPARE(input2->text(), QLatin1String("aa"));
2454 QCOMPARE(input2->hasAcceptableInput(), true);
2455 QCOMPARE(input2->property("acceptable").toBool(), true);
2456 QTRY_COMPARE(input2Spy.count(), 1);
2457
2458 input1->setFocus(true);
2459 QTRY_VERIFY(input1->hasActiveFocus());
2460 QTRY_VERIFY(!input2->hasActiveFocus());
2461 QTRY_COMPARE(editingFinished2Spy.count(), 1);
2462}
2463
2464void tst_qquicktextinput::signal_textEdited()
2465{
2466 QQuickWindow window;
2467 window.show();
2468 window.requestActivate();
2469 QVERIFY(QTest::qWaitForWindowActive(&window));
2470
2471 QQuickTextInput *input = new QQuickTextInput(window.contentItem());
2472 QVERIFY(input);
2473
2474 QSignalSpy textChangedSpy(input, SIGNAL(textChanged()));
2475 QVERIFY(textChangedSpy.isValid());
2476
2477 QSignalSpy textEditedSpy(input, SIGNAL(textEdited()));
2478 QVERIFY(textEditedSpy.isValid());
2479
2480 input->forceActiveFocus();
2481 QTRY_VERIFY(input->hasActiveFocus());
2482
2483 int textChanges = 0;
2484 int textEdits = 0;
2485
2486 QTest::keyClick(window: &window, key: Qt::Key_A);
2487 QCOMPARE(textChangedSpy.count(), ++textChanges);
2488 QCOMPARE(textEditedSpy.count(), ++textEdits);
2489
2490 QTest::keyClick(window: &window, key: Qt::Key_B);
2491 QCOMPARE(textChangedSpy.count(), ++textChanges);
2492 QCOMPARE(textEditedSpy.count(), ++textEdits);
2493
2494 QTest::keyClick(window: &window, key: Qt::Key_C);
2495 QCOMPARE(textChangedSpy.count(), ++textChanges);
2496 QCOMPARE(textEditedSpy.count(), ++textEdits);
2497
2498 QTest::keyClick(window: &window, key: Qt::Key_Space);
2499 QCOMPARE(textChangedSpy.count(), ++textChanges);
2500 QCOMPARE(textEditedSpy.count(), ++textEdits);
2501
2502 QTest::keyClick(window: &window, key: Qt::Key_Backspace);
2503 QCOMPARE(textChangedSpy.count(), ++textChanges);
2504 QCOMPARE(textEditedSpy.count(), ++textEdits);
2505
2506 input->clear();
2507 QCOMPARE(textChangedSpy.count(), ++textChanges);
2508 QCOMPARE(textEditedSpy.count(), textEdits);
2509
2510 input->setText("TextInput");
2511 QCOMPARE(textChangedSpy.count(), ++textChanges);
2512 QCOMPARE(textEditedSpy.count(), textEdits);
2513}
2514
2515/*
2516TextInput element should only handle left/right keys until the cursor reaches
2517the extent of the text, then they should ignore the keys.
2518
2519*/
2520void tst_qquicktextinput::navigation()
2521{
2522 QQuickView window(testFileUrl(fileName: "navigation.qml"));
2523 window.show();
2524 window.requestActivate();
2525
2526 QVERIFY(window.rootObject() != nullptr);
2527
2528 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "myInput")));
2529
2530 QVERIFY(input != nullptr);
2531 input->setCursorPosition(0);
2532 QTRY_VERIFY(input->hasActiveFocus());
2533 simulateKey(&window, key: Qt::Key_Left);
2534 QVERIFY(!input->hasActiveFocus());
2535 simulateKey(&window, key: Qt::Key_Right);
2536 QVERIFY(input->hasActiveFocus());
2537 //QT-2944: If text is selected, ensure we deselect upon cursor motion
2538 input->setCursorPosition(input->text().length());
2539 input->select(start: 0,end: input->text().length());
2540 QVERIFY(input->selectionStart() != input->selectionEnd());
2541 simulateKey(&window, key: Qt::Key_Right);
2542 QCOMPARE(input->selectionStart(), input->selectionEnd());
2543 QCOMPARE(input->selectionStart(), input->text().length());
2544 QVERIFY(input->hasActiveFocus());
2545 simulateKey(&window, key: Qt::Key_Right);
2546 QVERIFY(!input->hasActiveFocus());
2547 simulateKey(&window, key: Qt::Key_Left);
2548 QVERIFY(input->hasActiveFocus());
2549
2550 // Up and Down should NOT do Home/End, even on OS X (QTBUG-10438).
2551 input->setCursorPosition(2);
2552 QCOMPARE(input->cursorPosition(),2);
2553 simulateKey(&window, key: Qt::Key_Up);
2554 QCOMPARE(input->cursorPosition(),2);
2555 simulateKey(&window, key: Qt::Key_Down);
2556 QCOMPARE(input->cursorPosition(),2);
2557
2558 // Test left and right navigation works if the TextInput is empty (QTBUG-25447).
2559 input->setText(QString());
2560 QCOMPARE(input->cursorPosition(), 0);
2561 simulateKey(&window, key: Qt::Key_Right);
2562 QCOMPARE(input->hasActiveFocus(), false);
2563 simulateKey(&window, key: Qt::Key_Left);
2564 QCOMPARE(input->hasActiveFocus(), true);
2565 simulateKey(&window, key: Qt::Key_Left);
2566 QCOMPARE(input->hasActiveFocus(), false);
2567}
2568
2569void tst_qquicktextinput::navigation_RTL()
2570{
2571 QQuickView window(testFileUrl(fileName: "navigation.qml"));
2572 window.show();
2573 window.requestActivate();
2574
2575 QVERIFY(window.rootObject() != nullptr);
2576
2577 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "myInput")));
2578
2579 QVERIFY(input != nullptr);
2580 const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
2581 input->setText(QString::fromUtf16(arabic_str, size: 11));
2582
2583 input->setCursorPosition(0);
2584 QTRY_VERIFY(input->hasActiveFocus());
2585
2586 // move off
2587 simulateKey(&window, key: Qt::Key_Right);
2588 QVERIFY(!input->hasActiveFocus());
2589
2590 // move back
2591 simulateKey(&window, key: Qt::Key_Left);
2592 QVERIFY(input->hasActiveFocus());
2593
2594 input->setCursorPosition(input->text().length());
2595 QVERIFY(input->hasActiveFocus());
2596
2597 // move off
2598 simulateKey(&window, key: Qt::Key_Left);
2599 QVERIFY(!input->hasActiveFocus());
2600
2601 // move back
2602 simulateKey(&window, key: Qt::Key_Right);
2603 QVERIFY(input->hasActiveFocus());
2604}
2605
2606#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
2607void tst_qquicktextinput::copyAndPaste()
2608{
2609 if (!PlatformQuirks::isClipboardAvailable())
2610 QSKIP("This machine doesn't support the clipboard");
2611
2612 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
2613 QQmlComponent textInputComponent(&engine);
2614 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
2615 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
2616 QVERIFY(textInput != nullptr);
2617
2618 // copy and paste
2619 QCOMPARE(textInput->text().length(), 12);
2620 textInput->select(start: 0, end: textInput->text().length());
2621 textInput->copy();
2622 QCOMPARE(textInput->selectedText(), QString("Hello world!"));
2623 QCOMPARE(textInput->selectedText().length(), 12);
2624 textInput->setCursorPosition(0);
2625 QTRY_VERIFY(textInput->canPaste());
2626 textInput->paste();
2627 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2628 QCOMPARE(textInput->text().length(), 24);
2629
2630 // can paste
2631 QVERIFY(textInput->canPaste());
2632 textInput->setReadOnly(true);
2633 QVERIFY(!textInput->canPaste());
2634 textInput->paste();
2635 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2636 QCOMPARE(textInput->text().length(), 24);
2637 textInput->setReadOnly(false);
2638 QVERIFY(textInput->canPaste());
2639
2640 // cut: no selection
2641 textInput->cut();
2642 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2643
2644 // select word
2645 textInput->setCursorPosition(0);
2646 textInput->selectWord();
2647 QCOMPARE(textInput->selectedText(), QString("Hello"));
2648
2649 // cut: read only.
2650 textInput->setReadOnly(true);
2651 textInput->cut();
2652 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2653 textInput->setReadOnly(false);
2654
2655 // select all and cut
2656 textInput->selectAll();
2657 textInput->cut();
2658 QCOMPARE(textInput->text().length(), 0);
2659 textInput->paste();
2660 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2661 QCOMPARE(textInput->text().length(), 24);
2662
2663 // Copy first word.
2664 textInput->setCursorPosition(0);
2665 textInput->selectWord();
2666 textInput->copy();
2667 // copy: no selection, previous copy retained;
2668 textInput->setCursorPosition(0);
2669 QCOMPARE(textInput->selectedText(), QString());
2670 textInput->copy();
2671 textInput->setText(QString());
2672 textInput->paste();
2673 QCOMPARE(textInput->text(), QString("Hello"));
2674
2675 // clear copy buffer
2676 QClipboard *clipboard = QGuiApplication::clipboard();
2677 QVERIFY(clipboard);
2678 clipboard->clear();
2679 QTRY_VERIFY(!textInput->canPaste());
2680
2681 // test that copy functionality is disabled
2682 // when echo mode is set to hide text/password mode
2683 int index = 0;
2684 while (index < 4) {
2685 QQuickTextInput::EchoMode echoMode = QQuickTextInput::EchoMode(index);
2686 textInput->setEchoMode(echoMode);
2687 textInput->setText("My password");
2688 textInput->select(start: 0, end: textInput->text().length());
2689 textInput->copy();
2690 if (echoMode == QQuickTextInput::Normal) {
2691 QVERIFY(!clipboard->text().isEmpty());
2692 QCOMPARE(clipboard->text(), QString("My password"));
2693 clipboard->clear();
2694 } else {
2695 QVERIFY(!clipboard->ownsSelection() || clipboard->text().isEmpty());
2696 }
2697 index++;
2698 }
2699
2700 delete textInput;
2701}
2702#endif
2703
2704#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
2705void tst_qquicktextinput::copyAndPasteKeySequence()
2706{
2707 if (!PlatformQuirks::isClipboardAvailable())
2708 QSKIP("This machine doesn't support the clipboard");
2709
2710 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\"; focus: true }";
2711 QQmlComponent textInputComponent(&engine);
2712 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
2713 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
2714 QVERIFY(textInput != nullptr);
2715
2716 QQuickWindow window;
2717 textInput->setParentItem(window.contentItem());
2718 window.show();
2719 window.requestActivate();
2720 QVERIFY(QTest::qWaitForWindowActive(&window));
2721
2722 // copy and paste
2723 QVERIFY(textInput->hasActiveFocus());
2724 QCOMPARE(textInput->text().length(), 12);
2725 textInput->select(start: 0, end: textInput->text().length());
2726 simulateKeys(window: &window, sequence: QKeySequence::Copy);
2727 QCOMPARE(textInput->selectedText(), QString("Hello world!"));
2728 QCOMPARE(textInput->selectedText().length(), 12);
2729 textInput->setCursorPosition(0);
2730 QVERIFY(textInput->canPaste());
2731 simulateKeys(window: &window, sequence: QKeySequence::Paste);
2732 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2733 QCOMPARE(textInput->text().length(), 24);
2734
2735 // select all and cut
2736 simulateKeys(window: &window, sequence: QKeySequence::SelectAll);
2737 simulateKeys(window: &window, sequence: QKeySequence::Cut);
2738 QCOMPARE(textInput->text().length(), 0);
2739 simulateKeys(window: &window, sequence: QKeySequence::Paste);
2740 QCOMPARE(textInput->text(), QString("Hello world!Hello world!"));
2741 QCOMPARE(textInput->text().length(), 24);
2742
2743 // clear copy buffer
2744 QClipboard *clipboard = QGuiApplication::clipboard();
2745 QVERIFY(clipboard);
2746 clipboard->clear();
2747 QTRY_VERIFY(!textInput->canPaste());
2748
2749 // test that copy functionality is disabled
2750 // when echo mode is set to hide text/password mode
2751 int index = 0;
2752 while (index < 4) {
2753 QQuickTextInput::EchoMode echoMode = QQuickTextInput::EchoMode(index);
2754 textInput->setEchoMode(echoMode);
2755 textInput->setText("My password");
2756 textInput->select(start: 0, end: textInput->text().length());
2757 simulateKeys(window: &window, sequence: QKeySequence::Copy);
2758 if (echoMode == QQuickTextInput::Normal) {
2759 QVERIFY(!clipboard->text().isEmpty());
2760 QCOMPARE(clipboard->text(), QString("My password"));
2761 clipboard->clear();
2762 } else {
2763 QVERIFY(!clipboard->ownsClipboard() || clipboard->text().isEmpty());
2764 }
2765 index++;
2766 }
2767
2768 delete textInput;
2769}
2770#endif
2771
2772#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
2773void tst_qquicktextinput::canPasteEmpty()
2774{
2775 QGuiApplication::clipboard()->clear();
2776
2777 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
2778 QQmlComponent textInputComponent(&engine);
2779 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
2780 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
2781 QVERIFY(textInput != nullptr);
2782
2783 bool cp = !textInput->isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
2784 QCOMPARE(textInput->canPaste(), cp);
2785}
2786#endif
2787
2788#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
2789void tst_qquicktextinput::canPaste()
2790{
2791 QGuiApplication::clipboard()->setText("Some text");
2792
2793 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\" }";
2794 QQmlComponent textInputComponent(&engine);
2795 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
2796 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
2797 QVERIFY(textInput != nullptr);
2798
2799 bool cp = !textInput->isReadOnly() && QGuiApplication::clipboard()->text().length() != 0;
2800 QCOMPARE(textInput->canPaste(), cp);
2801}
2802#endif
2803
2804#if QT_CONFIG(clipboard) && QT_CONFIG(shortcut)
2805void tst_qquicktextinput::middleClickPaste()
2806{
2807 if (!PlatformQuirks::isClipboardAvailable())
2808 QSKIP("This machine doesn't support the clipboard");
2809
2810 QQuickView window(testFileUrl(fileName: "mouseselection_true.qml"));
2811
2812 window.show();
2813 window.requestActivate();
2814 QVERIFY(QTest::qWaitForWindowActive(&window));
2815
2816 QVERIFY(window.rootObject() != nullptr);
2817 QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput *>(object: window.rootObject());
2818 QVERIFY(textInputObject != nullptr);
2819
2820 textInputObject->setFocus(true);
2821
2822 QString originalText = textInputObject->text();
2823 QString selectedText = "234567";
2824
2825 // press-and-drag-and-release from x1 to x2
2826 const QPoint p1 = textInputObject->positionToRectangle(pos: 2).center().toPoint();
2827 const QPoint p2 = textInputObject->positionToRectangle(pos: 8).center().toPoint();
2828 const QPoint p3 = textInputObject->positionToRectangle(pos: 1).center().toPoint();
2829 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p1);
2830 QTest::mouseMove(window: &window, pos: p2);
2831 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: p2);
2832 QTRY_COMPARE(textInputObject->selectedText(), selectedText);
2833
2834 // Middle click pastes the selected text, assuming the platform supports it.
2835 QTest::mouseClick(window: &window, button: Qt::MiddleButton, stateKey: Qt::NoModifier, pos: p3);
2836
2837 // ### This is to prevent double click detection from carrying over to the next test.
2838 QTest::qWait(ms: QGuiApplication::styleHints()->mouseDoubleClickInterval() + 10);
2839
2840 if (QGuiApplication::clipboard()->supportsSelection())
2841 QCOMPARE(textInputObject->text().mid(1, selectedText.length()), selectedText);
2842 else
2843 QCOMPARE(textInputObject->text(), originalText);
2844}
2845#endif
2846
2847void tst_qquicktextinput::passwordCharacter()
2848{
2849 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"Hello world!\"; font.family: \"Helvetica\"; echoMode: TextInput.Password }";
2850 QQmlComponent textInputComponent(&engine);
2851 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
2852 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
2853 QVERIFY(textInput != nullptr);
2854
2855 textInput->setPasswordCharacter("X");
2856 qreal implicitWidth = textInput->implicitWidth();
2857 textInput->setPasswordCharacter(".");
2858
2859 // QTBUG-12383 content is updated and redrawn
2860 QVERIFY(textInput->implicitWidth() < implicitWidth);
2861
2862 delete textInput;
2863}
2864
2865void tst_qquicktextinput::cursorDelegate_data()
2866{
2867 QTest::addColumn<QUrl>(name: "source");
2868 QTest::newRow(dataTag: "out of line") << testFileUrl(fileName: "cursorTest.qml");
2869 QTest::newRow(dataTag: "in line") << testFileUrl(fileName: "cursorTestInline.qml");
2870 QTest::newRow(dataTag: "external") << testFileUrl(fileName: "cursorTestExternal.qml");
2871}
2872
2873void tst_qquicktextinput::cursorDelegate()
2874{
2875 QFETCH(QUrl, source);
2876 QQuickView view(source);
2877 view.show();
2878 view.requestActivate();
2879 QVERIFY(QTest::qWaitForWindowActive(&view));
2880 QQuickTextInput *textInputObject = view.rootObject()->findChild<QQuickTextInput*>(aName: "textInputObject");
2881 QVERIFY(textInputObject != nullptr);
2882 // Delegate is created on demand, and so won't be available immediately. Focus in or
2883 // setCursorVisible(true) will trigger creation.
2884 QTRY_VERIFY(!textInputObject->findChild<QQuickItem*>("cursorInstance"));
2885 QVERIFY(!textInputObject->isCursorVisible());
2886 //Test Delegate gets created
2887 textInputObject->setFocus(true);
2888 QVERIFY(textInputObject->isCursorVisible());
2889 QQuickItem* delegateObject = textInputObject->findChild<QQuickItem*>(aName: "cursorInstance");
2890 QVERIFY(delegateObject);
2891 QCOMPARE(delegateObject->property("localProperty").toString(), QString("Hello"));
2892 //Test Delegate gets moved
2893 for (int i=0; i<= textInputObject->text().length(); i++) {
2894 textInputObject->setCursorPosition(i);
2895 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2896 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2897 }
2898 textInputObject->setCursorPosition(0);
2899 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2900 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2901
2902 // Test delegate gets moved on mouse press.
2903 textInputObject->setSelectByMouse(true);
2904 textInputObject->setCursorPosition(0);
2905 const QPoint point1 = textInputObject->positionToRectangle(pos: 5).center().toPoint();
2906 QTest::qWait(ms: 400); //ensure this isn't treated as a double-click
2907 QTest::mouseClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: point1);
2908 QTest::qWait(ms: 50);
2909 QTRY_VERIFY(textInputObject->cursorPosition() != 0);
2910 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2911 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2912
2913 // Test delegate gets moved on mouse drag
2914 textInputObject->setCursorPosition(0);
2915 const QPoint point2 = textInputObject->positionToRectangle(pos: 10).center().toPoint();
2916 QTest::qWait(ms: 400); //ensure this isn't treated as a double-click
2917 QTest::mousePress(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: point1);
2918 QMouseEvent mv(QEvent::MouseMove, point2, Qt::LeftButton, Qt::LeftButton,Qt::NoModifier);
2919 QGuiApplication::sendEvent(receiver: &view, event: &mv);
2920 QTest::mouseRelease(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: point2);
2921 QTest::qWait(ms: 50);
2922 QTRY_COMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2923 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2924
2925 textInputObject->setReadOnly(true);
2926 textInputObject->setCursorPosition(0);
2927 QTest::qWait(ms: 400); //ensure this isn't treated as a double-click
2928 QTest::mouseClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: textInputObject->positionToRectangle(pos: 5).center().toPoint());
2929 QTest::qWait(ms: 50);
2930 QTRY_VERIFY(textInputObject->cursorPosition() != 0);
2931 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2932 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2933
2934 textInputObject->setCursorPosition(0);
2935 QTest::qWait(ms: 400); //ensure this isn't treated as a double-click
2936 QTest::mouseClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: textInputObject->positionToRectangle(pos: 5).center().toPoint());
2937 QTest::qWait(ms: 50);
2938 QTRY_VERIFY(textInputObject->cursorPosition() != 0);
2939 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2940 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2941
2942 textInputObject->setCursorPosition(0);
2943 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2944 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2945
2946 textInputObject->setReadOnly(false);
2947
2948 // Delegate moved when text is entered
2949 textInputObject->setText(QString());
2950 for (int i = 0; i < 20; ++i) {
2951 QTest::keyClick(window: &view, key: Qt::Key_A);
2952 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2953 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2954 }
2955
2956 // Delegate moved when text is entered by im.
2957 textInputObject->setText(QString());
2958 for (int i = 0; i < 20; ++i) {
2959 QInputMethodEvent event;
2960 event.setCommitString(commitString: "w");
2961 QGuiApplication::sendEvent(receiver: textInputObject, event: &event);
2962 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2963 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2964 }
2965 // Delegate moved when text is removed by im.
2966 for (int i = 19; i > 1; --i) {
2967 QInputMethodEvent event;
2968 event.setCommitString(commitString: QString(), replaceFrom: -1, replaceLength: 1);
2969 QGuiApplication::sendEvent(receiver: textInputObject, event: &event);
2970 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2971 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2972 }
2973 { // Delegate moved the text is changed in place by im.
2974 QInputMethodEvent event;
2975 event.setCommitString(commitString: "i", replaceFrom: -1, replaceLength: 1);
2976 QGuiApplication::sendEvent(receiver: textInputObject, event: &event);
2977 QCOMPARE(textInputObject->cursorRectangle().x(), delegateObject->x());
2978 QCOMPARE(textInputObject->cursorRectangle().y(), delegateObject->y());
2979 }
2980
2981 //Test Delegate gets deleted
2982 textInputObject->setCursorDelegate(nullptr);
2983 QVERIFY(!textInputObject->findChild<QQuickItem*>("cursorInstance"));
2984}
2985
2986void tst_qquicktextinput::remoteCursorDelegate()
2987{
2988 ThreadedTestHTTPServer server(dataDirectory(), TestHTTPServer::Delay);
2989 QQuickView view;
2990
2991 QQmlComponent component(view.engine(), server.url(documentPath: "/RemoteCursor.qml"));
2992
2993 view.rootContext()->setContextProperty("contextDelegate", &component);
2994 view.setSource(testFileUrl(fileName: "cursorTestRemote.qml"));
2995 view.show();
2996 view.requestActivate();
2997 QVERIFY(QTest::qWaitForWindowActive(&view));
2998 QQuickTextInput *textInputObject = view.rootObject()->findChild<QQuickTextInput*>(aName: "textInputObject");
2999 QVERIFY(textInputObject != nullptr);
3000
3001 // Delegate is created on demand, and so won't be available immediately. Focus in or
3002 // setCursorVisible(true) will trigger creation.
3003 QTRY_VERIFY(!textInputObject->findChild<QQuickItem*>("cursorInstance"));
3004 QVERIFY(!textInputObject->isCursorVisible());
3005
3006 textInputObject->setFocus(true);
3007 QVERIFY(textInputObject->isCursorVisible());
3008
3009 // Wait for component to load.
3010 QTRY_COMPARE(component.status(), QQmlComponent::Ready);
3011 QVERIFY(textInputObject->findChild<QQuickItem*>("cursorInstance"));
3012}
3013
3014void tst_qquicktextinput::cursorVisible()
3015{
3016 QSKIP("This test is unstable");
3017 QQuickTextInput input;
3018 input.componentComplete();
3019 QSignalSpy spy(&input, SIGNAL(cursorVisibleChanged(bool)));
3020
3021 QQuickView view(testFileUrl(fileName: "cursorVisible.qml"));
3022 view.show();
3023 view.requestActivate();
3024 QVERIFY(QTest::qWaitForWindowActive(&view));
3025
3026 QCOMPARE(input.isCursorVisible(), false);
3027
3028 input.setCursorVisible(true);
3029 QCOMPARE(input.isCursorVisible(), true);
3030 QCOMPARE(spy.count(), 1);
3031
3032 input.setCursorVisible(false);
3033 QCOMPARE(input.isCursorVisible(), false);
3034 QCOMPARE(spy.count(), 2);
3035
3036 input.setFocus(true);
3037 QCOMPARE(input.isCursorVisible(), false);
3038 QCOMPARE(spy.count(), 2);
3039
3040 input.setParentItem(view.rootObject());
3041 QCOMPARE(input.isCursorVisible(), true);
3042 QCOMPARE(spy.count(), 3);
3043
3044 input.setFocus(false);
3045 QCOMPARE(input.isCursorVisible(), false);
3046 QCOMPARE(spy.count(), 4);
3047
3048 input.setFocus(true);
3049 QCOMPARE(input.isCursorVisible(), true);
3050 QCOMPARE(spy.count(), 5);
3051
3052 QQuickView alternateView;
3053 alternateView.show();
3054 alternateView.requestActivate();
3055 QVERIFY(QTest::qWaitForWindowActive(&alternateView));
3056
3057 QCOMPARE(input.isCursorVisible(), false);
3058 QCOMPARE(spy.count(), 6);
3059
3060 view.requestActivate();
3061 QVERIFY(QTest::qWaitForWindowActive(&view));
3062 QCOMPARE(input.isCursorVisible(), true);
3063 QCOMPARE(spy.count(), 7);
3064
3065 { // Cursor attribute with 0 length hides cursor.
3066 QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
3067 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
3068 QCoreApplication::sendEvent(receiver: &input, event: &ev);
3069 }
3070 QCOMPARE(input.isCursorVisible(), false);
3071 QCOMPARE(spy.count(), 8);
3072
3073 { // Cursor attribute with non zero length shows cursor.
3074 QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
3075 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant()));
3076 QCoreApplication::sendEvent(receiver: &input, event: &ev);
3077 }
3078 QCOMPARE(input.isCursorVisible(), true);
3079 QCOMPARE(spy.count(), 9);
3080
3081 { // If the cursor is hidden by the input method and the text is changed it should be visible again.
3082 QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
3083 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
3084 QCoreApplication::sendEvent(receiver: &input, event: &ev);
3085 }
3086 QCOMPARE(input.isCursorVisible(), false);
3087 QCOMPARE(spy.count(), 10);
3088
3089 input.setText("something");
3090 QCOMPARE(input.isCursorVisible(), true);
3091 QCOMPARE(spy.count(), 11);
3092
3093 { // If the cursor is hidden by the input method and the cursor position is changed it should be visible again.
3094 QInputMethodEvent ev(QString(), QList<QInputMethodEvent::Attribute>()
3095 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
3096 QCoreApplication::sendEvent(receiver: &input, event: &ev);
3097 }
3098 QCOMPARE(input.isCursorVisible(), false);
3099 QCOMPARE(spy.count(), 12);
3100
3101 input.setCursorPosition(5);
3102 QCOMPARE(input.isCursorVisible(), true);
3103 QCOMPARE(spy.count(), 13);
3104}
3105
3106void tst_qquicktextinput::cursorRectangle_data()
3107{
3108 const quint16 arabic_str[] = { 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0020, 0x0638, 0x0643, 0x00646, 0x0647, 0x0633, 0x0647};
3109
3110 QTest::addColumn<QString>(name: "text");
3111 QTest::addColumn<int>(name: "positionAtWidth");
3112 QTest::addColumn<int>(name: "wrapPosition");
3113 QTest::addColumn<QString>(name: "shortText");
3114 QTest::addColumn<bool>(name: "leftToRight");
3115
3116 QTest::newRow(dataTag: "left to right")
3117 << "Hello World!" << 5 << 11
3118 << "Hi"
3119 << true;
3120 QTest::newRow(dataTag: "right to left")
3121 << QString::fromUtf16(arabic_str, size: lengthOf(arabic_str)) << 5 << 11
3122 << QString::fromUtf16(arabic_str, size: 3)
3123 << false;
3124}
3125
3126#if QT_CONFIG(im)
3127#define COMPARE_INPUT_METHOD_QUERY(type, input, property, method, result) \
3128 QCOMPARE((type) input->inputMethodQuery(property).method(), result);
3129#else
3130#define COMPARE_INPUT_METHOD_QUERY(type, input, property, method, result) \
3131 qt_noop()
3132#endif
3133
3134void tst_qquicktextinput::cursorRectangle()
3135{
3136 QFETCH(QString, text);
3137 QFETCH(int, positionAtWidth);
3138 QFETCH(int, wrapPosition);
3139 QFETCH(QString, shortText);
3140 QFETCH(bool, leftToRight);
3141
3142 QQuickTextInput input;
3143 input.setText(text);
3144 input.componentComplete();
3145
3146 QTextLayout layout(text);
3147 layout.setFont(input.font());
3148 if (!qmlDisableDistanceField()) {
3149 QTextOption option;
3150 option.setUseDesignMetrics(true);
3151 layout.setTextOption(option);
3152 }
3153 layout.beginLayout();
3154 QTextLine line = layout.createLine();
3155 layout.endLayout();
3156
3157 qreal offset = 0;
3158 if (leftToRight) {
3159 input.setWidth(line.cursorToX(cursorPos: positionAtWidth, edge: QTextLine::Leading));
3160 } else {
3161 input.setWidth(line.horizontalAdvance() - line.cursorToX(cursorPos: positionAtWidth, edge: QTextLine::Leading));
3162 offset = line.horizontalAdvance() - input.width();
3163 }
3164 input.setHeight(qCeil(v: line.height() * 3 / 2));
3165
3166 QRectF r;
3167
3168 for (int i = 0; i <= positionAtWidth; ++i) {
3169 input.setCursorPosition(i);
3170 r = input.cursorRectangle();
3171
3172 QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
3173 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3174 QCOMPARE(input.positionToRectangle(i), r);
3175 }
3176
3177 // Check the cursor rectangle remains within the input bounding rect when auto scrolling.
3178 QCOMPARE(r.left(), leftToRight ? input.width() : 0);
3179
3180 for (int i = positionAtWidth + 1; i < text.length(); ++i) {
3181 input.setCursorPosition(i);
3182 QCOMPARE(r, input.cursorRectangle());
3183 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3184 QCOMPARE(input.positionToRectangle(i), r);
3185 }
3186
3187 for (int i = text.length() - 2; i >= 0; --i) {
3188 input.setCursorPosition(i);
3189 r = input.cursorRectangle();
3190 QCOMPARE(r.top(), 0.);
3191 if (leftToRight) {
3192 QVERIFY(r.right() >= 0);
3193 } else {
3194 QVERIFY(r.left() <= input.width());
3195 }
3196 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3197 QCOMPARE(input.positionToRectangle(i), r);
3198 }
3199
3200 // Check position with word wrap.
3201 input.setWrapMode(QQuickTextInput::WordWrap);
3202 input.setAutoScroll(false);
3203 for (int i = 0; i < wrapPosition; ++i) {
3204 input.setCursorPosition(i);
3205 r = input.cursorRectangle();
3206
3207 QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
3208 QCOMPARE(r.top(), 0.);
3209 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3210 QCOMPARE(input.positionToRectangle(i), r);
3211 }
3212
3213 input.setCursorPosition(wrapPosition);
3214 r = input.cursorRectangle();
3215 if (leftToRight) {
3216 QCOMPARE(r.left(), 0.);
3217 } else {
3218 QCOMPARE(r.left(), input.width());
3219 }
3220 // we can't be exact here, as the space character can have a different ascent/descent from the arabic chars
3221 // this then leads to different line heights between the wrapped and non wrapped texts
3222 QVERIFY(r.top() >= line.height() - 5);
3223 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3224 QCOMPARE(input.positionToRectangle(11), r);
3225
3226 for (int i = wrapPosition + 1; i < text.length(); ++i) {
3227 input.setCursorPosition(i);
3228 r = input.cursorRectangle();
3229 QVERIFY(r.top() >= line.height() - 5);
3230 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3231 QCOMPARE(input.positionToRectangle(i), r);
3232 }
3233
3234 // Check vertical scrolling with word wrap.
3235 input.setAutoScroll(true);
3236 for (int i = 0; i <= positionAtWidth; ++i) {
3237 input.setCursorPosition(i);
3238 r = input.cursorRectangle();
3239
3240 QCOMPARE(r.left(), line.cursorToX(i, QTextLine::Leading) - offset);
3241 QCOMPARE(r.top(), 0.);
3242 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3243 QCOMPARE(input.positionToRectangle(i), r);
3244 }
3245
3246 // Whitespace doesn't wrap, so scroll horizontally until the until the cursor
3247 // reaches the next non-whitespace character.
3248 QCOMPARE(r.left(), leftToRight ? input.width() : 0);
3249 for (int i = positionAtWidth + 1; i < wrapPosition && leftToRight; ++i) {
3250 input.setCursorPosition(i);
3251 QCOMPARE(r, input.cursorRectangle());
3252 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3253 QCOMPARE(input.positionToRectangle(i), r);
3254 }
3255
3256 input.setCursorPosition(wrapPosition);
3257 r = input.cursorRectangle();
3258 if (leftToRight) {
3259 QCOMPARE(r.left(), 0.);
3260 } else {
3261 QCOMPARE(r.left(), input.width());
3262 }
3263 QVERIFY(r.bottom() >= input.height());
3264 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3265 QCOMPARE(input.positionToRectangle(11), r);
3266
3267 for (int i = wrapPosition + 1; i < text.length(); ++i) {
3268 input.setCursorPosition(i);
3269 r = input.cursorRectangle();
3270 QVERIFY(r.bottom() >= input.height());
3271 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3272 QCOMPARE(input.positionToRectangle(i), r);
3273 }
3274
3275 for (int i = text.length() - 2; i >= wrapPosition; --i) {
3276 input.setCursorPosition(i);
3277 r = input.cursorRectangle();
3278 QVERIFY(r.bottom() >= input.height());
3279 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3280 QCOMPARE(input.positionToRectangle(i), r);
3281 }
3282
3283 input.setCursorPosition(wrapPosition - 1);
3284 r = input.cursorRectangle();
3285 QCOMPARE(r.top(), 0.);
3286 QCOMPARE(r.left(), leftToRight ? input.width() : 0);
3287 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3288 QCOMPARE(input.positionToRectangle(10), r);
3289
3290 for (int i = wrapPosition - 2; i >= positionAtWidth + 1; --i) {
3291 input.setCursorPosition(i);
3292 QCOMPARE(r, input.cursorRectangle());
3293 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3294 QCOMPARE(input.positionToRectangle(i), r);
3295 }
3296
3297 for (int i = positionAtWidth; i >= 0; --i) {
3298 input.setCursorPosition(i);
3299 r = input.cursorRectangle();
3300 QCOMPARE(r.top(), 0.);
3301 COMPARE_INPUT_METHOD_QUERY(QRectF, (&input), Qt::ImCursorRectangle, toRectF, r);
3302 QCOMPARE(input.positionToRectangle(i), r);
3303 }
3304
3305 input.setText(shortText);
3306 input.setHAlign(leftToRight ? QQuickTextInput::AlignRight : QQuickTextInput::AlignLeft);
3307 r = input.cursorRectangle();
3308 QCOMPARE(r.left(), leftToRight ? input.width() : 0);
3309
3310 QSignalSpy cursorRectangleSpy(&input, SIGNAL(cursorRectangleChanged()));
3311
3312 QString widerText = shortText;
3313 widerText[1] = 'W'; // Assumes shortText is at least two characters long.
3314 input.setText(widerText);
3315
3316 QCOMPARE(cursorRectangleSpy.count(), 1);
3317}
3318
3319void tst_qquicktextinput::readOnly()
3320{
3321 QQuickView window(testFileUrl(fileName: "readOnly.qml"));
3322 window.show();
3323 window.requestActivate();
3324
3325 QVERIFY(window.rootObject() != nullptr);
3326
3327 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "myInput")));
3328
3329 QVERIFY(input != nullptr);
3330 QTRY_VERIFY(input->hasActiveFocus());
3331 QVERIFY(input->isReadOnly());
3332 QVERIFY(!input->isCursorVisible());
3333 QString initial = input->text();
3334 for (int k=Qt::Key_0; k<=Qt::Key_Z; k++)
3335 simulateKey(&window, key: k);
3336 simulateKey(&window, key: Qt::Key_Return);
3337 simulateKey(&window, key: Qt::Key_Space);
3338 simulateKey(&window, key: Qt::Key_Escape);
3339 QCOMPARE(input->text(), initial);
3340
3341 input->setCursorPosition(3);
3342 input->setReadOnly(false);
3343 QCOMPARE(input->isReadOnly(), false);
3344 QCOMPARE(input->cursorPosition(), input->text().length());
3345 QVERIFY(input->isCursorVisible());
3346}
3347
3348void tst_qquicktextinput::echoMode()
3349{
3350 QQuickView window(testFileUrl(fileName: "echoMode.qml"));
3351 window.show();
3352 window.requestActivate();
3353 QVERIFY(QTest::qWaitForWindowActive(&window));
3354
3355 QVERIFY(window.rootObject() != nullptr);
3356
3357 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "myInput")));
3358
3359 QVERIFY(input != nullptr);
3360 QTRY_VERIFY(input->hasActiveFocus());
3361 QString initial = input->text();
3362 Qt::InputMethodHints ref;
3363 QCOMPARE(initial, QLatin1String("ABCDefgh"));
3364 QCOMPARE(input->echoMode(), QQuickTextInput::Normal);
3365 QCOMPARE(input->displayText(), input->text());
3366 const QString passwordMaskCharacter = qApp->styleHints()->passwordMaskCharacter();
3367 //Normal
3368 ref &= ~Qt::ImhHiddenText;
3369 ref &= ~(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
3370 COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
3371 input->setEchoMode(QQuickTextInput::NoEcho);
3372 QCOMPARE(input->text(), initial);
3373 QCOMPARE(input->displayText(), QLatin1String(""));
3374 QCOMPARE(input->passwordCharacter(), passwordMaskCharacter);
3375 //NoEcho
3376 ref |= Qt::ImhHiddenText;
3377 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
3378 COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
3379 input->setEchoMode(QQuickTextInput::Password);
3380 //Password
3381 ref |= Qt::ImhHiddenText;
3382 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
3383 QCOMPARE(input->text(), initial);
3384 QCOMPARE(input->displayText(), QString(8, passwordMaskCharacter.at(0)));
3385 COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
3386 // clearing input hints do not clear bits set by echo mode
3387 input->setInputMethodHints(Qt::ImhNone);
3388 COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
3389 input->setPasswordCharacter(QChar('Q'));
3390 QCOMPARE(input->passwordCharacter(), QLatin1String("Q"));
3391 QCOMPARE(input->text(), initial);
3392 QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
3393 input->setEchoMode(QQuickTextInput::PasswordEchoOnEdit);
3394 //PasswordEchoOnEdit
3395 ref &= ~Qt::ImhHiddenText;
3396 ref |= (Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
3397 COMPARE_INPUT_METHOD_QUERY(Qt::InputMethodHints, input, Qt::ImHints, toInt, ref);
3398 QCOMPARE(input->text(), initial);
3399 QCOMPARE(input->displayText(), QLatin1String("QQQQQQQQ"));
3400 COMPARE_INPUT_METHOD_QUERY(QString, input, Qt::ImSurroundingText, toString,
3401 QLatin1String("QQQQQQQQ"));
3402 QTest::keyPress(window: &window, key: Qt::Key_A);//Clearing previous entry is part of PasswordEchoOnEdit
3403 QTest::keyRelease(window: &window, key: Qt::Key_A, modifier: Qt::NoModifier ,delay: 10);
3404 QCOMPARE(input->text(), QLatin1String("a"));
3405 QCOMPARE(input->displayText(), QLatin1String("a"));
3406 COMPARE_INPUT_METHOD_QUERY(QString, input, Qt::ImSurroundingText, toString, QLatin1String("a"));
3407 input->setFocus(false);
3408 QVERIFY(!input->hasActiveFocus());
3409 QCOMPARE(input->displayText(), QLatin1String("Q"));
3410 COMPARE_INPUT_METHOD_QUERY(QString, input, Qt::ImSurroundingText, toString, QLatin1String("Q"));
3411 input->setFocus(true);
3412 QVERIFY(input->hasActiveFocus());
3413 QInputMethodEvent inputEvent;
3414 inputEvent.setCommitString(commitString: initial);
3415 QGuiApplication::sendEvent(receiver: input, event: &inputEvent);
3416 QCOMPARE(input->text(), initial);
3417 QCOMPARE(input->displayText(), initial);
3418 COMPARE_INPUT_METHOD_QUERY(QString, input, Qt::ImSurroundingText, toString, initial);
3419}
3420
3421void tst_qquicktextinput::passwordEchoDelay()
3422{
3423 int maskDelay = qGuiApp->styleHints()->passwordMaskDelay();
3424 if (maskDelay <= 0)
3425 QSKIP("No mask delay in use");
3426 QQuickView window(testFileUrl(fileName: "echoMode.qml"));
3427 window.show();
3428 window.requestActivate();
3429 QVERIFY(QTest::qWaitForWindowActive(&window));
3430
3431 QVERIFY(window.rootObject() != nullptr);
3432
3433 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: qvariant_cast<QObject *>(v: window.rootObject()->property(name: "myInput")));
3434 QVERIFY(input);
3435 QVERIFY(input->hasActiveFocus());
3436
3437 QQuickItem *cursor = input->findChild<QQuickItem *>(aName: "cursor");
3438 QVERIFY(cursor);
3439
3440 QChar fillChar = qApp->styleHints()->passwordMaskCharacter();
3441
3442 input->setEchoMode(QQuickTextInput::Password);
3443 QCOMPARE(input->displayText(), QString(8, fillChar));
3444 input->setText(QString());
3445 QCOMPARE(input->displayText(), QString());
3446
3447 QTest::keyPress(window: &window, key: '0');
3448 QTest::keyPress(window: &window, key: '1');
3449 QTest::keyPress(window: &window, key: '2');
3450 QCOMPARE(input->displayText(), QString(2, fillChar) + QLatin1Char('2'));
3451 QTest::keyPress(window: &window, key: '3');
3452 QTest::keyPress(window: &window, key: '4');
3453 QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
3454 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
3455 QCOMPARE(input->displayText(), QString(4, fillChar));
3456 QTest::keyPress(window: &window, key: '4');
3457 QCOMPARE(input->displayText(), QString(4, fillChar) + QLatin1Char('4'));
3458 QCOMPARE(input->cursorRectangle().topLeft(), cursor->position());
3459
3460 // Verify the last character entered is replaced by the fill character after a delay.
3461 // Also check the cursor position is updated to accomdate a size difference between
3462 // the fill character and the replaced character.
3463 QSignalSpy cursorSpy(input, SIGNAL(cursorRectangleChanged()));
3464 QTest::qWait(ms: maskDelay);
3465 QTRY_COMPARE(input->displayText(), QString(5, fillChar));
3466 QCOMPARE(cursorSpy.count(), 1);
3467 QCOMPARE(input->cursorRectangle().topLeft(), cursor->position());
3468
3469 QTest::keyPress(window: &window, key: '5');
3470 QCOMPARE(input->displayText(), QString(5, fillChar) + QLatin1Char('5'));
3471 input->setFocus(false);
3472 QVERIFY(!input->hasFocus());
3473 QCOMPARE(input->displayText(), QString(6, fillChar));
3474 input->setFocus(true);
3475 QTRY_VERIFY(input->hasFocus());
3476 QCOMPARE(input->displayText(), QString(6, fillChar));
3477 QTest::keyPress(window: &window, key: '6');
3478 QCOMPARE(input->displayText(), QString(6, fillChar) + QLatin1Char('6'));
3479
3480 QInputMethodEvent ev;
3481 ev.setCommitString(commitString: QLatin1String("7"));
3482 QGuiApplication::sendEvent(receiver: input, event: &ev);
3483 QCOMPARE(input->displayText(), QString(7, fillChar) + QLatin1Char('7'));
3484
3485 input->setCursorPosition(3);
3486 QCOMPARE(input->displayText(), QString(7, fillChar) + QLatin1Char('7'));
3487 QTest::keyPress(window: &window, key: 'a');
3488 QCOMPARE(input->displayText(), QString(3, fillChar) + QLatin1Char('a') + QString(5, fillChar));
3489 QTest::keyPress(window: &window, key: Qt::Key_Backspace);
3490 QCOMPARE(input->displayText(), QString(8, fillChar));
3491}
3492
3493
3494void tst_qquicktextinput::simulateKey(QWindow *view, int key)
3495{
3496 QKeyEvent press(QKeyEvent::KeyPress, key, nullptr);
3497 QKeyEvent release(QKeyEvent::KeyRelease, key, nullptr);
3498
3499 QGuiApplication::sendEvent(receiver: view, event: &press);
3500 QGuiApplication::sendEvent(receiver: view, event: &release);
3501}
3502
3503
3504void tst_qquicktextinput::focusOnPress()
3505{
3506 QString componentStr =
3507 "import QtQuick 2.0\n"
3508 "TextInput {\n"
3509 "property bool selectOnFocus: false\n"
3510 "width: 100; height: 50\n"
3511 "activeFocusOnPress: true\n"
3512 "text: \"Hello World\"\n"
3513 "onFocusChanged: { if (focus && selectOnFocus) selectAll() }"
3514 " }";
3515 QQmlComponent texteditComponent(&engine);
3516 texteditComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
3517 QQuickTextInput *textInputObject = qobject_cast<QQuickTextInput*>(object: texteditComponent.create());
3518 QVERIFY(textInputObject != nullptr);
3519 QCOMPARE(textInputObject->focusOnPress(), true);
3520 QCOMPARE(textInputObject->hasFocus(), false);
3521
3522 QSignalSpy focusSpy(textInputObject, SIGNAL(focusChanged(bool)));
3523 QSignalSpy activeFocusSpy(textInputObject, SIGNAL(focusChanged(bool)));
3524 QSignalSpy activeFocusOnPressSpy(textInputObject, SIGNAL(activeFocusOnPressChanged(bool)));
3525
3526 textInputObject->setFocusOnPress(true);
3527 QCOMPARE(textInputObject->focusOnPress(), true);
3528 QCOMPARE(activeFocusOnPressSpy.count(), 0);
3529
3530 QQuickWindow window;
3531 window.resize(w: 100, h: 50);
3532 textInputObject->setParentItem(window.contentItem());
3533 window.showNormal();
3534 window.requestActivate();
3535 QVERIFY(QTest::qWaitForWindowActive(&window));
3536
3537 QCOMPARE(textInputObject->hasFocus(), false);
3538 QCOMPARE(textInputObject->hasActiveFocus(), false);
3539
3540 Qt::KeyboardModifiers noModifiers = Qt::NoModifier;
3541 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: noModifiers);
3542 QGuiApplication::processEvents();
3543 QCOMPARE(textInputObject->hasFocus(), true);
3544 QCOMPARE(textInputObject->hasActiveFocus(), true);
3545 QCOMPARE(focusSpy.count(), 1);
3546 QCOMPARE(activeFocusSpy.count(), 1);
3547 QCOMPARE(textInputObject->selectedText(), QString());
3548 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: noModifiers);
3549
3550 textInputObject->setFocusOnPress(false);
3551 QCOMPARE(textInputObject->focusOnPress(), false);
3552 QCOMPARE(activeFocusOnPressSpy.count(), 1);
3553
3554 textInputObject->setFocus(false);
3555 QCOMPARE(textInputObject->hasFocus(), false);
3556 QCOMPARE(textInputObject->hasActiveFocus(), false);
3557 QCOMPARE(focusSpy.count(), 2);
3558 QCOMPARE(activeFocusSpy.count(), 2);
3559
3560 // Wait for double click timeout to expire before clicking again.
3561 QTest::qWait(ms: 400);
3562 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: noModifiers);
3563 QGuiApplication::processEvents();
3564 QCOMPARE(textInputObject->hasFocus(), false);
3565 QCOMPARE(textInputObject->hasActiveFocus(), false);
3566 QCOMPARE(focusSpy.count(), 2);
3567 QCOMPARE(activeFocusSpy.count(), 2);
3568 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: noModifiers);
3569
3570 textInputObject->setFocusOnPress(true);
3571 QCOMPARE(textInputObject->focusOnPress(), true);
3572 QCOMPARE(activeFocusOnPressSpy.count(), 2);
3573
3574 // Test a selection made in the on(Active)FocusChanged handler isn't overwritten.
3575 textInputObject->setProperty(name: "selectOnFocus", value: true);
3576
3577 QTest::qWait(ms: 400);
3578 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: noModifiers);
3579 QGuiApplication::processEvents();
3580 QCOMPARE(textInputObject->hasFocus(), true);
3581 QCOMPARE(textInputObject->hasActiveFocus(), true);
3582 QCOMPARE(focusSpy.count(), 3);
3583 QCOMPARE(activeFocusSpy.count(), 3);
3584 QCOMPARE(textInputObject->selectedText(), textInputObject->text());
3585 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: noModifiers);
3586}
3587
3588void tst_qquicktextinput::focusOnPressOnlyOneItem()
3589{
3590 QQuickView window(testFileUrl(fileName: "focusOnlyOneOnPress.qml"));
3591 window.show();
3592 window.requestActivate();
3593 QVERIFY(QTest::qWaitForWindowActive(&window));
3594
3595 QQuickTextInput *first = window.rootObject()->findChild<QQuickTextInput*>(aName: "first");
3596 QQuickTextInput *second = window.rootObject()->findChild<QQuickTextInput*>(aName: "second");
3597 QQuickTextInput *third = window.rootObject()->findChild<QQuickTextInput*>(aName: "third");
3598
3599 // second is focused onComplete
3600 QVERIFY(second->hasActiveFocus());
3601
3602 // and first will try focus when we press it
3603 QVERIFY(first->focusOnPress());
3604
3605 // write some text to start editing
3606 QTest::keyClick(window: &window, key: Qt::Key_A);
3607
3608 // click the first input. naturally, we are giving focus on press, but
3609 // second's editingFinished also attempts to assign focus. lastly, focus
3610 // should bounce back to second from first's editingFinished signal.
3611 //
3612 // this is a contrived example to be sure, but at the end of this, the
3613 // important thing is that only one thing should have activeFocus.
3614 Qt::KeyboardModifiers noModifiers = nullptr;
3615 QTest::mousePress(window: &window, button: Qt::LeftButton, stateKey: noModifiers, pos: QPoint(10, 10));
3616
3617 // make sure the press is processed.
3618 QGuiApplication::processEvents();
3619
3620 QVERIFY(second->hasActiveFocus()); // make sure it's still there
3621 QVERIFY(!third->hasActiveFocus()); // make sure it didn't end up anywhere else
3622 QVERIFY(!first->hasActiveFocus()); // make sure it didn't end up anywhere else
3623
3624 // reset state
3625 QTest::mouseRelease(window: &window, button: Qt::LeftButton, stateKey: noModifiers, pos: QPoint(10, 10));
3626}
3627
3628void tst_qquicktextinput::openInputPanel()
3629{
3630 PlatformInputContext platformInputContext;
3631 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
3632 inputMethodPrivate->testContext = &platformInputContext;
3633
3634 QQuickView view(testFileUrl(fileName: "openInputPanel.qml"));
3635 view.showNormal();
3636 view.requestActivate();
3637 QVERIFY(QTest::qWaitForWindowActive(&view));
3638
3639 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: view.rootObject());
3640 QVERIFY(input);
3641
3642 // check default values
3643 QVERIFY(input->focusOnPress());
3644 QVERIFY(!input->hasActiveFocus());
3645 QVERIFY(qApp->focusObject() != input);
3646 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3647
3648 // input panel should open on focus
3649 Qt::KeyboardModifiers noModifiers = nullptr;
3650 QTest::mousePress(window: &view, button: Qt::LeftButton, stateKey: noModifiers);
3651 QGuiApplication::processEvents();
3652 QVERIFY(input->hasActiveFocus());
3653 QCOMPARE(qApp->focusObject(), input);
3654 QCOMPARE(qApp->inputMethod()->isVisible(), true);
3655 QTest::mouseRelease(window: &view, button: Qt::LeftButton, stateKey: noModifiers);
3656
3657 // input panel should be re-opened when pressing already focused TextInput
3658 qApp->inputMethod()->hide();
3659 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3660 QVERIFY(input->hasActiveFocus());
3661 QTest::mousePress(window: &view, button: Qt::LeftButton, stateKey: noModifiers);
3662 QGuiApplication::processEvents();
3663 QCOMPARE(qApp->inputMethod()->isVisible(), true);
3664 QTest::mouseRelease(window: &view, button: Qt::LeftButton, stateKey: noModifiers);
3665
3666 // input panel should stay visible if focus is lost to another text inputor
3667 QSignalSpy inputPanelVisibilitySpy(qApp->inputMethod(), SIGNAL(visibleChanged()));
3668 QQuickTextInput anotherInput;
3669 anotherInput.componentComplete();
3670 anotherInput.setParentItem(view.rootObject());
3671 anotherInput.setFocus(true);
3672 QCOMPARE(qApp->inputMethod()->isVisible(), true);
3673 QCOMPARE(qApp->focusObject(), qobject_cast<QObject*>(&anotherInput));
3674 QCOMPARE(inputPanelVisibilitySpy.count(), 0);
3675
3676 anotherInput.setFocus(false);
3677 QVERIFY(qApp->focusObject() != &anotherInput);
3678 QCOMPARE(view.activeFocusItem(), view.contentItem());
3679 anotherInput.setFocus(true);
3680
3681 qApp->inputMethod()->hide();
3682
3683 // input panel should not be opened if TextInput is read only
3684 input->setReadOnly(true);
3685 input->setFocus(true);
3686 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3687 QTest::mousePress(window: &view, button: Qt::LeftButton, stateKey: noModifiers);
3688 QTest::mouseRelease(window: &view, button: Qt::LeftButton, stateKey: noModifiers);
3689 QGuiApplication::processEvents();
3690 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3691
3692 // input panel should not be opened if focusOnPress is set to false
3693 input->setFocusOnPress(false);
3694 input->setFocus(false);
3695 input->setFocus(true);
3696 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3697 QTest::mousePress(window: &view, button: Qt::LeftButton, stateKey: noModifiers);
3698 QTest::mouseRelease(window: &view, button: Qt::LeftButton, stateKey: noModifiers);
3699 QCOMPARE(qApp->inputMethod()->isVisible(), false);
3700}
3701
3702class MyTextInput : public QQuickTextInput
3703{
3704public:
3705 MyTextInput(QQuickItem *parent = nullptr) : QQuickTextInput(parent)
3706 {
3707 nbPaint = 0;
3708 }
3709 virtual QSGNode *updatePaintNode(QSGNode *node, UpdatePaintNodeData *data)
3710 {
3711 nbPaint++;
3712 return QQuickTextInput::updatePaintNode(oldNode: node, data);
3713 }
3714 int nbPaint;
3715};
3716
3717void tst_qquicktextinput::setHAlignClearCache()
3718{
3719 QQuickView view;
3720 view.resize(w: 200, h: 200);
3721 MyTextInput input;
3722 input.setText("Hello world");
3723 input.setParentItem(view.contentItem());
3724 view.show();
3725 view.requestActivate();
3726 QVERIFY(QTest::qWaitForWindowActive(&view));
3727 QTRY_COMPARE(input.nbPaint, 1);
3728 input.setHAlign(QQuickTextInput::AlignRight);
3729 //Changing the alignment should trigger a repaint
3730 QTRY_COMPARE(input.nbPaint, 2);
3731}
3732
3733void tst_qquicktextinput::focusOutClearSelection()
3734{
3735 QQuickView view;
3736 QQuickTextInput input;
3737 QQuickTextInput input2;
3738 input.setText(QLatin1String("Hello world"));
3739 input.setFocus(true);
3740 input2.setParentItem(view.contentItem());
3741 input.setParentItem(view.contentItem());
3742 input.componentComplete();
3743 input2.componentComplete();
3744 view.show();
3745 view.requestActivate();
3746 QVERIFY(QTest::qWaitForWindowActive(&view));
3747 QVERIFY(input.hasActiveFocus());
3748 input.select(start: 2,end: 5);
3749 //The selection should work
3750 QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
3751 input2.setFocus(true);
3752 QGuiApplication::processEvents();
3753 //The input lost the focus selection should be cleared
3754 QTRY_COMPARE(input.selectedText(), QLatin1String(""));
3755}
3756
3757void tst_qquicktextinput::focusOutNotClearSelection()
3758{
3759 QQuickView view;
3760 QQuickTextInput input;
3761 input.setText(QLatin1String("Hello world"));
3762 input.setFocus(true);
3763 input.setParentItem(view.contentItem());
3764 input.componentComplete();
3765 view.show();
3766 view.requestActivate();
3767 QVERIFY(QTest::qWaitForWindowActive(&view));
3768
3769 QVERIFY(input.hasActiveFocus());
3770 input.select(start: 2,end: 5);
3771 QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
3772
3773 // The selection should not be cleared when the focus
3774 // out event has one of the following reason:
3775 // Qt::ActiveWindowFocusReason
3776 // Qt::PopupFocusReason
3777
3778 input.setFocus(focus: false, reason: Qt::ActiveWindowFocusReason);
3779 QGuiApplication::processEvents();
3780 QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
3781 QTRY_COMPARE(input.hasActiveFocus(), false);
3782
3783 input.setFocus(true);
3784 QTRY_COMPARE(input.hasActiveFocus(), true);
3785
3786 input.setFocus(focus: false, reason: Qt::PopupFocusReason);
3787 QGuiApplication::processEvents();
3788 QTRY_COMPARE(input.selectedText(), QLatin1String("llo"));
3789 // QTBUG-36332 and 36292: a popup window does not take focus
3790 QTRY_COMPARE(input.hasActiveFocus(), true);
3791
3792 input.setFocus(true);
3793 QTRY_COMPARE(input.hasActiveFocus(), true);
3794
3795 input.setFocus(focus: false, reason: Qt::OtherFocusReason);
3796 QGuiApplication::processEvents();
3797 QTRY_COMPARE(input.selectedText(), QLatin1String(""));
3798 QTRY_COMPARE(input.hasActiveFocus(), false);
3799}
3800
3801void tst_qquicktextinput::geometrySignals()
3802{
3803 QQmlComponent component(&engine, testFileUrl(fileName: "geometrySignals.qml"));
3804 QObject *o = component.create();
3805 QVERIFY(o);
3806 QCOMPARE(o->property("bindingWidth").toInt(), 400);
3807 QCOMPARE(o->property("bindingHeight").toInt(), 500);
3808 delete o;
3809}
3810
3811void tst_qquicktextinput::contentSize()
3812{
3813 QString componentStr = "import QtQuick 2.0\nTextInput { width: 75; height: 16; font.pixelSize: 10 }";
3814 QQmlComponent textComponent(&engine);
3815 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
3816 QScopedPointer<QObject> object(textComponent.create());
3817 QQuickTextInput *textObject = qobject_cast<QQuickTextInput *>(object: object.data());
3818
3819 QSignalSpy spy(textObject, SIGNAL(contentSizeChanged()));
3820
3821 textObject->setText("The quick red fox jumped over the lazy brown dog");
3822
3823 QVERIFY(textObject->contentWidth() > textObject->width());
3824 QVERIFY(textObject->contentHeight() < textObject->height());
3825 QCOMPARE(spy.count(), 1);
3826
3827 textObject->setWrapMode(QQuickTextInput::WordWrap);
3828 QVERIFY(textObject->contentWidth() <= textObject->width());
3829 QVERIFY(textObject->contentHeight() > textObject->height());
3830 QCOMPARE(spy.count(), 2);
3831
3832 textObject->setText("The quickredfoxjumpedoverthe lazy brown dog");
3833
3834 QVERIFY(textObject->contentWidth() > textObject->width());
3835 QVERIFY(textObject->contentHeight() > textObject->height());
3836 QCOMPARE(spy.count(), 3);
3837
3838 textObject->setText("The quick red fox jumped over the lazy brown dog");
3839 for (int w = 60; w < 120; ++w) {
3840 textObject->setWidth(w);
3841 QVERIFY(textObject->contentWidth() <= textObject->width());
3842 QVERIFY(textObject->contentHeight() > textObject->height());
3843 }
3844}
3845
3846static void sendPreeditText(QQuickItem *item, const QString &text, int cursor)
3847{
3848 QInputMethodEvent event(text, QList<QInputMethodEvent::Attribute>()
3849 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, cursor, text.length(), QVariant()));
3850 QCoreApplication::sendEvent(receiver: item, event: &event);
3851}
3852
3853void tst_qquicktextinput::preeditAutoScroll()
3854{
3855 QString preeditText = "califragisiticexpialidocious!";
3856
3857 QQuickView view(testFileUrl(fileName: "preeditAutoScroll.qml"));
3858 view.show();
3859 view.requestActivate();
3860 QVERIFY(QTest::qWaitForWindowActive(&view));
3861 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: view.rootObject());
3862 QVERIFY(input);
3863 QVERIFY(input->hasActiveFocus());
3864
3865 input->setWidth(input->implicitWidth());
3866
3867 QSignalSpy cursorRectangleSpy(input, SIGNAL(cursorRectangleChanged()));
3868 int cursorRectangleChanges = 0;
3869
3870 // test the text is scrolled so the preedit is visible.
3871 sendPreeditText(item: input, text: preeditText.mid(position: 0, n: 3), cursor: 1);
3872 QVERIFY(evaluate<int>(input, QString("positionAt(0)")) != 0);
3873 QVERIFY(input->cursorRectangle().left() < input->boundingRect().width());
3874 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3875
3876 // test the text is scrolled back when the preedit is removed.
3877 QInputMethodEvent imEvent;
3878 QCoreApplication::sendEvent(receiver: input, event: &imEvent);
3879 QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(0)), 0);
3880 QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(input->width())), 5);
3881 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3882
3883 QTextLayout layout(preeditText);
3884 layout.setFont(input->font());
3885 if (!qmlDisableDistanceField()) {
3886 QTextOption option;
3887 option.setUseDesignMetrics(true);
3888 layout.setTextOption(option);
3889 }
3890 layout.beginLayout();
3891 QTextLine line = layout.createLine();
3892 layout.endLayout();
3893
3894 // test if the preedit is larger than the text input that the
3895 // character preceding the cursor is still visible.
3896 qreal x = input->positionToRectangle(pos: 0).x();
3897 for (int i = 0; i < 3; ++i) {
3898 sendPreeditText(item: input, text: preeditText, cursor: i + 1);
3899 int width = ceil(x: line.cursorToX(cursorPos: i, edge: QTextLine::Trailing)) - floor(x: line.cursorToX(cursorPos: i));
3900 QVERIFY(input->cursorRectangle().right() >= width - 3);
3901 QVERIFY(input->positionToRectangle(0).x() < x);
3902 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3903 x = input->positionToRectangle(pos: 0).x();
3904 }
3905 for (int i = 1; i >= 0; --i) {
3906 sendPreeditText(item: input, text: preeditText, cursor: i + 1);
3907 int width = ceil(x: line.cursorToX(cursorPos: i, edge: QTextLine::Trailing)) - floor(x: line.cursorToX(cursorPos: i));
3908 QVERIFY(input->cursorRectangle().right() >= width - 3);
3909 QVERIFY(input->positionToRectangle(0).x() > x);
3910 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3911 x = input->positionToRectangle(pos: 0).x();
3912 }
3913
3914 // Test incrementing the preedit cursor doesn't cause further
3915 // scrolling when right most text is visible.
3916 sendPreeditText(item: input, text: preeditText, cursor: preeditText.length() - 3);
3917 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3918 x = input->positionToRectangle(pos: 0).x();
3919 for (int i = 2; i >= 0; --i) {
3920 sendPreeditText(item: input, text: preeditText, cursor: preeditText.length() - i);
3921 QCOMPARE(input->positionToRectangle(0).x(), x);
3922 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3923 }
3924 for (int i = 1; i < 3; ++i) {
3925 sendPreeditText(item: input, text: preeditText, cursor: preeditText.length() - i);
3926 QCOMPARE(input->positionToRectangle(0).x(), x);
3927 QCOMPARE(cursorRectangleSpy.count(), ++cursorRectangleChanges);
3928 }
3929
3930 // Test disabling auto scroll.
3931 QCoreApplication::sendEvent(receiver: input, event: &imEvent);
3932
3933 input->setAutoScroll(false);
3934 sendPreeditText(item: input, text: preeditText.mid(position: 0, n: 3), cursor: 1);
3935 QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(0)), 0);
3936 QCOMPARE(evaluate<int>(input, QString("positionAt(%1)").arg(input->width())), 5);
3937}
3938
3939void tst_qquicktextinput::preeditCursorRectangle()
3940{
3941 QString preeditText = "super";
3942
3943 QQuickView view(testFileUrl(fileName: "inputMethodEvent.qml"));
3944 view.show();
3945 view.requestActivate();
3946 QVERIFY(QTest::qWaitForWindowActive(&view));
3947 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: view.rootObject());
3948 QVERIFY(input);
3949 QVERIFY(input->hasActiveFocus());
3950
3951 QQuickItem *cursor = input->findChild<QQuickItem *>(aName: "cursor");
3952 QVERIFY(cursor);
3953
3954 QRectF currentRect;
3955
3956 QInputMethodQueryEvent query(Qt::ImCursorRectangle);
3957 QCoreApplication::sendEvent(receiver: input, event: &query);
3958 QRectF previousRect = query.value(query: Qt::ImCursorRectangle).toRectF();
3959
3960 // Verify that the micro focus rect is positioned the same for position 0 as
3961 // it would be if there was no preedit text.
3962 sendPreeditText(item: input, text: preeditText, cursor: 0);
3963 QCoreApplication::sendEvent(receiver: input, event: &query);
3964 currentRect = query.value(query: Qt::ImCursorRectangle).toRectF();
3965 QCOMPARE(currentRect, previousRect);
3966 QCOMPARE(input->cursorRectangle(), currentRect);
3967 QCOMPARE(cursor->position(), currentRect.topLeft());
3968
3969 QSignalSpy inputSpy(input, SIGNAL(cursorRectangleChanged()));
3970 QSignalSpy panelSpy(qGuiApp->inputMethod(), SIGNAL(cursorRectangleChanged()));
3971
3972 // Verify that the micro focus rect moves to the left as the cursor position
3973 // is incremented.
3974 for (int i = 1; i <= 5; ++i) {
3975 sendPreeditText(item: input, text: preeditText, cursor: i);
3976 QCoreApplication::sendEvent(receiver: input, event: &query);
3977 currentRect = query.value(query: Qt::ImCursorRectangle).toRectF();
3978 QVERIFY(previousRect.left() < currentRect.left());
3979 QCOMPARE(input->cursorRectangle(), currentRect);
3980 QCOMPARE(cursor->position(), currentRect.topLeft());
3981 QVERIFY(inputSpy.count() > 0); inputSpy.clear();
3982 QVERIFY(panelSpy.count() > 0); panelSpy.clear();
3983 previousRect = currentRect;
3984 }
3985
3986 // Verify that if the cursor rectangle is updated if the pre-edit text changes
3987 // but the (non-zero) cursor position is the same.
3988 inputSpy.clear();
3989 panelSpy.clear();
3990 sendPreeditText(item: input, text: "wwwww", cursor: 5);
3991 QCoreApplication::sendEvent(receiver: input, event: &query);
3992 currentRect = query.value(query: Qt::ImCursorRectangle).toRectF();
3993 QCOMPARE(input->cursorRectangle(), currentRect);
3994 QCOMPARE(cursor->position(), currentRect.topLeft());
3995 QCOMPARE(inputSpy.count(), 1);
3996 QCOMPARE(panelSpy.count(), 1);
3997
3998 // Verify that if there is no preedit cursor then the micro focus rect is the
3999 // same as it would be if it were positioned at the end of the preedit text.
4000 inputSpy.clear();
4001 panelSpy.clear();
4002 { QInputMethodEvent imEvent(preeditText, QList<QInputMethodEvent::Attribute>());
4003 QCoreApplication::sendEvent(receiver: input, event: &imEvent); }
4004 QCoreApplication::sendEvent(receiver: input, event: &query);
4005 currentRect = query.value(query: Qt::ImCursorRectangle).toRectF();
4006 QCOMPARE(currentRect, previousRect);
4007 QCOMPARE(input->cursorRectangle(), currentRect);
4008 QCOMPARE(cursor->position(), currentRect.topLeft());
4009 QCOMPARE(inputSpy.count(), 1);
4010 QCOMPARE(panelSpy.count(), 1);
4011}
4012
4013void tst_qquicktextinput::inputContextMouseHandler()
4014{
4015 PlatformInputContext platformInputContext;
4016 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
4017 inputMethodPrivate->testContext = &platformInputContext;
4018
4019 QString text = "supercalifragisiticexpialidocious!";
4020 QQuickView view(testFileUrl(fileName: "inputContext.qml"));
4021 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: view.rootObject());
4022 QVERIFY(input);
4023
4024 input->setFocus(true);
4025 input->setText("");
4026
4027 view.showNormal();
4028 view.requestActivate();
4029 QVERIFY(QTest::qWaitForWindowActive(&view));
4030
4031 QTextLayout layout(text);
4032 layout.setFont(input->font());
4033 if (!qmlDisableDistanceField()) {
4034 QTextOption option;
4035 option.setUseDesignMetrics(true);
4036 layout.setTextOption(option);
4037 }
4038 layout.beginLayout();
4039 QTextLine line = layout.createLine();
4040 layout.endLayout();
4041
4042 const qreal x = line.cursorToX(cursorPos: 2, edge: QTextLine::Leading);
4043 const qreal y = line.height() / 2;
4044 QPoint position = QPointF(x, y).toPoint();
4045
4046 QInputMethodEvent inputEvent(text.mid(position: 0, n: 5), QList<QInputMethodEvent::Attribute>());
4047 QGuiApplication::sendEvent(receiver: input, event: &inputEvent);
4048
4049 QTest::mousePress(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: position);
4050 QTest::mouseRelease(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: position);
4051 QGuiApplication::processEvents();
4052
4053 QCOMPARE(platformInputContext.m_action, QInputMethod::Click);
4054 QCOMPARE(platformInputContext.m_invokeActionCallCount, 1);
4055 QCOMPARE(platformInputContext.m_cursorPosition, 2);
4056}
4057
4058void tst_qquicktextinput::inputMethodComposing()
4059{
4060 QString text = "supercalifragisiticexpialidocious!";
4061
4062 QQuickView view(testFileUrl(fileName: "inputContext.qml"));
4063 view.show();
4064 view.requestActivate();
4065 QVERIFY(QTest::qWaitForWindowActive(&view));
4066 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: view.rootObject());
4067 QVERIFY(input);
4068 QVERIFY(input->hasActiveFocus());
4069 QSignalSpy spy(input, SIGNAL(inputMethodComposingChanged()));
4070
4071 QCOMPARE(input->isInputMethodComposing(), false);
4072 {
4073 QInputMethodEvent event(text.mid(position: 3), QList<QInputMethodEvent::Attribute>());
4074 QGuiApplication::sendEvent(receiver: input, event: &event);
4075 }
4076 QCOMPARE(input->isInputMethodComposing(), true);
4077 QCOMPARE(spy.count(), 1);
4078
4079 {
4080 QInputMethodEvent event(text.mid(position: 12), QList<QInputMethodEvent::Attribute>());
4081 QGuiApplication::sendEvent(receiver: input, event: &event);
4082 }
4083 QCOMPARE(spy.count(), 1);
4084
4085 {
4086 QInputMethodEvent event;
4087 QGuiApplication::sendEvent(receiver: input, event: &event);
4088 }
4089 QCOMPARE(input->isInputMethodComposing(), false);
4090 QCOMPARE(spy.count(), 2);
4091
4092 // Changing the text while not composing doesn't alter the composing state.
4093 input->setText(text.mid(position: 0, n: 16));
4094 QCOMPARE(input->isInputMethodComposing(), false);
4095 QCOMPARE(spy.count(), 2);
4096
4097 {
4098 QInputMethodEvent event(text.mid(position: 16), QList<QInputMethodEvent::Attribute>());
4099 QGuiApplication::sendEvent(receiver: input, event: &event);
4100 }
4101 QCOMPARE(input->isInputMethodComposing(), true);
4102 QCOMPARE(spy.count(), 3);
4103
4104 // Changing the text while composing cancels composition.
4105 input->setText(text.mid(position: 0, n: 12));
4106 QCOMPARE(input->isInputMethodComposing(), false);
4107 QCOMPARE(spy.count(), 4);
4108
4109 { // Preedit cursor positioned outside (empty) preedit; composing.
4110 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
4111 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, -2, 1, QVariant()));
4112 QGuiApplication::sendEvent(receiver: input, event: &event);
4113 }
4114 QCOMPARE(input->isInputMethodComposing(), true);
4115 QCOMPARE(spy.count(), 5);
4116
4117
4118 { // Cursor hidden; composing
4119 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
4120 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 0, QVariant()));
4121 QGuiApplication::sendEvent(receiver: input, event: &event);
4122 }
4123 QCOMPARE(input->isInputMethodComposing(), true);
4124 QCOMPARE(spy.count(), 5);
4125
4126 { // Default cursor attributes; composing.
4127 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
4128 << QInputMethodEvent::Attribute(QInputMethodEvent::Cursor, 0, 1, QVariant()));
4129 QGuiApplication::sendEvent(receiver: input, event: &event);
4130 }
4131 QCOMPARE(input->isInputMethodComposing(), true);
4132 QCOMPARE(spy.count(), 5);
4133
4134 { // Selections are persisted: not composing
4135 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
4136 << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, -5, 4, QVariant()));
4137 QGuiApplication::sendEvent(receiver: input, event: &event);
4138 }
4139 QCOMPARE(input->isInputMethodComposing(), false);
4140 QCOMPARE(spy.count(), 6);
4141
4142 input->setCursorPosition(12);
4143
4144 { // Formatting applied; composing.
4145 QTextCharFormat format;
4146 format.setUnderlineStyle(QTextCharFormat::SingleUnderline);
4147 QInputMethodEvent event(QString(), QList<QInputMethodEvent::Attribute>()
4148 << QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, -5, 4, format));
4149 QGuiApplication::sendEvent(receiver: input, event: &event);
4150 }
4151 QCOMPARE(input->isInputMethodComposing(), true);
4152 QCOMPARE(spy.count(), 7);
4153
4154 {
4155 QInputMethodEvent event;
4156 QGuiApplication::sendEvent(receiver: input, event: &event);
4157 }
4158 QCOMPARE(input->isInputMethodComposing(), false);
4159 QCOMPARE(spy.count(), 8);
4160}
4161
4162void tst_qquicktextinput::inputMethodUpdate()
4163{
4164 PlatformInputContext platformInputContext;
4165 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
4166 inputMethodPrivate->testContext = &platformInputContext;
4167
4168 QQuickView view(testFileUrl(fileName: "inputContext.qml"));
4169 view.show();
4170 view.requestActivate();
4171 QVERIFY(QTest::qWaitForWindowActive(&view));
4172 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: view.rootObject());
4173 QVERIFY(input);
4174 QVERIFY(input->hasActiveFocus());
4175
4176 // text change even without cursor position change needs to trigger update
4177 input->setText("test");
4178 platformInputContext.clear();
4179 input->setText("xxxx");
4180 QVERIFY(platformInputContext.m_updateCallCount > 0);
4181
4182 // input method event replacing text
4183 platformInputContext.clear();
4184 {
4185 QInputMethodEvent inputMethodEvent;
4186 inputMethodEvent.setCommitString(commitString: "y", replaceFrom: -1, replaceLength: 1);
4187 QGuiApplication::sendEvent(receiver: input, event: &inputMethodEvent);
4188 }
4189 QVERIFY(platformInputContext.m_updateCallCount > 0);
4190
4191 // input method changing selection
4192 platformInputContext.clear();
4193 {
4194 QList<QInputMethodEvent::Attribute> attributes;
4195 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 0, 2, QVariant());
4196 QInputMethodEvent inputMethodEvent("", attributes);
4197 QGuiApplication::sendEvent(receiver: input, event: &inputMethodEvent);
4198 }
4199 QVERIFY(input->selectionStart() != input->selectionEnd());
4200 QVERIFY(platformInputContext.m_updateCallCount > 0);
4201
4202 // programmatical selections trigger update
4203 platformInputContext.clear();
4204 input->selectAll();
4205 QVERIFY(platformInputContext.m_updateCallCount > 0);
4206
4207 // font changes
4208 platformInputContext.clear();
4209 QFont font = input->font();
4210 font.setBold(!font.bold());
4211 input->setFont(font);
4212 QVERIFY(platformInputContext.m_updateCallCount > 0);
4213
4214 // normal input
4215 platformInputContext.clear();
4216 {
4217 QInputMethodEvent inputMethodEvent;
4218 inputMethodEvent.setCommitString(commitString: "y");
4219 QGuiApplication::sendEvent(receiver: input, event: &inputMethodEvent);
4220 }
4221 QVERIFY(platformInputContext.m_updateCallCount > 0);
4222
4223 // changing cursor position
4224 platformInputContext.clear();
4225 input->setCursorPosition(0);
4226 QVERIFY(platformInputContext.m_updateCallCount > 0);
4227
4228 // read only disabled input method
4229 platformInputContext.clear();
4230 input->setReadOnly(true);
4231 QVERIFY(platformInputContext.m_updateCallCount > 0);
4232 input->setReadOnly(false);
4233
4234 // no updates while no focus
4235 input->setFocus(false);
4236 platformInputContext.clear();
4237 input->setText("Foo");
4238 QCOMPARE(platformInputContext.m_updateCallCount, 0);
4239 input->setCursorPosition(1);
4240 QCOMPARE(platformInputContext.m_updateCallCount, 0);
4241 input->selectAll();
4242 QCOMPARE(platformInputContext.m_updateCallCount, 0);
4243 input->setReadOnly(true);
4244 QCOMPARE(platformInputContext.m_updateCallCount, 0);
4245}
4246
4247void tst_qquicktextinput::cursorRectangleSize()
4248{
4249 QQuickView *window = new QQuickView(testFileUrl(fileName: "positionAt.qml"));
4250 QVERIFY(window->rootObject() != nullptr);
4251 QQuickTextInput *textInput = qobject_cast<QQuickTextInput *>(object: window->rootObject());
4252
4253 // make sure cursor rectangle is not at (0,0)
4254 textInput->setX(10);
4255 textInput->setY(10);
4256 textInput->setCursorPosition(3);
4257 QVERIFY(textInput != nullptr);
4258 textInput->setFocus(true);
4259 window->show();
4260 window->requestActivate();
4261 QVERIFY(QTest::qWaitForWindowActive(window));
4262 QVERIFY(textInput->hasActiveFocus());
4263
4264 QInputMethodQueryEvent event(Qt::ImCursorRectangle);
4265 qApp->sendEvent(receiver: textInput, event: &event);
4266 QRectF cursorRectFromQuery = event.value(query: Qt::ImCursorRectangle).toRectF();
4267
4268 QRectF cursorRectFromItem = textInput->cursorRectangle();
4269 QRectF cursorRectFromPositionToRectangle = textInput->positionToRectangle(pos: textInput->cursorPosition());
4270
4271 // item and input query cursor rectangles match
4272 QCOMPARE(cursorRectFromItem, cursorRectFromQuery);
4273
4274 // item cursor rectangle and positionToRectangle calculations match
4275 QCOMPARE(cursorRectFromItem, cursorRectFromPositionToRectangle);
4276
4277 // item-window transform and input item transform match
4278 QCOMPARE(QQuickItemPrivate::get(textInput)->itemToWindowTransform(), qApp->inputMethod()->inputItemTransform());
4279
4280 // input panel cursorRectangle property and tranformed item cursor rectangle match
4281 QRectF sceneCursorRect = QQuickItemPrivate::get(item: textInput)->itemToWindowTransform().mapRect(cursorRectFromItem);
4282 QCOMPARE(sceneCursorRect, qApp->inputMethod()->cursorRectangle());
4283
4284 delete window;
4285}
4286
4287void tst_qquicktextinput::tripleClickSelectsAll()
4288{
4289 QString qmlfile = testFile(fileName: "positionAt.qml");
4290 QQuickView view(QUrl::fromLocalFile(localfile: qmlfile));
4291 view.show();
4292 view.requestActivate();
4293 QVERIFY(QTest::qWaitForWindowActive(&view));
4294
4295 QQuickTextInput* input = qobject_cast<QQuickTextInput*>(object: view.rootObject());
4296 QVERIFY(input);
4297
4298 QLatin1String hello("Hello world!");
4299 input->setSelectByMouse(true);
4300 input->setText(hello);
4301
4302 // Clicking on the same point inside TextInput three times in a row
4303 // should trigger a triple click, thus selecting all the text.
4304 QPoint pointInside = input->position().toPoint() + QPoint(2,2);
4305 QTest::mouseDClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: pointInside);
4306 QTest::mouseClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: pointInside);
4307 QGuiApplication::processEvents();
4308 QCOMPARE(input->selectedText(), hello);
4309
4310 // Now it simulates user moving the mouse between the second and the third click.
4311 // In this situation, we don't expect a triple click.
4312 QPoint pointInsideButFar = QPoint(input->width(),input->height()) - QPoint(2,2);
4313 QTest::mouseDClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: pointInside);
4314 QTest::mouseClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: pointInsideButFar);
4315 QGuiApplication::processEvents();
4316 QVERIFY(input->selectedText().isEmpty());
4317
4318 // And now we press the third click too late, so no triple click event is triggered.
4319 QTest::mouseDClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: pointInside);
4320 QGuiApplication::processEvents();
4321 QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 1);
4322 QTest::mouseClick(window: &view, button: Qt::LeftButton, stateKey: Qt::NoModifier, pos: pointInside);
4323 QGuiApplication::processEvents();
4324 QVERIFY(input->selectedText().isEmpty());
4325}
4326
4327void tst_qquicktextinput::QTBUG_19956_data()
4328{
4329 QTest::addColumn<QString>(name: "url");
4330 QTest::newRow(dataTag: "intvalidator") << "qtbug-19956int.qml";
4331 QTest::newRow(dataTag: "doublevalidator") << "qtbug-19956double.qml";
4332}
4333
4334
4335void tst_qquicktextinput::getText_data()
4336{
4337 QTest::addColumn<QString>(name: "text");
4338 QTest::addColumn<QString>(name: "inputMask");
4339 QTest::addColumn<int>(name: "start");
4340 QTest::addColumn<int>(name: "end");
4341 QTest::addColumn<QString>(name: "expectedText");
4342
4343 QTest::newRow(dataTag: "all plain text")
4344 << standard.at(i: 0)
4345 << QString()
4346 << 0 << standard.at(i: 0).length()
4347 << standard.at(i: 0);
4348
4349 QTest::newRow(dataTag: "plain text sub string")
4350 << standard.at(i: 0)
4351 << QString()
4352 << 0 << 12
4353 << standard.at(i: 0).mid(position: 0, n: 12);
4354
4355 QTest::newRow(dataTag: "plain text sub string reversed")
4356 << standard.at(i: 0)
4357 << QString()
4358 << 12 << 0
4359 << standard.at(i: 0).mid(position: 0, n: 12);
4360
4361 QTest::newRow(dataTag: "plain text cropped beginning")
4362 << standard.at(i: 0)
4363 << QString()
4364 << -3 << 4
4365 << standard.at(i: 0).mid(position: 0, n: 4);
4366
4367 QTest::newRow(dataTag: "plain text cropped end")
4368 << standard.at(i: 0)
4369 << QString()
4370 << 23 << standard.at(i: 0).length() + 8
4371 << standard.at(i: 0).mid(position: 23);
4372
4373 QTest::newRow(dataTag: "plain text cropped beginning and end")
4374 << standard.at(i: 0)
4375 << QString()
4376 << -9 << standard.at(i: 0).length() + 4
4377 << standard.at(i: 0);
4378}
4379
4380void tst_qquicktextinput::getText()
4381{
4382 QFETCH(QString, text);
4383 QFETCH(QString, inputMask);
4384 QFETCH(int, start);
4385 QFETCH(int, end);
4386 QFETCH(QString, expectedText);
4387
4388 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
4389 QQmlComponent textInputComponent(&engine);
4390 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
4391 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
4392 QVERIFY(textInput != nullptr);
4393
4394 QCOMPARE(textInput->getText(start, end), expectedText);
4395}
4396
4397void tst_qquicktextinput::insert_data()
4398{
4399 QTest::addColumn<QString>(name: "text");
4400 QTest::addColumn<QString>(name: "inputMask");
4401 QTest::addColumn<int>(name: "selectionStart");
4402 QTest::addColumn<int>(name: "selectionEnd");
4403 QTest::addColumn<int>(name: "insertPosition");
4404 QTest::addColumn<QString>(name: "insertText");
4405 QTest::addColumn<QString>(name: "expectedText");
4406 QTest::addColumn<int>(name: "expectedSelectionStart");
4407 QTest::addColumn<int>(name: "expectedSelectionEnd");
4408 QTest::addColumn<int>(name: "expectedCursorPosition");
4409 QTest::addColumn<bool>(name: "selectionChanged");
4410 QTest::addColumn<bool>(name: "cursorPositionChanged");
4411
4412 QTest::newRow(dataTag: "at cursor position (beginning)")
4413 << standard.at(i: 0)
4414 << QString()
4415 << 0 << 0 << 0
4416 << QString("Hello")
4417 << QString("Hello") + standard.at(i: 0)
4418 << 5 << 5 << 5
4419 << false << true;
4420
4421 QTest::newRow(dataTag: "at cursor position (end)")
4422 << standard.at(i: 0)
4423 << QString()
4424 << standard.at(i: 0).length() << standard.at(i: 0).length() << standard.at(i: 0).length()
4425 << QString("Hello")
4426 << standard.at(i: 0) + QString("Hello")
4427 << standard.at(i: 0).length() + 5 << standard.at(i: 0).length() + 5 << standard.at(i: 0).length() + 5
4428 << false << true;
4429
4430 QTest::newRow(dataTag: "at cursor position (middle)")
4431 << standard.at(i: 0)
4432 << QString()
4433 << 18 << 18 << 18
4434 << QString("Hello")
4435 << standard.at(i: 0).mid(position: 0, n: 18) + QString("Hello") + standard.at(i: 0).mid(position: 18)
4436 << 23 << 23 << 23
4437 << false << true;
4438
4439 QTest::newRow(dataTag: "after cursor position (beginning)")
4440 << standard.at(i: 0)
4441 << QString()
4442 << 0 << 0 << 18
4443 << QString("Hello")
4444 << standard.at(i: 0).mid(position: 0, n: 18) + QString("Hello") + standard.at(i: 0).mid(position: 18)
4445 << 0 << 0 << 0
4446 << false << false;
4447
4448 QTest::newRow(dataTag: "before cursor position (end)")
4449 << standard.at(i: 0)
4450 << QString()
4451 << standard.at(i: 0).length() << standard.at(i: 0).length() << 18
4452 << QString("Hello")
4453 << standard.at(i: 0).mid(position: 0, n: 18) + QString("Hello") + standard.at(i: 0).mid(position: 18)
4454 << standard.at(i: 0).length() + 5 << standard.at(i: 0).length() + 5 << standard.at(i: 0).length() + 5
4455 << false << true;
4456
4457 QTest::newRow(dataTag: "before cursor position (middle)")
4458 << standard.at(i: 0)
4459 << QString()
4460 << 18 << 18 << 0
4461 << QString("Hello")
4462 << QString("Hello") + standard.at(i: 0)
4463 << 23 << 23 << 23
4464 << false << true;
4465
4466 QTest::newRow(dataTag: "after cursor position (middle)")
4467 << standard.at(i: 0)
4468 << QString()
4469 << 18 << 18 << standard.at(i: 0).length()
4470 << QString("Hello")
4471 << standard.at(i: 0) + QString("Hello")
4472 << 18 << 18 << 18
4473 << false << false;
4474
4475 QTest::newRow(dataTag: "before selection")
4476 << standard.at(i: 0)
4477 << QString()
4478 << 14 << 19 << 0
4479 << QString("Hello")
4480 << QString("Hello") + standard.at(i: 0)
4481 << 19 << 24 << 24
4482 << false << true;
4483
4484 QTest::newRow(dataTag: "before reversed selection")
4485 << standard.at(i: 0)
4486 << QString()
4487 << 19 << 14 << 0
4488 << QString("Hello")
4489 << QString("Hello") + standard.at(i: 0)
4490 << 19 << 24 << 19
4491 << false << true;
4492
4493 QTest::newRow(dataTag: "after selection")
4494 << standard.at(i: 0)
4495 << QString()
4496 << 14 << 19 << standard.at(i: 0).length()
4497 << QString("Hello")
4498 << standard.at(i: 0) + QString("Hello")
4499 << 14 << 19 << 19
4500 << false << false;
4501
4502 QTest::newRow(dataTag: "after reversed selection")
4503 << standard.at(i: 0)
4504 << QString()
4505 << 19 << 14 << standard.at(i: 0).length()
4506 << QString("Hello")
4507 << standard.at(i: 0) + QString("Hello")
4508 << 14 << 19 << 14
4509 << false << false;
4510
4511 QTest::newRow(dataTag: "into selection")
4512 << standard.at(i: 0)
4513 << QString()
4514 << 14 << 19 << 18
4515 << QString("Hello")
4516 << standard.at(i: 0).mid(position: 0, n: 18) + QString("Hello") + standard.at(i: 0).mid(position: 18)
4517 << 14 << 24 << 24
4518 << true << true;
4519
4520 QTest::newRow(dataTag: "into reversed selection")
4521 << standard.at(i: 0)
4522 << QString()
4523 << 19 << 14 << 18
4524 << QString("Hello")
4525 << standard.at(i: 0).mid(position: 0, n: 18) + QString("Hello") + standard.at(i: 0).mid(position: 18)
4526 << 14 << 24 << 14
4527 << true << false;
4528
4529 QTest::newRow(dataTag: "rich text into plain text")
4530 << standard.at(i: 0)
4531 << QString()
4532 << 0 << 0 << 0
4533 << QString("<b>Hello</b>")
4534 << QString("<b>Hello</b>") + standard.at(i: 0)
4535 << 12 << 12 << 12
4536 << false << true;
4537
4538 QTest::newRow(dataTag: "before start")
4539 << standard.at(i: 0)
4540 << QString()
4541 << 0 << 0 << -3
4542 << QString("Hello")
4543 << standard.at(i: 0)
4544 << 0 << 0 << 0
4545 << false << false;
4546
4547 QTest::newRow(dataTag: "past end")
4548 << standard.at(i: 0)
4549 << QString()
4550 << 0 << 0 << standard.at(i: 0).length() + 3
4551 << QString("Hello")
4552 << standard.at(i: 0)
4553 << 0 << 0 << 0
4554 << false << false;
4555
4556 const QString inputMask = "009.009.009.009";
4557 const QString ip = "192.168.2.14";
4558
4559 QTest::newRow(dataTag: "mask: at cursor position (beginning)")
4560 << ip
4561 << inputMask
4562 << 0 << 0 << 0
4563 << QString("125")
4564 << QString("125.168.2.14")
4565 << 0 << 0 << 0
4566 << false << false;
4567
4568 QTest::newRow(dataTag: "mask: at cursor position (end)")
4569 << ip
4570 << inputMask
4571 << inputMask.length() << inputMask.length() << inputMask.length()
4572 << QString("8")
4573 << ip
4574 << inputMask.length() << inputMask.length() << inputMask.length()
4575 << false << false;
4576
4577 QTest::newRow(dataTag: "mask: at cursor position (middle)")
4578 << ip
4579 << inputMask
4580 << 6 << 6 << 6
4581 << QString("75.2")
4582 << QString("192.167.5.24")
4583 << 6 << 6 << 6
4584 << false << false;
4585
4586 QTest::newRow(dataTag: "mask: after cursor position (beginning)")
4587 << ip
4588 << inputMask
4589 << 0 << 0 << 6
4590 << QString("75.2")
4591 << QString("192.167.5.24")
4592 << 0 << 0 << 0
4593 << false << false;
4594
4595 QTest::newRow(dataTag: "mask: before cursor position (end)")
4596 << ip
4597 << inputMask
4598 << inputMask.length() << inputMask.length() << 6
4599 << QString("75.2")
4600 << QString("192.167.5.24")
4601 << inputMask.length() << inputMask.length() << inputMask.length()
4602 << false << false;
4603
4604 QTest::newRow(dataTag: "mask: before cursor position (middle)")
4605 << ip
4606 << inputMask
4607 << 6 << 6 << 0
4608 << QString("125")
4609 << QString("125.168.2.14")
4610 << 6 << 6 << 6
4611 << false << false;
4612
4613 QTest::newRow(dataTag: "mask: after cursor position (middle)")
4614 << ip
4615 << inputMask
4616 << 6 << 6 << 13
4617 << QString("8")
4618 << "192.168.2.18"
4619 << 6 << 6 << 6
4620 << false << false;
4621
4622 QTest::newRow(dataTag: "mask: before selection")
4623 << ip
4624 << inputMask
4625 << 6 << 8 << 0
4626 << QString("125")
4627 << QString("125.168.2.14")
4628 << 6 << 8 << 8
4629 << false << false;
4630
4631 QTest::newRow(dataTag: "mask: before reversed selection")
4632 << ip
4633 << inputMask
4634 << 8 << 6 << 0
4635 << QString("125")
4636 << QString("125.168.2.14")
4637 << 6 << 8 << 6
4638 << false << false;
4639
4640 QTest::newRow(dataTag: "mask: after selection")
4641 << ip
4642 << inputMask
4643 << 6 << 8 << 13
4644 << QString("8")
4645 << "192.168.2.18"
4646 << 6 << 8 << 8
4647 << false << false;
4648
4649 QTest::newRow(dataTag: "mask: after reversed selection")
4650 << ip
4651 << inputMask
4652 << 8 << 6 << 13
4653 << QString("8")
4654 << "192.168.2.18"
4655 << 6 << 8 << 6
4656 << false << false;
4657
4658 QTest::newRow(dataTag: "mask: into selection")
4659 << ip
4660 << inputMask
4661 << 5 << 8 << 6
4662 << QString("75.2")
4663 << QString("192.167.5.24")
4664 << 5 << 8 << 8
4665 << true << false;
4666
4667 QTest::newRow(dataTag: "mask: into reversed selection")
4668 << ip
4669 << inputMask
4670 << 8 << 5 << 6
4671 << QString("75.2")
4672 << QString("192.167.5.24")
4673 << 5 << 8 << 5
4674 << true << false;
4675
4676 QTest::newRow(dataTag: "mask: before start")
4677 << ip
4678 << inputMask
4679 << 0 << 0 << -3
4680 << QString("4")
4681 << ip
4682 << 0 << 0 << 0
4683 << false << false;
4684
4685 QTest::newRow(dataTag: "mask: past end")
4686 << ip
4687 << inputMask
4688 << 0 << 0 << ip.length() + 3
4689 << QString("4")
4690 << ip
4691 << 0 << 0 << 0
4692 << false << false;
4693
4694 QTest::newRow(dataTag: "mask: invalid characters")
4695 << ip
4696 << inputMask
4697 << 0 << 0 << 0
4698 << QString("abc")
4699 << QString("192.168.2.14")
4700 << 0 << 0 << 0
4701 << false << false;
4702
4703 QTest::newRow(dataTag: "mask: mixed validity")
4704 << ip
4705 << inputMask
4706 << 0 << 0 << 0
4707 << QString("a1b2c5")
4708 << QString("125.168.2.14")
4709 << 0 << 0 << 0
4710 << false << false;
4711}
4712
4713void tst_qquicktextinput::insert()
4714{
4715 QFETCH(QString, text);
4716 QFETCH(QString, inputMask);
4717 QFETCH(int, selectionStart);
4718 QFETCH(int, selectionEnd);
4719 QFETCH(int, insertPosition);
4720 QFETCH(QString, insertText);
4721 QFETCH(QString, expectedText);
4722 QFETCH(int, expectedSelectionStart);
4723 QFETCH(int, expectedSelectionEnd);
4724 QFETCH(int, expectedCursorPosition);
4725 QFETCH(bool, selectionChanged);
4726 QFETCH(bool, cursorPositionChanged);
4727
4728 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
4729 QQmlComponent textInputComponent(&engine);
4730 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
4731 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
4732 QVERIFY(textInput != nullptr);
4733
4734 textInput->select(start: selectionStart, end: selectionEnd);
4735
4736 QSignalSpy selectionSpy(textInput, SIGNAL(selectedTextChanged()));
4737 QSignalSpy selectionStartSpy(textInput, SIGNAL(selectionStartChanged()));
4738 QSignalSpy selectionEndSpy(textInput, SIGNAL(selectionEndChanged()));
4739 QSignalSpy textSpy(textInput, SIGNAL(textChanged()));
4740 QSignalSpy cursorPositionSpy(textInput, SIGNAL(cursorPositionChanged()));
4741
4742 textInput->insert(position: insertPosition, text: insertText);
4743
4744 QCOMPARE(textInput->text(), expectedText);
4745 QCOMPARE(textInput->length(), inputMask.isEmpty() ? expectedText.length() : inputMask.length());
4746
4747 QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
4748 QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
4749 QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);
4750
4751 if (selectionStart > selectionEnd)
4752 qSwap(value1&: selectionStart, value2&: selectionEnd);
4753
4754 QCOMPARE(selectionSpy.count() > 0, selectionChanged);
4755 QCOMPARE(selectionStartSpy.count() > 0, selectionStart != expectedSelectionStart);
4756 QCOMPARE(selectionEndSpy.count() > 0, selectionEnd != expectedSelectionEnd);
4757 QCOMPARE(textSpy.count() > 0, text != expectedText);
4758 QCOMPARE(cursorPositionSpy.count() > 0, cursorPositionChanged);
4759}
4760
4761void tst_qquicktextinput::remove_data()
4762{
4763 QTest::addColumn<QString>(name: "text");
4764 QTest::addColumn<QString>(name: "inputMask");
4765 QTest::addColumn<int>(name: "selectionStart");
4766 QTest::addColumn<int>(name: "selectionEnd");
4767 QTest::addColumn<int>(name: "removeStart");
4768 QTest::addColumn<int>(name: "removeEnd");
4769 QTest::addColumn<QString>(name: "expectedText");
4770 QTest::addColumn<int>(name: "expectedSelectionStart");
4771 QTest::addColumn<int>(name: "expectedSelectionEnd");
4772 QTest::addColumn<int>(name: "expectedCursorPosition");
4773 QTest::addColumn<bool>(name: "selectionChanged");
4774 QTest::addColumn<bool>(name: "cursorPositionChanged");
4775
4776 QTest::newRow(dataTag: "from cursor position (beginning)")
4777 << standard.at(i: 0)
4778 << QString()
4779 << 0 << 0
4780 << 0 << 5
4781 << standard.at(i: 0).mid(position: 5)
4782 << 0 << 0 << 0
4783 << false << false;
4784
4785 QTest::newRow(dataTag: "to cursor position (beginning)")
4786 << standard.at(i: 0)
4787 << QString()
4788 << 0 << 0
4789 << 5 << 0
4790 << standard.at(i: 0).mid(position: 5)
4791 << 0 << 0 << 0
4792 << false << false;
4793
4794 QTest::newRow(dataTag: "to cursor position (end)")
4795 << standard.at(i: 0)
4796 << QString()
4797 << standard.at(i: 0).length() << standard.at(i: 0).length()
4798 << standard.at(i: 0).length() << standard.at(i: 0).length() - 5
4799 << standard.at(i: 0).mid(position: 0, n: standard.at(i: 0).length() - 5)
4800 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length() - 5
4801 << false << true;
4802
4803 QTest::newRow(dataTag: "to cursor position (end)")
4804 << standard.at(i: 0)
4805 << QString()
4806 << standard.at(i: 0).length() << standard.at(i: 0).length()
4807 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length()
4808 << standard.at(i: 0).mid(position: 0, n: standard.at(i: 0).length() - 5)
4809 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length() - 5
4810 << false << true;
4811
4812 QTest::newRow(dataTag: "from cursor position (middle)")
4813 << standard.at(i: 0)
4814 << QString()
4815 << 18 << 18
4816 << 18 << 23
4817 << standard.at(i: 0).mid(position: 0, n: 18) + standard.at(i: 0).mid(position: 23)
4818 << 18 << 18 << 18
4819 << false << false;
4820
4821 QTest::newRow(dataTag: "to cursor position (middle)")
4822 << standard.at(i: 0)
4823 << QString()
4824 << 23 << 23
4825 << 18 << 23
4826 << standard.at(i: 0).mid(position: 0, n: 18) + standard.at(i: 0).mid(position: 23)
4827 << 18 << 18 << 18
4828 << false << true;
4829
4830 QTest::newRow(dataTag: "after cursor position (beginning)")
4831 << standard.at(i: 0)
4832 << QString()
4833 << 0 << 0
4834 << 18 << 23
4835 << standard.at(i: 0).mid(position: 0, n: 18) + standard.at(i: 0).mid(position: 23)
4836 << 0 << 0 << 0
4837 << false << false;
4838
4839 QTest::newRow(dataTag: "before cursor position (end)")
4840 << standard.at(i: 0)
4841 << QString()
4842 << standard.at(i: 0).length() << standard.at(i: 0).length()
4843 << 18 << 23
4844 << standard.at(i: 0).mid(position: 0, n: 18) + standard.at(i: 0).mid(position: 23)
4845 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length() - 5
4846 << false << true;
4847
4848 QTest::newRow(dataTag: "before cursor position (middle)")
4849 << standard.at(i: 0)
4850 << QString()
4851 << 23 << 23
4852 << 0 << 5
4853 << standard.at(i: 0).mid(position: 5)
4854 << 18 << 18 << 18
4855 << false << true;
4856
4857 QTest::newRow(dataTag: "after cursor position (middle)")
4858 << standard.at(i: 0)
4859 << QString()
4860 << 18 << 18
4861 << 18 << 23
4862 << standard.at(i: 0).mid(position: 0, n: 18) + standard.at(i: 0).mid(position: 23)
4863 << 18 << 18 << 18
4864 << false << false;
4865
4866 QTest::newRow(dataTag: "before selection")
4867 << standard.at(i: 0)
4868 << QString()
4869 << 14 << 19
4870 << 0 << 5
4871 << standard.at(i: 0).mid(position: 5)
4872 << 9 << 14 << 14
4873 << false << true;
4874
4875 QTest::newRow(dataTag: "before reversed selection")
4876 << standard.at(i: 0)
4877 << QString()
4878 << 19 << 14
4879 << 0 << 5
4880 << standard.at(i: 0).mid(position: 5)
4881 << 9 << 14 << 9
4882 << false << true;
4883
4884 QTest::newRow(dataTag: "after selection")
4885 << standard.at(i: 0)
4886 << QString()
4887 << 14 << 19
4888 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length()
4889 << standard.at(i: 0).mid(position: 0, n: standard.at(i: 0).length() - 5)
4890 << 14 << 19 << 19
4891 << false << false;
4892
4893 QTest::newRow(dataTag: "after reversed selection")
4894 << standard.at(i: 0)
4895 << QString()
4896 << 19 << 14
4897 << standard.at(i: 0).length() - 5 << standard.at(i: 0).length()
4898 << standard.at(i: 0).mid(position: 0, n: standard.at(i: 0).length() - 5)
4899 << 14 << 19 << 14
4900 << false << false;
4901
4902 QTest::newRow(dataTag: "from selection")
4903 << standard.at(i: 0)
4904 << QString()
4905 << 14 << 24
4906 << 18 << 23
4907 << standard.at(i: 0).mid(position: 0, n: 18) + standard.at(i: 0).mid(position: 23)
4908 << 14 << 19 << 19
4909 << true << true;
4910
4911 QTest::newRow(dataTag: "from reversed selection")
4912 << standard.at(i: 0)
4913 << QString()
4914 << 24 << 14
4915 << 18 << 23
4916 << standard.at(i: 0).mid(position: 0, n: 18) + standard.at(i: 0).mid(position: 23)
4917 << 14 << 19 << 14
4918 << true << false;
4919
4920 QTest::newRow(dataTag: "cropped beginning")
4921 << standard.at(i: 0)
4922 << QString()
4923 << 0 << 0
4924 << -3 << 4
4925 << standard.at(i: 0).mid(position: 4)
4926 << 0 << 0 << 0
4927 << false << false;
4928
4929 QTest::newRow(dataTag: "cropped end")
4930 << standard.at(i: 0)
4931 << QString()
4932 << 0 << 0
4933 << 23 << standard.at(i: 0).length() + 8
4934 << standard.at(i: 0).mid(position: 0, n: 23)
4935 << 0 << 0 << 0
4936 << false << false;
4937
4938 QTest::newRow(dataTag: "cropped beginning and end")
4939 << standard.at(i: 0)
4940 << QString()
4941 << 0 << 0
4942 << -9 << standard.at(i: 0).length() + 4
4943 << QString()
4944 << 0 << 0 << 0
4945 << false << false;
4946
4947 const QString inputMask = "009.009.009.009";
4948 const QString ip = "192.168.2.14";
4949
4950 QTest::newRow(dataTag: "mask: from cursor position")
4951 << ip
4952 << inputMask
4953 << 6 << 6
4954 << 6 << 9
4955 << QString("192.16..14")
4956 << 6 << 6 << 6
4957 << false << false;
4958
4959 QTest::newRow(dataTag: "mask: to cursor position")
4960 << ip
4961 << inputMask
4962 << 6 << 6
4963 << 2 << 6
4964 << QString("19.8.2.14")
4965 << 6 << 6 << 6
4966 << false << false;
4967
4968 QTest::newRow(dataTag: "mask: before cursor position")
4969 << ip
4970 << inputMask
4971 << 6 << 6
4972 << 0 << 2
4973 << QString("2.168.2.14")
4974 << 6 << 6 << 6
4975 << false << false;
4976
4977 QTest::newRow(dataTag: "mask: after cursor position")
4978 << ip
4979 << inputMask
4980 << 6 << 6
4981 << 12 << 16
4982 << QString("192.168.2.")
4983 << 6 << 6 << 6
4984 << false << false;
4985
4986 QTest::newRow(dataTag: "mask: before selection")
4987 << ip
4988 << inputMask
4989 << 6 << 8
4990 << 0 << 2
4991 << QString("2.168.2.14")
4992 << 6 << 8 << 8
4993 << false << false;
4994
4995 QTest::newRow(dataTag: "mask: before reversed selection")
4996 << ip
4997 << inputMask
4998 << 8 << 6
4999 << 0 << 2
5000 << QString("2.168.2.14")
5001 << 6 << 8 << 6
5002 << false << false;
5003
5004 QTest::newRow(dataTag: "mask: after selection")
5005 << ip
5006 << inputMask
5007 << 6 << 8
5008 << 12 << 16
5009 << QString("192.168.2.")
5010 << 6 << 8 << 8
5011 << false << false;
5012
5013 QTest::newRow(dataTag: "mask: after reversed selection")
5014 << ip
5015 << inputMask
5016 << 8 << 6
5017 << 12 << 16
5018 << QString("192.168.2.")
5019 << 6 << 8 << 6
5020 << false << false;
5021
5022 QTest::newRow(dataTag: "mask: from selection")
5023 << ip
5024 << inputMask
5025 << 6 << 13
5026 << 8 << 10
5027 << QString("192.168..14")
5028 << 6 << 13 << 13
5029 << true << false;
5030
5031 QTest::newRow(dataTag: "mask: from reversed selection")
5032 << ip
5033 << inputMask
5034 << 13 << 6
5035 << 8 << 10
5036 << QString("192.168..14")
5037 << 6 << 13 << 6
5038 << true << false;
5039
5040 QTest::newRow(dataTag: "mask: cropped beginning")
5041 << ip
5042 << inputMask
5043 << 0 << 0
5044 << -3 << 4
5045 << QString(".168.2.14")
5046 << 0 << 0 << 0
5047 << false << false;
5048
5049 QTest::newRow(dataTag: "mask: cropped end")
5050 << ip
5051 << inputMask
5052 << 0 << 0
5053 << 13 << 28
5054 << QString("192.168.2.1")
5055 << 0 << 0 << 0
5056 << false << false;
5057
5058 QTest::newRow(dataTag: "mask: cropped beginning and end")
5059 << ip
5060 << inputMask
5061 << 0 << 0
5062 << -9 << 28
5063 << QString("...")
5064 << 0 << 0 << 0
5065 << false << false;
5066}
5067
5068void tst_qquicktextinput::remove()
5069{
5070 QFETCH(QString, text);
5071 QFETCH(QString, inputMask);
5072 QFETCH(int, selectionStart);
5073 QFETCH(int, selectionEnd);
5074 QFETCH(int, removeStart);
5075 QFETCH(int, removeEnd);
5076 QFETCH(QString, expectedText);
5077 QFETCH(int, expectedSelectionStart);
5078 QFETCH(int, expectedSelectionEnd);
5079 QFETCH(int, expectedCursorPosition);
5080 QFETCH(bool, selectionChanged);
5081 QFETCH(bool, cursorPositionChanged);
5082
5083 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; inputMask: \"" + inputMask + "\" }";
5084 QQmlComponent textInputComponent(&engine);
5085 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
5086 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
5087 QVERIFY(textInput != nullptr);
5088
5089 textInput->select(start: selectionStart, end: selectionEnd);
5090
5091 QSignalSpy selectionSpy(textInput, SIGNAL(selectedTextChanged()));
5092 QSignalSpy selectionStartSpy(textInput, SIGNAL(selectionStartChanged()));
5093 QSignalSpy selectionEndSpy(textInput, SIGNAL(selectionEndChanged()));
5094 QSignalSpy textSpy(textInput, SIGNAL(textChanged()));
5095 QSignalSpy cursorPositionSpy(textInput, SIGNAL(cursorPositionChanged()));
5096
5097 textInput->remove(start: removeStart, end: removeEnd);
5098
5099 QCOMPARE(textInput->text(), expectedText);
5100 QCOMPARE(textInput->length(), inputMask.isEmpty() ? expectedText.length() : inputMask.length());
5101
5102 if (selectionStart > selectionEnd) //
5103 qSwap(value1&: selectionStart, value2&: selectionEnd);
5104
5105 QCOMPARE(textInput->selectionStart(), expectedSelectionStart);
5106 QCOMPARE(textInput->selectionEnd(), expectedSelectionEnd);
5107 QCOMPARE(textInput->cursorPosition(), expectedCursorPosition);
5108
5109 QCOMPARE(selectionSpy.count() > 0, selectionChanged);
5110 QCOMPARE(selectionStartSpy.count() > 0, selectionStart != expectedSelectionStart);
5111 QCOMPARE(selectionEndSpy.count() > 0, selectionEnd != expectedSelectionEnd);
5112 QCOMPARE(textSpy.count() > 0, text != expectedText);
5113
5114 if (cursorPositionChanged) //
5115 QVERIFY(cursorPositionSpy.count() > 0);
5116}
5117
5118#if QT_CONFIG(shortcut)
5119void tst_qquicktextinput::keySequence_data()
5120{
5121 QTest::addColumn<QString>(name: "text");
5122 QTest::addColumn<QKeySequence>(name: "sequence");
5123 QTest::addColumn<int>(name: "selectionStart");
5124 QTest::addColumn<int>(name: "selectionEnd");
5125 QTest::addColumn<int>(name: "cursorPosition");
5126 QTest::addColumn<QString>(name: "expectedText");
5127 QTest::addColumn<QString>(name: "selectedText");
5128 QTest::addColumn<QQuickTextInput::EchoMode>(name: "echoMode");
5129 QTest::addColumn<Qt::Key>(name: "layoutDirection");
5130
5131 // standard[0] == "the [4]quick [10]brown [16]fox [20]jumped [27]over [32]the [36]lazy [41]dog"
5132
5133 QTest::newRow(dataTag: "select all")
5134 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectAll) << 0 << 0
5135 << 44 << standard.at(i: 0) << standard.at(i: 0)
5136 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5137 QTest::newRow(dataTag: "select start of line")
5138 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectStartOfLine) << 5 << 5
5139 << 0 << standard.at(i: 0) << standard.at(i: 0).mid(position: 0, n: 5)
5140 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5141 QTest::newRow(dataTag: "select start of block")
5142 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectStartOfBlock) << 5 << 5
5143 << 0 << standard.at(i: 0) << standard.at(i: 0).mid(position: 0, n: 5)
5144 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5145 QTest::newRow(dataTag: "select end of line")
5146 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectEndOfLine) << 5 << 5
5147 << 44 << standard.at(i: 0) << standard.at(i: 0).mid(position: 5)
5148 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5149 QTest::newRow(dataTag: "select end of document") // ### Not handled.
5150 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectEndOfDocument) << 3 << 3
5151 << 3 << standard.at(i: 0) << QString()
5152 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5153 QTest::newRow(dataTag: "select end of block")
5154 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectEndOfBlock) << 18 << 18
5155 << 44 << standard.at(i: 0) << standard.at(i: 0).mid(position: 18)
5156 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5157 QTest::newRow(dataTag: "delete end of line")
5158 << standard.at(i: 0) << QKeySequence(QKeySequence::DeleteEndOfLine) << 24 << 24
5159 << 24 << standard.at(i: 0).mid(position: 0, n: 24) << QString()
5160 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5161 QTest::newRow(dataTag: "move to start of line")
5162 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToStartOfLine) << 31 << 31
5163 << 0 << standard.at(i: 0) << QString()
5164 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5165 QTest::newRow(dataTag: "move to start of block")
5166 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToStartOfBlock) << 25 << 25
5167 << 0 << standard.at(i: 0) << QString()
5168 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5169 QTest::newRow(dataTag: "move to next char")
5170 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToNextChar) << 12 << 12
5171 << 13 << standard.at(i: 0) << QString()
5172 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5173 QTest::newRow(dataTag: "move to previous char (ltr)")
5174 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 3
5175 << 2 << standard.at(i: 0) << QString()
5176 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5177 QTest::newRow(dataTag: "move to previous char (rtl)")
5178 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 3
5179 << 4 << standard.at(i: 0) << QString()
5180 << QQuickTextInput::Normal << Qt::Key_Direction_R;
5181 QTest::newRow(dataTag: "move to previous char with selection")
5182 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToPreviousChar) << 3 << 7
5183 << 3 << standard.at(i: 0) << QString()
5184 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5185 QTest::newRow(dataTag: "select next char (ltr)")
5186 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectNextChar) << 23 << 23
5187 << 24 << standard.at(i: 0) << standard.at(i: 0).mid(position: 23, n: 1)
5188 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5189 QTest::newRow(dataTag: "select next char (rtl)")
5190 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectNextChar) << 23 << 23
5191 << 22 << standard.at(i: 0) << standard.at(i: 0).mid(position: 22, n: 1)
5192 << QQuickTextInput::Normal << Qt::Key_Direction_R;
5193 QTest::newRow(dataTag: "select previous char (ltr)")
5194 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectPreviousChar) << 19 << 19
5195 << 18 << standard.at(i: 0) << standard.at(i: 0).mid(position: 18, n: 1)
5196 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5197 QTest::newRow(dataTag: "select previous char (rtl)")
5198 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectPreviousChar) << 19 << 19
5199 << 20 << standard.at(i: 0) << standard.at(i: 0).mid(position: 19, n: 1)
5200 << QQuickTextInput::Normal << Qt::Key_Direction_R;
5201 QTest::newRow(dataTag: "move to next word (ltr)")
5202 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
5203 << 10 << standard.at(i: 0) << QString()
5204 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5205 QTest::newRow(dataTag: "move to next word (rtl)")
5206 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
5207 << 4 << standard.at(i: 0) << QString()
5208 << QQuickTextInput::Normal << Qt::Key_Direction_R;
5209 QTest::newRow(dataTag: "move to next word (password,ltr)")
5210 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
5211 << 44 << standard.at(i: 0) << QString()
5212 << QQuickTextInput::Password << Qt::Key_Direction_L;
5213 QTest::newRow(dataTag: "move to next word (password,rtl)")
5214 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToNextWord) << 7 << 7
5215 << 0 << standard.at(i: 0) << QString()
5216 << QQuickTextInput::Password << Qt::Key_Direction_R;
5217 QTest::newRow(dataTag: "move to previous word (ltr)")
5218 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
5219 << 4 << standard.at(i: 0) << QString()
5220 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5221 QTest::newRow(dataTag: "move to previous word (rlt)")
5222 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
5223 << 10 << standard.at(i: 0) << QString()
5224 << QQuickTextInput::Normal << Qt::Key_Direction_R;
5225 QTest::newRow(dataTag: "move to previous word (password,ltr)")
5226 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
5227 << 0 << standard.at(i: 0) << QString()
5228 << QQuickTextInput::Password << Qt::Key_Direction_L;
5229 QTest::newRow(dataTag: "move to previous word (password,rtl)")
5230 << standard.at(i: 0) << QKeySequence(QKeySequence::MoveToPreviousWord) << 7 << 7
5231 << 44 << standard.at(i: 0) << QString()
5232 << QQuickTextInput::Password << Qt::Key_Direction_R;
5233 QTest::newRow(dataTag: "select next word")
5234 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectNextWord) << 11 << 11
5235 << 16 << standard.at(i: 0) << standard.at(i: 0).mid(position: 11, n: 5)
5236 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5237 QTest::newRow(dataTag: "select previous word")
5238 << standard.at(i: 0) << QKeySequence(QKeySequence::SelectPreviousWord) << 11 << 11
5239 << 10 << standard.at(i: 0) << standard.at(i: 0).mid(position: 10, n: 1)
5240 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5241 QTest::newRow(dataTag: "delete (selection)")
5242 << standard.at(i: 0) << QKeySequence(QKeySequence::Delete) << 12 << 15
5243 << 12 << (standard.at(i: 0).mid(position: 0, n: 12) + standard.at(i: 0).mid(position: 15)) << QString()
5244 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5245 QTest::newRow(dataTag: "delete (no selection)")
5246 << standard.at(i: 0) << QKeySequence(QKeySequence::Delete) << 15 << 15
5247 << 15 << (standard.at(i: 0).mid(position: 0, n: 15) + standard.at(i: 0).mid(position: 16)) << QString()
5248 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5249 QTest::newRow(dataTag: "delete end of word")
5250 << standard.at(i: 0) << QKeySequence(QKeySequence::DeleteEndOfWord) << 24 << 24
5251 << 24 << (standard.at(i: 0).mid(position: 0, n: 24) + standard.at(i: 0).mid(position: 27)) << QString()
5252 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5253 QTest::newRow(dataTag: "delete start of word")
5254 << standard.at(i: 0) << QKeySequence(QKeySequence::DeleteStartOfWord) << 7 << 7
5255 << 4 << (standard.at(i: 0).mid(position: 0, n: 4) + standard.at(i: 0).mid(position: 7)) << QString()
5256 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5257 QTest::newRow(dataTag: "delete complete line")
5258 << standard.at(i: 0) << QKeySequence(QKeySequence::DeleteCompleteLine) << 0 << 0
5259 << 0 << QString() << QString()
5260 << QQuickTextInput::Normal << Qt::Key_Direction_L;
5261}
5262
5263void tst_qquicktextinput::keySequence()
5264{
5265 QFETCH(QString, text);
5266 QFETCH(QKeySequence, sequence);
5267 QFETCH(int, selectionStart);
5268 QFETCH(int, selectionEnd);
5269 QFETCH(int, cursorPosition);
5270 QFETCH(QString, expectedText);
5271 QFETCH(QString, selectedText);
5272 QFETCH(QQuickTextInput::EchoMode, echoMode);
5273 QFETCH(Qt::Key, layoutDirection);
5274
5275 if (sequence.isEmpty()) {
5276 QSKIP("Key sequence is undefined");
5277 }
5278
5279 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; text: \"" + text + "\" }";
5280 QQmlComponent textInputComponent(&engine);
5281 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
5282 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
5283 QVERIFY(textInput != nullptr);
5284 textInput->setEchoMode(echoMode);
5285
5286 QQuickWindow window;
5287 textInput->setParentItem(window.contentItem());
5288 window.show();
5289 window.requestActivate();
5290 QVERIFY(QTest::qWaitForWindowActive(&window));
5291 QVERIFY(textInput->hasActiveFocus());
5292
5293 simulateKey(view: &window, key: layoutDirection);
5294
5295 textInput->select(start: selectionStart, end: selectionEnd);
5296
5297 simulateKeys(window: &window, sequence);
5298
5299 QCOMPARE(textInput->cursorPosition(), cursorPosition);
5300 QCOMPARE(textInput->text(), expectedText);
5301 QCOMPARE(textInput->selectedText(), selectedText);
5302}
5303
5304#endif // QT_CONFIG(shortcut)
5305
5306#define NORMAL 0
5307#define REPLACE_UNTIL_END 1
5308
5309void tst_qquicktextinput::undo_data()
5310{
5311 QTest::addColumn<QStringList>(name: "insertString");
5312 QTest::addColumn<IntList>(name: "insertIndex");
5313 QTest::addColumn<IntList>(name: "insertMode");
5314 QTest::addColumn<QStringList>(name: "expectedString");
5315 QTest::addColumn<bool>(name: "use_keys");
5316
5317 for (int i=0; i<2; i++) {
5318 QString keys_str = "keyboard";
5319 bool use_keys = true;
5320 if (i==0) {
5321 keys_str = "insert";
5322 use_keys = false;
5323 }
5324
5325 {
5326 IntList insertIndex;
5327 IntList insertMode;
5328 QStringList insertString;
5329 QStringList expectedString;
5330
5331 insertIndex << -1;
5332 insertMode << NORMAL;
5333 insertString << "1";
5334
5335 insertIndex << -1;
5336 insertMode << NORMAL;
5337 insertString << "5";
5338
5339 insertIndex << 1;
5340 insertMode << NORMAL;
5341 insertString << "3";
5342
5343 insertIndex << 1;
5344 insertMode << NORMAL;
5345 insertString << "2";
5346
5347 insertIndex << 3;
5348 insertMode << NORMAL;
5349 insertString << "4";
5350
5351 expectedString << "12345";
5352 expectedString << "1235";
5353 expectedString << "135";
5354 expectedString << "15";
5355 expectedString << "";
5356
5357 QTest::newRow(dataTag: QString(keys_str + "_numbers").toLatin1()) <<
5358 insertString <<
5359 insertIndex <<
5360 insertMode <<
5361 expectedString <<
5362 bool(use_keys);
5363 }
5364 {
5365 IntList insertIndex;
5366 IntList insertMode;
5367 QStringList insertString;
5368 QStringList expectedString;
5369
5370 insertIndex << -1;
5371 insertMode << NORMAL;
5372 insertString << "World"; // World
5373
5374 insertIndex << 0;
5375 insertMode << NORMAL;
5376 insertString << "Hello"; // HelloWorld
5377
5378 insertIndex << 0;
5379 insertMode << NORMAL;
5380 insertString << "Well"; // WellHelloWorld
5381
5382 insertIndex << 9;
5383 insertMode << NORMAL;
5384 insertString << "There"; // WellHelloThereWorld;
5385
5386 expectedString << "WellHelloThereWorld";
5387 expectedString << "WellHelloWorld";
5388 expectedString << "HelloWorld";
5389 expectedString << "World";
5390 expectedString << "";
5391
5392 QTest::newRow(dataTag: QString(keys_str + "_helloworld").toLatin1()) <<
5393 insertString <<
5394 insertIndex <<
5395 insertMode <<
5396 expectedString <<
5397 bool(use_keys);
5398 }
5399 {
5400 IntList insertIndex;
5401 IntList insertMode;
5402 QStringList insertString;
5403 QStringList expectedString;
5404
5405 insertIndex << -1;
5406 insertMode << NORMAL;
5407 insertString << "Ensuring";
5408
5409 insertIndex << -1;
5410 insertMode << NORMAL;
5411 insertString << " instan";
5412
5413 insertIndex << 9;
5414 insertMode << NORMAL;
5415 insertString << "an ";
5416
5417 insertIndex << 10;
5418 insertMode << REPLACE_UNTIL_END;
5419 insertString << " unique instance.";
5420
5421 expectedString << "Ensuring a unique instance.";
5422 expectedString << "Ensuring an instan";
5423 expectedString << "Ensuring instan";
5424 expectedString << "";
5425
5426 QTest::newRow(dataTag: QString(keys_str + "_patterns").toLatin1()) <<
5427 insertString <<
5428 insertIndex <<
5429 insertMode <<
5430 expectedString <<
5431 bool(use_keys);
5432 }
5433 }
5434}
5435
5436void tst_qquicktextinput::undo()
5437{
5438 QFETCH(QStringList, insertString);
5439 QFETCH(IntList, insertIndex);
5440 QFETCH(IntList, insertMode);
5441 QFETCH(QStringList, expectedString);
5442
5443 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
5444 QQmlComponent textInputComponent(&engine);
5445 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
5446 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
5447 QVERIFY(textInput != nullptr);
5448
5449 QQuickWindow window;
5450 textInput->setParentItem(window.contentItem());
5451 window.show();
5452 window.requestActivate();
5453 QVERIFY(QTest::qWaitForWindowActive(&window));
5454 QVERIFY(textInput->hasActiveFocus());
5455
5456 QVERIFY(!textInput->canUndo());
5457
5458 QSignalSpy spy(textInput, SIGNAL(canUndoChanged()));
5459
5460 int i;
5461
5462// STEP 1: First build up an undo history by inserting or typing some strings...
5463 for (i = 0; i < insertString.size(); ++i) {
5464 if (insertIndex[i] > -1)
5465 textInput->setCursorPosition(insertIndex[i]);
5466
5467 // experimental stuff
5468 if (insertMode[i] == REPLACE_UNTIL_END) {
5469 textInput->select(start: insertIndex[i], end: insertIndex[i] + 8);
5470
5471 // This is what I actually want...
5472 // QTest::keyClick(testWidget, Qt::Key_End, Qt::ShiftModifier);
5473 }
5474
5475 for (int j = 0; j < insertString.at(i).length(); j++)
5476 QTest::keyClick(window: &window, key: insertString.at(i).at(i: j).toLatin1());
5477 }
5478
5479 QCOMPARE(spy.count(), 1);
5480
5481// STEP 2: Next call undo several times and see if we can restore to the previous state
5482 for (i = 0; i < expectedString.size() - 1; ++i) {
5483 QCOMPARE(textInput->text(), expectedString[i]);
5484 QVERIFY(textInput->canUndo());
5485 textInput->undo();
5486 }
5487
5488// STEP 3: Verify that we have undone everything
5489 QVERIFY(textInput->text().isEmpty());
5490 QVERIFY(!textInput->canUndo());
5491 QCOMPARE(spy.count(), 2);
5492}
5493
5494void tst_qquicktextinput::redo_data()
5495{
5496 QTest::addColumn<QStringList>(name: "insertString");
5497 QTest::addColumn<IntList>(name: "insertIndex");
5498 QTest::addColumn<QStringList>(name: "expectedString");
5499
5500 {
5501 IntList insertIndex;
5502 QStringList insertString;
5503 QStringList expectedString;
5504
5505 insertIndex << -1;
5506 insertString << "World"; // World
5507 insertIndex << 0;
5508 insertString << "Hello"; // HelloWorld
5509 insertIndex << 0;
5510 insertString << "Well"; // WellHelloWorld
5511 insertIndex << 9;
5512 insertString << "There"; // WellHelloThereWorld;
5513
5514 expectedString << "World";
5515 expectedString << "HelloWorld";
5516 expectedString << "WellHelloWorld";
5517 expectedString << "WellHelloThereWorld";
5518
5519 QTest::newRow(dataTag: "Inserts and setting cursor") << insertString << insertIndex << expectedString;
5520 }
5521}
5522
5523void tst_qquicktextinput::redo()
5524{
5525 QFETCH(QStringList, insertString);
5526 QFETCH(IntList, insertIndex);
5527 QFETCH(QStringList, expectedString);
5528
5529 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
5530 QQmlComponent textInputComponent(&engine);
5531 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
5532 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
5533 QVERIFY(textInput != nullptr);
5534
5535 QQuickWindow window;
5536 textInput->setParentItem(window.contentItem());
5537 window.show();
5538 window.requestActivate();
5539 QVERIFY(QTest::qWaitForWindowActive(&window));
5540
5541 QVERIFY(textInput->hasActiveFocus());
5542 QVERIFY(!textInput->canUndo());
5543 QVERIFY(!textInput->canRedo());
5544
5545 QSignalSpy spy(textInput, SIGNAL(canRedoChanged()));
5546
5547 int i;
5548 // inserts the diff strings at diff positions
5549 for (i = 0; i < insertString.size(); ++i) {
5550 if (insertIndex[i] > -1)
5551 textInput->setCursorPosition(insertIndex[i]);
5552 for (int j = 0; j < insertString.at(i).length(); j++)
5553 QTest::keyClick(window: &window, key: insertString.at(i).at(i: j).toLatin1());
5554 QVERIFY(textInput->canUndo());
5555 QVERIFY(!textInput->canRedo());
5556 }
5557
5558 QCOMPARE(spy.count(), 0);
5559
5560 // undo everything
5561 while (!textInput->text().isEmpty()) {
5562 QVERIFY(textInput->canUndo());
5563 textInput->undo();
5564 QVERIFY(textInput->canRedo());
5565 }
5566
5567 QCOMPARE(spy.count(), 1);
5568
5569 for (i = 0; i < expectedString.size(); ++i) {
5570 QVERIFY(textInput->canRedo());
5571 textInput->redo();
5572 QCOMPARE(textInput->text() , expectedString[i]);
5573 QVERIFY(textInput->canUndo());
5574 }
5575 QVERIFY(!textInput->canRedo());
5576 QCOMPARE(spy.count(), 2);
5577}
5578
5579#if QT_CONFIG(shortcut)
5580
5581void tst_qquicktextinput::undo_keypressevents_data()
5582{
5583 QTest::addColumn<KeyList>(name: "keys");
5584 QTest::addColumn<QStringList>(name: "expectedString");
5585
5586 {
5587 KeyList keys;
5588 QStringList expectedString;
5589
5590 keys << "AFRAID"
5591 << QKeySequence::MoveToStartOfLine
5592 << "VERY"
5593 << Qt::Key_Left
5594 << Qt::Key_Left
5595 << Qt::Key_Left
5596 << Qt::Key_Left
5597 << "BE"
5598 << QKeySequence::MoveToEndOfLine
5599 << "!";
5600
5601 expectedString << "BEVERYAFRAID!";
5602 expectedString << "BEVERYAFRAID";
5603 expectedString << "VERYAFRAID";
5604 expectedString << "AFRAID";
5605
5606 QTest::newRow(dataTag: "Inserts and moving cursor") << keys << expectedString;
5607 } {
5608 KeyList keys;
5609 QStringList expectedString;
5610
5611 // inserting '1234'
5612 keys << "1234" << QKeySequence::MoveToStartOfLine
5613 // skipping '12'
5614 << Qt::Key_Right << Qt::Key_Right
5615 // selecting '34'
5616 << (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
5617 // deleting '34'
5618 << Qt::Key_Delete;
5619
5620 expectedString << "12";
5621 expectedString << "1234";
5622
5623 QTest::newRow(dataTag: "Inserts,moving,selection and delete") << keys << expectedString;
5624 } {
5625 KeyList keys;
5626 QStringList expectedString;
5627
5628 // inserting 'AB12'
5629 keys << "AB12"
5630 << QKeySequence::MoveToStartOfLine
5631 // selecting 'AB'
5632 << (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
5633 << Qt::Key_Backspace
5634 << QKeySequence::Undo
5635 << Qt::Key_Right
5636 << (Qt::Key_Right | Qt::ShiftModifier) << (Qt::Key_Right | Qt::ShiftModifier)
5637 << Qt::Key_Delete;
5638
5639 expectedString << "AB";
5640 expectedString << "AB12";
5641
5642 QTest::newRow(dataTag: "Inserts,moving,selection, delete and undo") << keys << expectedString;
5643 } {
5644 KeyList keys;
5645 QStringList expectedString;
5646
5647 // inserting 'ABCD'
5648 keys << "abcd"
5649 //move left two
5650 << Qt::Key_Left << Qt::Key_Left
5651 // inserting '1234'
5652 << "1234"
5653 // selecting '1234'
5654 << (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier) << (Qt::Key_Left | Qt::ShiftModifier)
5655 // overwriting '1234' with '5'
5656 << "5"
5657 // undoing deletion of 'AB'
5658 << QKeySequence::Undo
5659 // overwriting '1234' with '6'
5660 << "6";
5661
5662 expectedString << "ab6cd";
5663 // for versions previous to 3.2 we overwrite needed two undo operations
5664 expectedString << "ab1234cd";
5665 expectedString << "abcd";
5666
5667 QTest::newRow(dataTag: "Inserts,moving,selection and undo, removing selection") << keys << expectedString;
5668 } {
5669 KeyList keys;
5670 QStringList expectedString;
5671
5672 // inserting 'ABC'
5673 keys << "ABC"
5674 // removes 'C'
5675 << Qt::Key_Backspace;
5676
5677 expectedString << "AB";
5678 expectedString << "ABC";
5679
5680 QTest::newRow(dataTag: "Inserts,backspace") << keys << expectedString;
5681 } {
5682 KeyList keys;
5683 QStringList expectedString;
5684
5685 keys << "ABC"
5686 // removes 'C'
5687 << Qt::Key_Backspace
5688 // inserting 'Z'
5689 << "Z";
5690
5691 expectedString << "ABZ";
5692 expectedString << "AB";
5693 expectedString << "ABC";
5694
5695 QTest::newRow(dataTag: "Inserts,backspace,inserts") << keys << expectedString;
5696 } {
5697 KeyList keys;
5698 QStringList expectedString;
5699
5700 // inserting '123'
5701 keys << "123" << QKeySequence::MoveToStartOfLine
5702 // selecting '123'
5703 << QKeySequence::SelectEndOfLine
5704 // overwriting '123' with 'ABC'
5705 << "ABC";
5706
5707 expectedString << "ABC";
5708 expectedString << "123";
5709
5710 QTest::newRow(dataTag: "Inserts,moving,selection and overwriting") << keys << expectedString;
5711 } {
5712 KeyList keys;
5713 QStringList expectedString;
5714
5715 // inserting '123'
5716 keys << "123"
5717 << QKeySequence::Undo
5718 << QKeySequence::Redo;
5719
5720 expectedString << "123";
5721 expectedString << QString();
5722
5723 QTest::newRow(dataTag: "Insert,undo,redo") << keys << expectedString;
5724 } {
5725 KeyList keys;
5726 QStringList expectedString;
5727
5728 keys << "hello world"
5729 << (Qt::Key_Backspace | Qt::ControlModifier)
5730 << QKeySequence::Undo
5731 << QKeySequence::Redo
5732 << "hello";
5733
5734 expectedString
5735 << "hello hello"
5736 << "hello "
5737 << "hello world"
5738 << QString();
5739
5740 QTest::newRow(dataTag: "Insert,delete previous word,undo,redo,insert") << keys << expectedString;
5741 } {
5742 KeyList keys;
5743 QStringList expectedString;
5744
5745 keys << "hello world"
5746 << QKeySequence::SelectPreviousWord
5747 << (Qt::Key_Backspace)
5748 << QKeySequence::Undo
5749 << "hello";
5750
5751 expectedString
5752 << "hello hello"
5753 << "hello world"
5754 << QString();
5755
5756 QTest::newRow(dataTag: "Insert,select previous word,remove,undo,insert") << keys << expectedString;
5757 } {
5758 KeyList keys;
5759 QStringList expectedString;
5760
5761 keys << "hello world"
5762 << QKeySequence::DeleteStartOfWord
5763 << QKeySequence::Undo
5764 << "hello";
5765
5766 expectedString
5767 << "hello worldhello"
5768 << "hello world"
5769 << QString();
5770
5771 QTest::newRow(dataTag: "Insert,delete previous word,undo,insert") << keys << expectedString;
5772 } {
5773 KeyList keys;
5774 QStringList expectedString;
5775
5776 keys << "hello world"
5777 << QKeySequence::MoveToPreviousWord
5778 << QKeySequence::DeleteEndOfWord
5779 << QKeySequence::Undo
5780 << "hello";
5781
5782 expectedString
5783 << "hello helloworld"
5784 << "hello world"
5785 << QString();
5786
5787 QTest::newRow(dataTag: "Insert,move,delete next word,undo,insert") << keys << expectedString;
5788 }
5789 if (!QKeySequence(QKeySequence::DeleteEndOfLine).isEmpty()) { // X11 only.
5790 KeyList keys;
5791 QStringList expectedString;
5792
5793 keys << "hello world"
5794 << QKeySequence::MoveToStartOfLine
5795 << Qt::Key_Right
5796 << QKeySequence::DeleteEndOfLine
5797 << QKeySequence::Undo
5798 << "hello";
5799
5800 expectedString
5801 << "hhelloello world"
5802 << "hello world"
5803 << QString();
5804
5805 QTest::newRow(dataTag: "Insert,move,delete end of line,undo,insert") << keys << expectedString;
5806 } {
5807 KeyList keys;
5808 QStringList expectedString;
5809
5810 keys << "hello world"
5811 << QKeySequence::MoveToPreviousWord
5812 << (Qt::Key_Left | Qt::ShiftModifier)
5813 << (Qt::Key_Left | Qt::ShiftModifier)
5814 << QKeySequence::DeleteEndOfWord
5815 << QKeySequence::Undo
5816 << "hello";
5817
5818 expectedString
5819 << "hellhelloworld"
5820 << "hello world"
5821 << QString();
5822
5823 QTest::newRow(dataTag: "Insert,move,select,delete next word,undo,insert") << keys << expectedString;
5824 }
5825
5826 bool canCopyPaste = PlatformQuirks::isClipboardAvailable();
5827
5828 if (canCopyPaste) {
5829 KeyList keys;
5830 keys << "123"
5831 << QKeySequence(QKeySequence::SelectStartOfLine)
5832 << QKeySequence(QKeySequence::Cut)
5833 << "ABC"
5834 << QKeySequence(QKeySequence::Paste);
5835 QStringList expectedString = QStringList()
5836 << "ABC123"
5837 << "ABC"
5838 // TextEdit: ""
5839 << "123";
5840 QTest::newRow(dataTag: "Cut,paste") << keys << expectedString;
5841 }
5842 if (canCopyPaste) {
5843 KeyList keys;
5844 keys << "123"
5845 << QKeySequence(QKeySequence::SelectStartOfLine)
5846 << QKeySequence(QKeySequence::Copy)
5847 << "ABC"
5848 << QKeySequence(QKeySequence::Paste);
5849 QStringList expectedString = QStringList()
5850 << "ABC123"
5851 << "ABC"
5852 // TextEdit: "A"
5853 << "123";
5854 QTest::newRow(dataTag: "Copy,paste") << keys << expectedString;
5855 }
5856}
5857
5858void tst_qquicktextinput::undo_keypressevents()
5859{
5860 QFETCH(KeyList, keys);
5861 QFETCH(QStringList, expectedString);
5862
5863 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
5864 QQmlComponent textInputComponent(&engine);
5865 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
5866 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
5867 QVERIFY(textInput != nullptr);
5868
5869 QQuickWindow window;
5870 textInput->setParentItem(window.contentItem());
5871 window.show();
5872 window.requestActivate();
5873 QVERIFY(QTest::qWaitForWindowActive(&window));
5874 QVERIFY(textInput->hasActiveFocus());
5875
5876 simulateKeys(window: &window, keys);
5877
5878 for (int i = 0; i < expectedString.size(); ++i) {
5879 QCOMPARE(textInput->text() , expectedString[i]);
5880 textInput->undo();
5881 }
5882 QVERIFY(textInput->text().isEmpty());
5883}
5884
5885#endif // QT_CONFIG(shortcut)
5886
5887void tst_qquicktextinput::clear()
5888{
5889 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
5890 QQmlComponent textInputComponent(&engine);
5891 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
5892 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
5893 QVERIFY(textInput != nullptr);
5894
5895 QQuickWindow window;
5896 textInput->setParentItem(window.contentItem());
5897 window.show();
5898 window.requestActivate();
5899 QVERIFY(QTest::qWaitForWindowActive(&window));
5900 QVERIFY(textInput->hasActiveFocus());
5901 QVERIFY(!textInput->canUndo());
5902
5903 QSignalSpy spy(textInput, SIGNAL(canUndoChanged()));
5904
5905 textInput->setText("I am Legend");
5906 QCOMPARE(textInput->text(), QString("I am Legend"));
5907 textInput->clear();
5908 QVERIFY(textInput->text().isEmpty());
5909
5910 QCOMPARE(spy.count(), 1);
5911
5912 // checks that clears can be undone
5913 textInput->undo();
5914 QVERIFY(!textInput->canUndo());
5915 QCOMPARE(spy.count(), 2);
5916 QCOMPARE(textInput->text(), QString("I am Legend"));
5917
5918 textInput->setCursorPosition(4);
5919 QInputMethodEvent preeditEvent("PREEDIT", QList<QInputMethodEvent::Attribute>());
5920 QGuiApplication::sendEvent(receiver: textInput, event: &preeditEvent);
5921 QCOMPARE(textInput->text(), QString("I am Legend"));
5922 QCOMPARE(textInput->displayText(), QString("I amPREEDIT Legend"));
5923 QCOMPARE(textInput->preeditText(), QString("PREEDIT"));
5924
5925 textInput->clear();
5926 QVERIFY(textInput->text().isEmpty());
5927 QVERIFY2(textInput->preeditText().isEmpty(), "Pre-edit text must be empty after clear");
5928
5929 QCOMPARE(spy.count(), 3);
5930
5931 // checks that clears can be undone
5932 textInput->undo();
5933 QVERIFY(!textInput->canUndo());
5934 QCOMPARE(spy.count(), 4);
5935 QCOMPARE(textInput->text(), QString("I am Legend"));
5936 QVERIFY2(textInput->preeditText().isEmpty(), "Pre-edit text must be empty after undo");
5937}
5938
5939void tst_qquicktextinput::backspaceSurrogatePairs()
5940{
5941 // Test backspace, and delete remove both characters in a surrogate pair.
5942 static const quint16 textData[] = { 0xd800, 0xdf00, 0xd800, 0xdf01, 0xd800, 0xdf02, 0xd800, 0xdf03, 0xd800, 0xdf04 };
5943 const QString text = QString::fromUtf16(textData, size: lengthOf(textData));
5944
5945 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true }";
5946 QQmlComponent textInputComponent(&engine);
5947 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
5948 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
5949 QVERIFY(textInput != nullptr);
5950 textInput->setText(text);
5951 textInput->setCursorPosition(text.length());
5952
5953 QQuickWindow window;
5954 textInput->setParentItem(window.contentItem());
5955 window.show();
5956 window.requestActivate();
5957 QVERIFY(QTest::qWaitForWindowActive(&window));
5958 QCOMPARE(QGuiApplication::focusWindow(), &window);
5959
5960 for (int i = text.length(); i >= 0; i -= 2) {
5961 QCOMPARE(textInput->text(), text.mid(0, i));
5962 QTest::keyClick(window: &window, key: Qt::Key_Backspace, modifier: Qt::NoModifier);
5963 }
5964 QCOMPARE(textInput->text(), QString());
5965
5966 textInput->setText(text);
5967 textInput->setCursorPosition(0);
5968
5969 for (int i = 0; i < text.length(); i += 2) {
5970 QCOMPARE(textInput->text(), text.mid(i));
5971 QTest::keyClick(window: &window, key: Qt::Key_Delete, modifier: Qt::NoModifier);
5972 }
5973 QCOMPARE(textInput->text(), QString());
5974}
5975
5976void tst_qquicktextinput::QTBUG_19956()
5977{
5978 QFETCH(QString, url);
5979
5980 QQuickView window(testFileUrl(fileName: url));
5981 window.show();
5982 window.requestActivate();
5983 QVERIFY(QTest::qWaitForWindowActive(&window));
5984 QVERIFY(window.rootObject() != nullptr);
5985 QQuickTextInput *input = qobject_cast<QQuickTextInput*>(object: window.rootObject());
5986 QVERIFY(input);
5987 input->setFocus(true);
5988 QVERIFY(input->hasActiveFocus());
5989
5990 QCOMPARE(window.rootObject()->property("topvalue").toInt(), 30);
5991 QCOMPARE(window.rootObject()->property("bottomvalue").toInt(), 10);
5992 QCOMPARE(window.rootObject()->property("text").toString(), QString("20"));
5993 QVERIFY(window.rootObject()->property("acceptableInput").toBool());
5994
5995 window.rootObject()->setProperty(name: "topvalue", value: 15);
5996 QCOMPARE(window.rootObject()->property("topvalue").toInt(), 15);
5997 QVERIFY(!window.rootObject()->property("acceptableInput").toBool());
5998
5999 window.rootObject()->setProperty(name: "topvalue", value: 25);
6000 QCOMPARE(window.rootObject()->property("topvalue").toInt(), 25);
6001 QVERIFY(window.rootObject()->property("acceptableInput").toBool());
6002
6003 window.rootObject()->setProperty(name: "bottomvalue", value: 21);
6004 QCOMPARE(window.rootObject()->property("bottomvalue").toInt(), 21);
6005 QVERIFY(!window.rootObject()->property("acceptableInput").toBool());
6006
6007 window.rootObject()->setProperty(name: "bottomvalue", value: 10);
6008 QCOMPARE(window.rootObject()->property("bottomvalue").toInt(), 10);
6009 QVERIFY(window.rootObject()->property("acceptableInput").toBool());
6010}
6011
6012void tst_qquicktextinput::QTBUG_19956_regexp()
6013{
6014 QUrl url = testFileUrl(fileName: "qtbug-19956regexp.qml");
6015
6016 QString warning = url.toString() + ":11:9: Unable to assign [undefined] to QRegExp";
6017 QTest::ignoreMessage(type: QtWarningMsg, qPrintable(warning));
6018
6019 QQuickView window(url);
6020 window.show();
6021 window.requestActivate();
6022 QVERIFY(QTest::qWaitForWindowActive(&window));
6023 QVERIFY(window.rootObject() != nullptr);
6024 QQuickTextInput *input = qobject_cast<QQuickTextInput*>(object: window.rootObject());
6025 QVERIFY(input);
6026 input->setFocus(true);
6027 QVERIFY(input->hasActiveFocus());
6028
6029 window.rootObject()->setProperty(name: "regexvalue", value: QRegExp("abc"));
6030 QCOMPARE(window.rootObject()->property("regexvalue").toRegExp(), QRegExp("abc"));
6031 QCOMPARE(window.rootObject()->property("text").toString(), QString("abc"));
6032 QVERIFY(window.rootObject()->property("acceptableInput").toBool());
6033
6034 window.rootObject()->setProperty(name: "regexvalue", value: QRegExp("abcd"));
6035 QCOMPARE(window.rootObject()->property("regexvalue").toRegExp(), QRegExp("abcd"));
6036 QVERIFY(!window.rootObject()->property("acceptableInput").toBool());
6037
6038 window.rootObject()->setProperty(name: "regexvalue", value: QRegExp("abc"));
6039 QCOMPARE(window.rootObject()->property("regexvalue").toRegExp(), QRegExp("abc"));
6040 QVERIFY(window.rootObject()->property("acceptableInput").toBool());
6041}
6042
6043void tst_qquicktextinput::implicitSize_data()
6044{
6045 QTest::addColumn<QString>(name: "text");
6046 QTest::addColumn<QString>(name: "wrap");
6047 QTest::newRow(dataTag: "plain") << "The quick red fox jumped over the lazy brown dog" << "TextInput.NoWrap";
6048 QTest::newRow(dataTag: "plain_wrap") << "The quick red fox jumped over the lazy brown dog" << "TextInput.Wrap";
6049}
6050
6051void tst_qquicktextinput::implicitSize()
6052{
6053 QFETCH(QString, text);
6054 QFETCH(QString, wrap);
6055 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; width: 50; wrapMode: " + wrap + " }";
6056 QQmlComponent textComponent(&engine);
6057 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
6058 QQuickTextInput *textObject = qobject_cast<QQuickTextInput*>(object: textComponent.create());
6059
6060 QVERIFY(textObject->width() < textObject->implicitWidth());
6061 QCOMPARE(textObject->height(), textObject->implicitHeight());
6062
6063 textObject->resetWidth();
6064 QCOMPARE(textObject->width(), textObject->implicitWidth());
6065 QCOMPARE(textObject->height(), textObject->implicitHeight());
6066}
6067
6068void tst_qquicktextinput::implicitSizeBinding_data()
6069{
6070 implicitSize_data();
6071}
6072
6073void tst_qquicktextinput::implicitSizeBinding()
6074{
6075 QFETCH(QString, text);
6076 QFETCH(QString, wrap);
6077 QString componentStr = "import QtQuick 2.0\nTextInput { text: \"" + text + "\"; width: implicitWidth; height: implicitHeight; wrapMode: " + wrap + " }";
6078 QQmlComponent textComponent(&engine);
6079 textComponent.setData(componentStr.toLatin1(), baseUrl: QUrl::fromLocalFile(localfile: ""));
6080 QScopedPointer<QObject> object(textComponent.create());
6081 QQuickTextInput *textObject = qobject_cast<QQuickTextInput *>(object: object.data());
6082
6083 QCOMPARE(textObject->width(), textObject->implicitWidth());
6084 QCOMPARE(textObject->height(), textObject->implicitHeight());
6085
6086 textObject->resetWidth();
6087 QCOMPARE(textObject->width(), textObject->implicitWidth());
6088 QCOMPARE(textObject->height(), textObject->implicitHeight());
6089
6090 textObject->resetHeight();
6091 QCOMPARE(textObject->width(), textObject->implicitWidth());
6092 QCOMPARE(textObject->height(), textObject->implicitHeight());
6093}
6094
6095void tst_qquicktextinput::implicitResize_data()
6096{
6097 QTest::addColumn<int>(name: "alignment");
6098 QTest::newRow(dataTag: "left") << int(Qt::AlignLeft);
6099 QTest::newRow(dataTag: "center") << int(Qt::AlignHCenter);
6100 QTest::newRow(dataTag: "right") << int(Qt::AlignRight);
6101}
6102
6103void tst_qquicktextinput::implicitResize()
6104{
6105 QFETCH(int, alignment);
6106
6107 QQmlComponent component(&engine);
6108 component.setData("import QtQuick 2.0\nTextInput { }", baseUrl: QUrl::fromLocalFile(localfile: ""));
6109
6110 QScopedPointer<QQuickTextInput> textInput(qobject_cast<QQuickTextInput *>(object: component.create()));
6111 QVERIFY(!textInput.isNull());
6112
6113 QScopedPointer<QQuickTextInput> textField(qobject_cast<QQuickTextInput *>(object: component.create()));
6114 QVERIFY(!textField.isNull());
6115 QQuickTextInputPrivate::get(t: textField.data())->setImplicitResizeEnabled(false);
6116
6117 textInput->setWidth(200);
6118 textField->setImplicitWidth(200);
6119
6120 textInput->setHAlign(QQuickTextInput::HAlignment(alignment));
6121 textField->setHAlign(QQuickTextInput::HAlignment(alignment));
6122
6123 textInput->setText("Qt");
6124 textField->setText("Qt");
6125
6126 QCOMPARE(textField->positionToRectangle(0), textInput->positionToRectangle(0));
6127}
6128
6129void tst_qquicktextinput::negativeDimensions()
6130{
6131 // Verify this doesn't assert during initialization.
6132 QQmlComponent component(&engine, testFileUrl(fileName: "negativeDimensions.qml"));
6133 QScopedPointer<QObject> o(component.create());
6134 QVERIFY(o);
6135 QQuickTextInput *input = o->findChild<QQuickTextInput *>(aName: "input");
6136 QVERIFY(input);
6137 QCOMPARE(input->width(), qreal(-1));
6138 QCOMPARE(input->height(), qreal(-1));
6139}
6140
6141void tst_qquicktextinput::keypress_inputMask_withValidator_data()
6142{
6143 QTest::addColumn<QString>(name: "mask");
6144 QTest::addColumn<qreal>(name: "validatorMinimum");
6145 QTest::addColumn<qreal>(name: "validatorMaximum");
6146 QTest::addColumn<int>(name: "decimals");
6147 QTest::addColumn<QString>(name: "validatorRegExp");
6148 QTest::addColumn<KeyList>(name: "keys");
6149 QTest::addColumn<QString>(name: "expectedText");
6150 QTest::addColumn<QString>(name: "expectedDisplayText");
6151
6152 {
6153 KeyList keys;
6154 // inserting '1212' then two backspaces
6155 keys << Qt::Key_Home << "1212" << Qt::Key_Backspace << Qt::Key_Backspace;
6156 QTest::newRow(dataTag: "backspaceWithInt") << QString("9999;_") << 1.0 << 9999.00 << 0 << QString()
6157 << keys << QString("12") << QString("12__");
6158 }
6159 {
6160 KeyList keys;
6161 // inserting '12.12' then two backspaces
6162 keys << Qt::Key_Home << "12.12" << Qt::Key_Backspace << Qt::Key_Backspace;
6163 QTest::newRow(dataTag: "backspaceWithDouble") << QString("99.99;_") << 1.0 << 99.99 << 2 << QString()
6164 << keys << QString("12.") << QString("12.__");
6165 }
6166 {
6167 KeyList keys;
6168 // inserting '1111.11' then two backspaces
6169 keys << Qt::Key_Home << "1111.11" << Qt::Key_Backspace << Qt::Key_Backspace;
6170 QTest::newRow(dataTag: "backspaceWithRegExp") << QString("9999;_") << 0.0 << 0.0 << 0
6171 << QString("/^[-]?((\\.\\d+)|(\\d+(\\.\\d+)?))$/")
6172 << keys << QString("11") << QString("11__");
6173 }
6174 {
6175 KeyList keys;
6176 // inserting '99' - QTBUG-64616
6177 keys << Qt::Key_Home << "99";
6178 QTest::newRow(dataTag: "invalidTextWithRegExp") << QString("X9;_") << 0.0 << 0.0 << 0
6179 << QString("/[+-][0+9]/")
6180 << keys << QString("") << QString("__");
6181 }
6182}
6183
6184void tst_qquicktextinput::keypress_inputMask_withValidator()
6185{
6186 QFETCH(QString, mask);
6187 QFETCH(qreal, validatorMinimum);
6188 QFETCH(qreal, validatorMaximum);
6189 QFETCH(int, decimals);
6190 QFETCH(QString, validatorRegExp);
6191 QFETCH(KeyList, keys);
6192 QFETCH(QString, expectedText);
6193 QFETCH(QString, expectedDisplayText);
6194
6195 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\"\n";
6196 if (!validatorRegExp.isEmpty())
6197 componentStr += "validator: RegExpValidator { regExp: " + validatorRegExp + " }\n}";
6198 else if (decimals > 0)
6199 componentStr += QString("validator: DoubleValidator { bottom: %1; decimals: %2; top: %3 }\n}").
6200 arg(a: validatorMinimum).arg(a: decimals).arg(a: validatorMaximum);
6201 else
6202 componentStr += QString("validator: IntValidator { bottom: %1; top: %2 }\n}").
6203 arg(a: (int)validatorMinimum).arg(a: (int)validatorMaximum);
6204
6205 QQmlComponent textInputComponent(&engine);
6206 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
6207 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
6208 QVERIFY(textInput != nullptr);
6209
6210 QQuickWindow window;
6211 textInput->setParentItem(window.contentItem());
6212 window.show();
6213 window.requestActivate();
6214 QVERIFY(QTest::qWaitForWindowActive(&window));
6215 QVERIFY(textInput->hasActiveFocus());
6216
6217 simulateKeys(window: &window, keys);
6218
6219 QCOMPARE(textInput->text(), expectedText);
6220 QCOMPARE(textInput->displayText(), expectedDisplayText);
6221}
6222
6223void tst_qquicktextinput::setInputMask_data()
6224{
6225 QTest::addColumn<QString>(name: "mask");
6226 QTest::addColumn<QString>(name: "input");
6227 QTest::addColumn<QString>(name: "expectedText");
6228 QTest::addColumn<QString>(name: "expectedDisplay");
6229 QTest::addColumn<bool>(name: "insert_text");
6230
6231 // both keyboard and insert()
6232 for (int i=0; i<2; i++) {
6233 bool insert_text = i==0 ? false : true;
6234 QString insert_mode = "keys ";
6235 if (insert_text)
6236 insert_mode = "insert ";
6237
6238 QTest::newRow(dataTag: QString(insert_mode + "ip_localhost").toLatin1())
6239 << QString("000.000.000.000")
6240 << QString("127.0.0.1")
6241 << QString("127.0.0.1")
6242 << QString("127.0 .0 .1 ")
6243 << bool(insert_text);
6244 QTest::newRow(dataTag: QString(insert_mode + "mac").toLatin1())
6245 << QString("HH:HH:HH:HH:HH:HH;#")
6246 << QString("00:E0:81:21:9E:8E")
6247 << QString("00:E0:81:21:9E:8E")
6248 << QString("00:E0:81:21:9E:8E")
6249 << bool(insert_text);
6250 QTest::newRow(dataTag: QString(insert_mode + "mac2").toLatin1())
6251 << QString("<HH:>HH:!HH:HH:HH:HH;#")
6252 << QString("AAe081219E8E")
6253 << QString("aa:E0:81:21:9E:8E")
6254 << QString("aa:E0:81:21:9E:8E")
6255 << bool(insert_text);
6256 QTest::newRow(dataTag: QString(insert_mode + "byte").toLatin1())
6257 << QString("BBBBBBBB;0")
6258 << QString("11011001")
6259 << QString("11111")
6260 << QString("11011001")
6261 << bool(insert_text);
6262 QTest::newRow(dataTag: QString(insert_mode + "halfbytes").toLatin1())
6263 << QString("bbbb.bbbb;-")
6264 << QString("110. 0001")
6265 << QString("110.0001")
6266 << QString("110-.0001")
6267 << bool(insert_text);
6268 QTest::newRow(dataTag: QString(insert_mode + "blank char same type as content").toLatin1())
6269 << QString("000.000.000.000;0")
6270 << QString("127.0.0.1")
6271 << QString("127...1")
6272 << QString("127.000.000.100")
6273 << bool(insert_text);
6274 QTest::newRow(dataTag: QString(insert_mode + "parts of ip_localhost").toLatin1())
6275 << QString("000.000.000.000")
6276 << QString(".0.0.1")
6277 << QString(".0.0.1")
6278 << QString(" .0 .0 .1 ")
6279 << bool(insert_text);
6280 QTest::newRow(dataTag: QString(insert_mode + "ip_null").toLatin1())
6281 << QString("000.000.000.000")
6282 << QString()
6283 << QString("...")
6284 << QString(" . . . ")
6285 << bool(insert_text);
6286 QTest::newRow(dataTag: QString(insert_mode + "ip_null_hash").toLatin1())
6287 << QString("000.000.000.000;#")
6288 << QString()
6289 << QString("...")
6290 << QString("###.###.###.###")
6291 << bool(insert_text);
6292 QTest::newRow(dataTag: QString(insert_mode + "ip_overflow").toLatin1())
6293 << QString("000.000.000.000")
6294 << QString("1234123412341234")
6295 << QString("123.412.341.234")
6296 << QString("123.412.341.234")
6297 << bool(insert_text);
6298 QTest::newRow(dataTag: QString(insert_mode + "uppercase").toLatin1())
6299 << QString(">AAAA")
6300 << QString("AbCd")
6301 << QString("ABCD")
6302 << QString("ABCD")
6303 << bool(insert_text);
6304 QTest::newRow(dataTag: QString(insert_mode + "lowercase").toLatin1())
6305 << QString("<AAAA")
6306 << QString("AbCd")
6307 << QString("abcd")
6308 << QString("abcd")
6309 << bool(insert_text);
6310
6311 QTest::newRow(dataTag: QString(insert_mode + "nocase").toLatin1())
6312 << QString("!AAAA")
6313 << QString("AbCd")
6314 << QString("AbCd")
6315 << QString("AbCd")
6316 << bool(insert_text);
6317 QTest::newRow(dataTag: QString(insert_mode + "nocase1").toLatin1())
6318 << QString("!A!A!A!A")
6319 << QString("AbCd")
6320 << QString("AbCd")
6321 << QString("AbCd")
6322 << bool(insert_text);
6323 QTest::newRow(dataTag: QString(insert_mode + "nocase2").toLatin1())
6324 << QString("AAAA")
6325 << QString("AbCd")
6326 << QString("AbCd")
6327 << QString("AbCd")
6328 << bool(insert_text);
6329
6330 QTest::newRow(dataTag: QString(insert_mode + "reserved").toLatin1())
6331 << QString("{n}[0]")
6332 << QString("A9")
6333 << QString("A9")
6334 << QString("A9")
6335 << bool(insert_text);
6336 QTest::newRow(dataTag: QString(insert_mode + "escape01").toLatin1())
6337 << QString("\\\\N\\\\n00")
6338 << QString("9")
6339 << QString("Nn9")
6340 << QString("Nn9 ")
6341 << bool(insert_text);
6342 QTest::newRow(dataTag: QString(insert_mode + "escape02").toLatin1())
6343 << QString("\\\\\\\\00")
6344 << QString("0")
6345 << QString("\\0")
6346 << QString("\\0 ")
6347 << bool(insert_text);
6348 QTest::newRow(dataTag: QString(insert_mode + "escape03").toLatin1())
6349 << QString("\\\\(00\\\\)")
6350 << QString("0")
6351 << QString("(0)")
6352 << QString("(0 )")
6353 << bool(insert_text);
6354
6355 QTest::newRow(dataTag: QString(insert_mode + "upper_lower_nocase1").toLatin1())
6356 << QString(">AAAA<AAAA!AAAA")
6357 << QString("AbCdEfGhIjKl")
6358 << QString("ABCDefghIjKl")
6359 << QString("ABCDefghIjKl")
6360 << bool(insert_text);
6361 QTest::newRow(dataTag: QString(insert_mode + "upper_lower_nocase2").toLatin1())
6362 << QString(">aaaa<aaaa!aaaa")
6363 << QString("AbCdEfGhIjKl")
6364 << QString("ABCDefghIjKl")
6365 << QString("ABCDefghIjKl")
6366 << bool(insert_text);
6367
6368 QTest::newRow(dataTag: QString(insert_mode + "exact_case1").toLatin1())
6369 << QString(">A<A<A>A>A<A!A!A")
6370 << QString("AbCdEFGH")
6371 << QString("AbcDEfGH")
6372 << QString("AbcDEfGH")
6373 << bool(insert_text);
6374 QTest::newRow(dataTag: QString(insert_mode + "exact_case2").toLatin1())
6375 << QString(">A<A<A>A>A<A!A!A")
6376 << QString("aBcDefgh")
6377 << QString("AbcDEfgh")
6378 << QString("AbcDEfgh")
6379 << bool(insert_text);
6380 QTest::newRow(dataTag: QString(insert_mode + "exact_case3").toLatin1())
6381 << QString(">a<a<a>a>a<a!a!a")
6382 << QString("AbCdEFGH")
6383 << QString("AbcDEfGH")
6384 << QString("AbcDEfGH")
6385 << bool(insert_text);
6386 QTest::newRow(dataTag: QString(insert_mode + "exact_case4").toLatin1())
6387 << QString(">a<a<a>a>a<a!a!a")
6388 << QString("aBcDefgh")
6389 << QString("AbcDEfgh")
6390 << QString("AbcDEfgh")
6391 << bool(insert_text);
6392 QTest::newRow(dataTag: QString(insert_mode + "exact_case5").toLatin1())
6393 << QString(">H<H<H>H>H<H!H!H")
6394 << QString("aBcDef01")
6395 << QString("AbcDEf01")
6396 << QString("AbcDEf01")
6397 << bool(insert_text);
6398 QTest::newRow(dataTag: QString(insert_mode + "exact_case6").toLatin1())
6399 << QString(">h<h<h>h>h<h!h!h")
6400 << QString("aBcDef92")
6401 << QString("AbcDEf92")
6402 << QString("AbcDEf92")
6403 << bool(insert_text);
6404
6405 QTest::newRow(dataTag: QString(insert_mode + "illegal_keys1").toLatin1())
6406 << QString("AAAAAAAA")
6407 << QString("A2#a;.0!")
6408 << QString("Aa")
6409 << QString("Aa ")
6410 << bool(insert_text);
6411 QTest::newRow(dataTag: QString(insert_mode + "illegal_keys2").toLatin1())
6412 << QString("AAAA")
6413 << QString("f4f4f4f4")
6414 << QString("ffff")
6415 << QString("ffff")
6416 << bool(insert_text);
6417 QTest::newRow(dataTag: QString(insert_mode + "blank=input").toLatin1())
6418 << QString("9999;0")
6419 << QString("2004")
6420 << QString("24")
6421 << QString("2004")
6422 << bool(insert_text);
6423 QTest::newRow(dataTag: QString(insert_mode + "any_opt").toLatin1())
6424 << QString("@xxx@")
6425 << QString("@A C@")
6426 << QString("@AC@")
6427 << QString("@A C@")
6428 << bool(insert_text);
6429 QTest::newRow(dataTag: QString(insert_mode + "any_req").toLatin1())
6430 << QString("@XXX@")
6431 << QString("@A C@")
6432 << QString("@AC@@")
6433 << QString("@AC@@")
6434 << bool(insert_text);
6435 }
6436}
6437
6438void tst_qquicktextinput::setInputMask()
6439{
6440 QFETCH(QString, mask);
6441 QFETCH(QString, input);
6442 QFETCH(QString, expectedText);
6443 QFETCH(QString, expectedDisplay);
6444 QFETCH(bool, insert_text);
6445
6446 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
6447 QQmlComponent textInputComponent(&engine);
6448 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
6449 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
6450 QVERIFY(textInput != nullptr);
6451
6452 // [QTBUG-80190] check if setting the same property value again doesn't emit an
6453 // inputMaskChanged signal
6454 QString unescapedMask = mask; // mask is escaped, because '\' is also escape in a JS string
6455 unescapedMask.replace(before: QLatin1String("\\\\"), after: QLatin1String("\\")); // simple unescape
6456 QSignalSpy spy(textInput, SIGNAL(inputMaskChanged(const QString &)));
6457 textInput->setInputMask(unescapedMask);
6458 QCOMPARE(spy.count(), 0);
6459
6460 // then either insert using insert() or keyboard
6461 if (insert_text) {
6462 textInput->insert(position: 0, text: input);
6463 } else {
6464 QQuickWindow window;
6465 textInput->setParentItem(window.contentItem());
6466 window.show();
6467 window.requestActivate();
6468 QVERIFY(QTest::qWaitForWindowActive(&window));
6469 QVERIFY(textInput->hasActiveFocus());
6470
6471 simulateKey(view: &window, key: Qt::Key_Home);
6472 for (int i = 0; i < input.length(); i++)
6473 QTest::keyClick(window: &window, key: input.at(i).toLatin1());
6474 }
6475
6476 QCOMPARE(textInput->text(), expectedText);
6477 QCOMPARE(textInput->displayText(), expectedDisplay);
6478}
6479
6480void tst_qquicktextinput::inputMask_data()
6481{
6482 QTest::addColumn<QString>(name: "mask");
6483 QTest::addColumn<QString>(name: "expectedMask");
6484
6485 // if no mask is set a nul string should be returned
6486 QTest::newRow(dataTag: "nul 1") << QString("") << QString();
6487 QTest::newRow(dataTag: "nul 2") << QString() << QString();
6488
6489 // try different masks
6490 QTest::newRow(dataTag: "mask 1") << QString("000.000.000.000") << QString("000.000.000.000; ");
6491 QTest::newRow(dataTag: "mask 2") << QString("000.000.000.000;#") << QString("000.000.000.000;#");
6492 QTest::newRow(dataTag: "mask 3") << QString("AAA.aa.999.###;") << QString("AAA.aa.999.###; ");
6493 QTest::newRow(dataTag: "mask 4") << QString(">abcdef<GHIJK") << QString(">abcdef<GHIJK; ");
6494
6495 // set an invalid input mask...
6496 // the current behaviour is that this exact (faulty) string is returned.
6497 QTest::newRow(dataTag: "invalid") << QString("ABCDEFGHIKLMNOP;") << QString("ABCDEFGHIKLMNOP; ");
6498
6499 // verify that we can unset the mask again
6500 QTest::newRow(dataTag: "unset") << QString("") << QString();
6501}
6502
6503void tst_qquicktextinput::inputMask()
6504{
6505 QFETCH(QString, mask);
6506 QFETCH(QString, expectedMask);
6507
6508 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
6509 QQmlComponent textInputComponent(&engine);
6510 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
6511 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
6512 QVERIFY(textInput != nullptr);
6513
6514 QCOMPARE(textInput->inputMask(), expectedMask);
6515}
6516
6517void tst_qquicktextinput::clearInputMask()
6518{
6519 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"000.000.000.000\" }";
6520 QQmlComponent textInputComponent(&engine);
6521 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
6522 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
6523 QVERIFY(textInput != nullptr);
6524
6525 QVERIFY(!textInput->inputMask().isEmpty());
6526 textInput->setInputMask(QString());
6527 QCOMPARE(textInput->inputMask(), QString());
6528}
6529
6530void tst_qquicktextinput::keypress_inputMask_data()
6531{
6532 QTest::addColumn<QString>(name: "mask");
6533 QTest::addColumn<KeyList>(name: "keys");
6534 QTest::addColumn<QString>(name: "expectedText");
6535 QTest::addColumn<QString>(name: "expectedDisplayText");
6536
6537 {
6538 KeyList keys;
6539 // inserting 'A1.2B'
6540 keys << Qt::Key_Home << "A1.2B";
6541 QTest::newRow(dataTag: "jumping on period(separator)") << QString("000.000;_") << keys << QString("1.2") << QString("1__.2__");
6542 }
6543 {
6544 KeyList keys;
6545 // inserting '0!P3'
6546 keys << Qt::Key_Home << "0!P3";
6547 QTest::newRow(dataTag: "jumping on input") << QString("D0.AA.XX.AA.00;_") << keys << QString("0..!P..3") << QString("_0.__.!P.__.3_");
6548 }
6549 {
6550 KeyList keys;
6551 // pressing delete
6552 keys << Qt::Key_Home
6553 << Qt::Key_Delete;
6554 QTest::newRow(dataTag: "delete") << QString("000.000;_") << keys << QString(".") << QString("___.___");
6555 }
6556 {
6557 KeyList keys;
6558 // selecting all and delete
6559 keys << Qt::Key_Home
6560 << Key(Qt::ShiftModifier, Qt::Key_End)
6561 << Qt::Key_Delete;
6562 QTest::newRow(dataTag: "deleting all") << QString("000.000;_") << keys << QString(".") << QString("___.___");
6563 }
6564 {
6565 KeyList keys;
6566 // inserting '12.12' then two backspaces
6567 keys << Qt::Key_Home << "12.12" << Qt::Key_Backspace << Qt::Key_Backspace;
6568 QTest::newRow(dataTag: "backspace") << QString("000.000;_") << keys << QString("12.") << QString("12_.___");
6569 }
6570 {
6571 KeyList keys;
6572 // inserting '12ab'
6573 keys << Qt::Key_Home << "12ab";
6574 QTest::newRow(dataTag: "uppercase") << QString("9999 >AA;_") << keys << QString("12 AB") << QString("12__ AB");
6575 }
6576 {
6577 KeyList keys;
6578 // inserting '12ab'
6579 keys << Qt::Key_Right << Qt::Key_Right << "1";
6580 QTest::newRow(dataTag: "Move in mask") << QString("#0:00;*") << keys << QString(":1") << QString("**:1*");
6581 }
6582}
6583
6584void tst_qquicktextinput::keypress_inputMask()
6585{
6586 QFETCH(QString, mask);
6587 QFETCH(KeyList, keys);
6588 QFETCH(QString, expectedText);
6589 QFETCH(QString, expectedDisplayText);
6590
6591 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"" + mask + "\" }";
6592 QQmlComponent textInputComponent(&engine);
6593 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
6594 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
6595 QVERIFY(textInput != nullptr);
6596
6597 QQuickWindow window;
6598 textInput->setParentItem(window.contentItem());
6599 window.show();
6600 window.requestActivate();
6601 QVERIFY(QTest::qWaitForWindowActive(&window));
6602 QVERIFY(textInput->hasActiveFocus());
6603
6604 simulateKeys(window: &window, keys);
6605
6606 QCOMPARE(textInput->text(), expectedText);
6607 QCOMPARE(textInput->displayText(), expectedDisplayText);
6608}
6609
6610void tst_qquicktextinput::keypress_inputMethod_inputMask()
6611{
6612 // Similar to the keypress_inputMask test, but this is done solely via
6613 // input methods
6614 QString componentStr = "import QtQuick 2.0\nTextInput { focus: true; inputMask: \"AA.AA.AA\" }";
6615 QQmlComponent textInputComponent(&engine);
6616 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
6617 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
6618 QVERIFY(textInput != nullptr);
6619
6620 QQuickWindow window;
6621 textInput->setParentItem(window.contentItem());
6622 window.show();
6623 window.requestActivate();
6624 QVERIFY(QTest::qWaitForWindowActive(&window));
6625 QVERIFY(textInput->hasActiveFocus());
6626
6627 {
6628 QList<QInputMethodEvent::Attribute> attributes;
6629 QInputMethodEvent event("", attributes);
6630 event.setCommitString(commitString: "EE");
6631 QGuiApplication::sendEvent(receiver: textInput, event: &event);
6632 }
6633 QCOMPARE(textInput->cursorPosition(), 3);
6634 QCOMPARE(textInput->text(), QStringLiteral("EE.."));
6635 {
6636 QList<QInputMethodEvent::Attribute> attributes;
6637 QInputMethodEvent event("", attributes);
6638 event.setCommitString(commitString: "EE");
6639 QGuiApplication::sendEvent(receiver: textInput, event: &event);
6640 }
6641 QCOMPARE(textInput->cursorPosition(), 6);
6642 QCOMPARE(textInput->text(), QStringLiteral("EE.EE."));
6643 {
6644 QList<QInputMethodEvent::Attribute> attributes;
6645 QInputMethodEvent event("", attributes);
6646 event.setCommitString(commitString: "EE");
6647 QGuiApplication::sendEvent(receiver: textInput, event: &event);
6648 }
6649 QCOMPARE(textInput->cursorPosition(), 8);
6650 QCOMPARE(textInput->text(), QStringLiteral("EE.EE.EE"));
6651}
6652
6653void tst_qquicktextinput::hasAcceptableInputMask_data()
6654{
6655 QTest::addColumn<QString>(name: "optionalMask");
6656 QTest::addColumn<QString>(name: "requiredMask");
6657 QTest::addColumn<QString>(name: "invalid");
6658 QTest::addColumn<QString>(name: "valid");
6659
6660 QTest::newRow(dataTag: "Alphabetic optional and required")
6661 << QString("aaaa") << QString("AAAA") << QString("ab") << QString("abcd");
6662 QTest::newRow(dataTag: "Alphanumeric optional and require")
6663 << QString("nnnn") << QString("NNNN") << QString("R2") << QString("R2D2");
6664 QTest::newRow(dataTag: "Any optional and required")
6665 << QString("xxxx") << QString("XXXX") << QString("+-") << QString("+-*/");
6666 QTest::newRow(dataTag: "Numeric (0-9) required")
6667 << QString("0000") << QString("9999") << QString("11") << QString("1138");
6668 QTest::newRow(dataTag: "Numeric (1-9) optional and required")
6669 << QString("dddd") << QString("DDDD") << QString("12") << QString("1234");
6670}
6671
6672void tst_qquicktextinput::hasAcceptableInputMask()
6673{
6674 QFETCH(QString, optionalMask);
6675 QFETCH(QString, requiredMask);
6676 QFETCH(QString, invalid);
6677 QFETCH(QString, valid);
6678
6679 QString componentStr = "import QtQuick 2.0\nTextInput { inputMask: \"" + optionalMask + "\" }";
6680 QQmlComponent textInputComponent(&engine);
6681 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
6682 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
6683 QVERIFY(textInput != nullptr);
6684
6685 // test that invalid input (for required) work for optionalMask
6686 textInput->setText(invalid);
6687 QVERIFY(textInput->hasAcceptableInput());
6688
6689 // test requiredMask
6690 textInput->setInputMask(requiredMask);
6691 textInput->setText(invalid);
6692 // invalid text gets the input mask applied when setting, text becomes acceptable.
6693 QVERIFY(textInput->hasAcceptableInput());
6694
6695 textInput->setText(valid);
6696 QVERIFY(textInput->hasAcceptableInput());
6697}
6698
6699void tst_qquicktextinput::maskCharacter_data()
6700{
6701 QTest::addColumn<QString>(name: "mask");
6702 QTest::addColumn<QString>(name: "input");
6703 QTest::addColumn<bool>(name: "expectedValid");
6704
6705 QTest::newRow(dataTag: "Hex") << QString("H")
6706 << QString("0123456789abcdefABCDEF") << true;
6707 QTest::newRow(dataTag: "hex") << QString("h")
6708 << QString("0123456789abcdefABCDEF") << true;
6709 QTest::newRow(dataTag: "HexInvalid") << QString("H")
6710 << QString("ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ")
6711 << false;
6712 QTest::newRow(dataTag: "hexInvalid") << QString("h")
6713 << QString("ghijklmnopqrstuvwxyzGHIJKLMNOPQRSTUVWXYZ")
6714 << false;
6715 QTest::newRow(dataTag: "Bin") << QString("B")
6716 << QString("01") << true;
6717 QTest::newRow(dataTag: "bin") << QString("b")
6718 << QString("01") << true;
6719 QTest::newRow(dataTag: "BinInvalid") << QString("B")
6720 << QString("23456789qwertyuiopasdfghjklzxcvbnm")
6721 << false;
6722 QTest::newRow(dataTag: "binInvalid") << QString("b")
6723 << QString("23456789qwertyuiopasdfghjklzxcvbnm")
6724 << false;
6725}
6726
6727void tst_qquicktextinput::maskCharacter()
6728{
6729 QFETCH(QString, mask);
6730 QFETCH(QString, input);
6731 QFETCH(bool, expectedValid);
6732
6733 QString componentStr = "import QtQuick 2.0\nTextInput { inputMask: \"" + mask + "\" }";
6734 QQmlComponent textInputComponent(&engine);
6735 textInputComponent.setData(componentStr.toLatin1(), baseUrl: QUrl());
6736 QQuickTextInput *textInput = qobject_cast<QQuickTextInput*>(object: textInputComponent.create());
6737 QVERIFY(textInput != nullptr);
6738
6739 for (int i = 0; i < input.size(); ++i) {
6740 QString in = QString(input.at(i));
6741 QString expected = expectedValid ? in : QString();
6742 textInput->setText(QString(input.at(i)));
6743 QCOMPARE(textInput->text(), expected);
6744 }
6745}
6746
6747class TestValidator : public QValidator
6748{
6749public:
6750 TestValidator(QObject *parent = nullptr) : QValidator(parent) { }
6751
6752 State validate(QString &input, int &) const { return input == QStringLiteral("ok") ? Acceptable : Intermediate; }
6753 void fixup(QString &input) const { input = QStringLiteral("ok"); }
6754};
6755
6756void tst_qquicktextinput::fixup()
6757{
6758 QQuickWindow window;
6759 window.show();
6760 window.requestActivate();
6761 QVERIFY(QTest::qWaitForWindowActive(&window));
6762
6763 QQuickTextInput *input = new QQuickTextInput(window.contentItem());
6764 input->setValidator(new TestValidator(input));
6765
6766 // fixup() on accept
6767 input->setFocus(true);
6768 QVERIFY(input->hasActiveFocus());
6769 QTest::keyClick(window: &window, key: Qt::Key_Enter);
6770 QCOMPARE(input->text(), QStringLiteral("ok"));
6771
6772 // fixup() on defocus
6773 input->setText(QString());
6774 input->setFocus(false);
6775 QVERIFY(!input->hasActiveFocus());
6776 QCOMPARE(input->text(), QStringLiteral("ok"));
6777}
6778
6779typedef qreal (*ExpectedBaseline)(QQuickTextInput *item);
6780Q_DECLARE_METATYPE(ExpectedBaseline)
6781
6782static qreal expectedBaselineTop(QQuickTextInput *item)
6783{
6784 QFontMetricsF fm(item->font());
6785 return fm.ascent() + item->topPadding();
6786}
6787
6788static qreal expectedBaselineBottom(QQuickTextInput *item)
6789{
6790 QFontMetricsF fm(item->font());
6791 return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent();
6792}
6793
6794static qreal expectedBaselineCenter(QQuickTextInput *item)
6795{
6796 QFontMetricsF fm(item->font());
6797 return ((item->height() - item->contentHeight() - item->topPadding() - item->bottomPadding()) / 2) + fm.ascent() + item->topPadding();
6798}
6799
6800static qreal expectedBaselineMultilineBottom(QQuickTextInput *item)
6801{
6802 QFontMetricsF fm(item->font());
6803 return item->height() - item->contentHeight() - item->bottomPadding() + fm.ascent();
6804}
6805
6806void tst_qquicktextinput::baselineOffset_data()
6807{
6808 QTest::addColumn<QString>(name: "text");
6809 QTest::addColumn<QByteArray>(name: "bindings");
6810 QTest::addColumn<qreal>(name: "setHeight");
6811 QTest::addColumn<ExpectedBaseline>(name: "expectedBaseline");
6812 QTest::addColumn<ExpectedBaseline>(name: "expectedBaselineEmpty");
6813
6814 QTest::newRow(dataTag: "normal")
6815 << "Typography"
6816 << QByteArray()
6817 << -1.
6818 << &expectedBaselineTop
6819 << &expectedBaselineTop;
6820
6821 QTest::newRow(dataTag: "top align")
6822 << "Typography"
6823 << QByteArray("height: 200; verticalAlignment: Text.AlignTop")
6824 << -1.
6825 << &expectedBaselineTop
6826 << &expectedBaselineTop;
6827
6828 QTest::newRow(dataTag: "bottom align")
6829 << "Typography"
6830 << QByteArray("height: 200; verticalAlignment: Text.AlignBottom")
6831 << 100.
6832 << &expectedBaselineBottom
6833 << &expectedBaselineBottom;
6834
6835 QTest::newRow(dataTag: "center align")
6836 << "Typography"
6837 << QByteArray("height: 200; verticalAlignment: Text.AlignVCenter")
6838 << 100.
6839 << &expectedBaselineCenter
6840 << &expectedBaselineCenter;
6841
6842 QTest::newRow(dataTag: "multiline bottom aligned")
6843 << "The quick brown fox jumps over the lazy dog"
6844 << QByteArray("height: 200; width: 30; verticalAlignment: Text.AlignBottom; wrapMode: TextInput.WordWrap")
6845 << -1.
6846 << &expectedBaselineMultilineBottom
6847 << &expectedBaselineBottom;
6848
6849 QTest::newRow(dataTag: "padding")
6850 << "Typography"
6851 << QByteArray("topPadding: 10; bottomPadding: 20")
6852 << -1.
6853 << &expectedBaselineTop
6854 << &expectedBaselineTop;
6855
6856 QTest::newRow(dataTag: "top align with padding")
6857 << "Typography"
6858 << QByteArray("height: 200; verticalAlignment: Text.AlignTop; topPadding: 10; bottomPadding: 20")
6859 << -1.
6860 << &expectedBaselineTop
6861 << &expectedBaselineTop;
6862
6863 QTest::newRow(dataTag: "bottom align with padding")
6864 << "Typography"
6865 << QByteArray("height: 200; verticalAlignment: Text.AlignBottom; topPadding: 10; bottomPadding: 20")
6866 << 100.
6867 << &expectedBaselineBottom
6868 << &expectedBaselineBottom;
6869
6870 QTest::newRow(dataTag: "center align with padding")
6871 << "Typography"
6872 << QByteArray("height: 200; verticalAlignment: Text.AlignVCenter; topPadding: 10; bottomPadding: 20")
6873 << 100.
6874 << &expectedBaselineCenter
6875 << &expectedBaselineCenter;
6876
6877 QTest::newRow(dataTag: "multiline bottom aligned with padding")
6878 << "The quick brown fox jumps over the lazy dog"
6879 << QByteArray("height: 200; width: 30; verticalAlignment: Text.AlignBottom; wrapMode: TextInput.WordWrap; topPadding: 10; bottomPadding: 20")
6880 << -1.
6881 << &expectedBaselineMultilineBottom
6882 << &expectedBaselineBottom;
6883}
6884
6885void tst_qquicktextinput::baselineOffset()
6886{
6887 QFETCH(QString, text);
6888 QFETCH(QByteArray, bindings);
6889 QFETCH(qreal, setHeight);
6890 QFETCH(ExpectedBaseline, expectedBaseline);
6891 QFETCH(ExpectedBaseline, expectedBaselineEmpty);
6892
6893 QQmlComponent component(&engine);
6894 component.setData(
6895 "import QtQuick 2.6\n"
6896 "TextInput {\n"
6897 + bindings + "\n"
6898 "}", baseUrl: QUrl());
6899
6900 QScopedPointer<QObject> object(component.create());
6901 QQuickTextInput *item = qobject_cast<QQuickTextInput *>(object: object.data());
6902
6903 int passes = setHeight >= 0 ? 2 : 1;
6904 while (passes--) {
6905 QVERIFY(item);
6906 QCOMPARE(item->baselineOffset(), expectedBaselineEmpty(item));
6907 item->setText(text);
6908 QCOMPARE(item->baselineOffset(), expectedBaseline(item));
6909 item->setText(QString());
6910 QCOMPARE(item->baselineOffset(), expectedBaselineEmpty(item));
6911 if (setHeight >= 0)
6912 item->setHeight(setHeight);
6913 }
6914}
6915
6916void tst_qquicktextinput::ensureVisible()
6917{
6918 QQmlComponent component(&engine);
6919 component.setData("import QtQuick 2.0\n TextInput {}", baseUrl: QUrl());
6920 QScopedPointer<QObject> object(component.create());
6921 QQuickTextInput *input = qobject_cast<QQuickTextInput *>(object: object.data());
6922 QVERIFY(input);
6923
6924 input->setWidth(QFontMetrics(input->font()).averageCharWidth() * 3);
6925 input->setText("Hello World");
6926
6927 QTextLayout layout;
6928 layout.setText(input->text());
6929 layout.setFont(input->font());
6930
6931 if (!qmlDisableDistanceField()) {
6932 QTextOption option;
6933 option.setUseDesignMetrics(true);
6934 layout.setTextOption(option);
6935 }
6936 layout.beginLayout();
6937 QTextLine line = layout.createLine();
6938 layout.endLayout();
6939
6940 input->ensureVisible(position: 0);
6941
6942 QCOMPARE(input->boundingRect().x(), qreal(0));
6943 QCOMPARE(input->boundingRect().y(), qreal(0));
6944 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
6945 QCOMPARE(input->boundingRect().height(), line.height());
6946
6947 QSignalSpy cursorSpy(input, SIGNAL(cursorRectangleChanged()));
6948 QVERIFY(cursorSpy.isValid());
6949
6950 input->ensureVisible(position: input->length());
6951
6952 QCOMPARE(cursorSpy.count(), 1);
6953
6954 QCOMPARE(input->boundingRect().x(), input->width() - line.naturalTextWidth());
6955 QCOMPARE(input->boundingRect().y(), qreal(0));
6956 QCOMPARE(input->boundingRect().width(), line.naturalTextWidth() + input->cursorRectangle().width());
6957 QCOMPARE(input->boundingRect().height(), line.height());
6958}
6959
6960void tst_qquicktextinput::padding()
6961{
6962 QScopedPointer<QQuickView> window(new QQuickView);
6963 window->setSource(testFileUrl(fileName: "padding.qml"));
6964 QTRY_COMPARE(window->status(), QQuickView::Ready);
6965 window->show();
6966 QVERIFY(QTest::qWaitForWindowExposed(window.data()));
6967 QQuickItem *root = window->rootObject();
6968 QVERIFY(root);
6969 QQuickTextInput *obj = qobject_cast<QQuickTextInput*>(object: root);
6970 QVERIFY(obj != nullptr);
6971
6972 qreal cw = obj->contentWidth();
6973 qreal ch = obj->contentHeight();
6974
6975 QVERIFY(cw > 0);
6976 QVERIFY(ch > 0);
6977
6978 QCOMPARE(obj->padding(), 10.0);
6979 QCOMPARE(obj->topPadding(), 20.0);
6980 QCOMPARE(obj->leftPadding(), 30.0);
6981 QCOMPARE(obj->rightPadding(), 40.0);
6982 QCOMPARE(obj->bottomPadding(), 50.0);
6983
6984 QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
6985 QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
6986
6987 obj->setTopPadding(2.25);
6988 QCOMPARE(obj->topPadding(), 2.25);
6989 QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
6990
6991 obj->setLeftPadding(3.75);
6992 QCOMPARE(obj->leftPadding(), 3.75);
6993 QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
6994
6995 obj->setRightPadding(4.4);
6996 QCOMPARE(obj->rightPadding(), 4.4);
6997 QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
6998
6999 obj->setBottomPadding(1.11);
7000 QCOMPARE(obj->bottomPadding(), 1.11);
7001 QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
7002
7003 obj->setText("Qt");
7004 QVERIFY(obj->contentWidth() < cw);
7005 QCOMPARE(obj->contentHeight(), ch);
7006 cw = obj->contentWidth();
7007
7008 QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
7009 QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
7010
7011 obj->setFont(QFont("Courier", 96));
7012 QVERIFY(obj->contentWidth() > cw);
7013 QVERIFY(obj->contentHeight() > ch);
7014 cw = obj->contentWidth();
7015 ch = obj->contentHeight();
7016
7017 QCOMPARE(obj->implicitWidth(), qCeil(cw) + obj->leftPadding() + obj->rightPadding());
7018 QCOMPARE(obj->implicitHeight(), qCeil(ch) + obj->topPadding() + obj->bottomPadding());
7019
7020 obj->resetTopPadding();
7021 QCOMPARE(obj->topPadding(), 10.0);
7022 obj->resetLeftPadding();
7023 QCOMPARE(obj->leftPadding(), 10.0);
7024 obj->resetRightPadding();
7025 QCOMPARE(obj->rightPadding(), 10.0);
7026 obj->resetBottomPadding();
7027 QCOMPARE(obj->bottomPadding(), 10.0);
7028
7029 obj->resetPadding();
7030 QCOMPARE(obj->padding(), 0.0);
7031 QCOMPARE(obj->topPadding(), 0.0);
7032 QCOMPARE(obj->leftPadding(), 0.0);
7033 QCOMPARE(obj->rightPadding(), 0.0);
7034 QCOMPARE(obj->bottomPadding(), 0.0);
7035
7036 delete root;
7037}
7038
7039void tst_qquicktextinput::QTBUG_51115_readOnlyResetsSelection()
7040{
7041 QQuickView view;
7042 view.setSource(testFileUrl(fileName: "qtbug51115.qml"));
7043 view.show();
7044 QVERIFY(QTest::qWaitForWindowExposed(&view));
7045 QQuickTextInput *obj = qobject_cast<QQuickTextInput*>(object: view.rootObject());
7046
7047 QCOMPARE(obj->selectedText(), QString());
7048}
7049
7050void tst_qquicktextinput::QTBUG_77814_InsertRemoveNoSelection()
7051{
7052 QQuickView view;
7053 view.setSource(testFileUrl(fileName: "qtbug77841.qml"));
7054 view.show();
7055 QVERIFY(QTest::qWaitForWindowExposed(&view));
7056 QQuickTextInput *textInput = view.rootObject()->findChild<QQuickTextInput*>(aName: "qwe");
7057 QVERIFY(textInput);
7058
7059 QCOMPARE(textInput->selectedText(), QString());
7060}
7061
7062void tst_qquicktextinput::checkCursorDelegateWhenPaddingChanged()
7063{
7064 QQuickView view;
7065 view.setSource(testFileUrl(fileName: "checkCursorDelegateWhenPaddingChanged.qml"));
7066 view.show();
7067 QVERIFY(QTest::qWaitForWindowExposed(&view));
7068
7069 QQuickTextInput *textInput = view.rootObject()->findChild<QQuickTextInput *>(aName: "textInput");
7070 QVERIFY(textInput);
7071
7072 QQuickItem *cursorDelegate = textInput->findChild<QQuickItem *>(aName: "cursorDelegate");
7073 QVERIFY(cursorDelegate);
7074
7075 QCOMPARE(cursorDelegate->x(), textInput->leftPadding());
7076 QCOMPARE(cursorDelegate->y(), textInput->topPadding());
7077
7078 textInput->setPadding(5);
7079 QCOMPARE(cursorDelegate->x(), textInput->leftPadding());
7080 QCOMPARE(cursorDelegate->y(), textInput->topPadding());
7081
7082 textInput->setTopPadding(10);
7083 QCOMPARE(cursorDelegate->x(), textInput->leftPadding());
7084 QCOMPARE(cursorDelegate->y(), textInput->topPadding());
7085
7086 textInput->setLeftPadding(10);
7087 QCOMPARE(cursorDelegate->x(), textInput->leftPadding());
7088 QCOMPARE(cursorDelegate->y(), textInput->topPadding());
7089}
7090
7091/*!
7092 Verifies that TextInput items get focus in/out events with the
7093 correct focus reason set.
7094
7095 Up and Down keys translates to Backtab and Tab focus reasons.
7096
7097 See QTBUG-75862.
7098*/
7099void tst_qquicktextinput::focusReason()
7100{
7101 QQuickView view;
7102 view.setSource(testFileUrl(fileName: "focusReason.qml"));
7103
7104 QQuickTextInput *first = view.rootObject()->findChild<QQuickTextInput *>(aName: "first");
7105 QQuickTextInput *second = view.rootObject()->findChild<QQuickTextInput *>(aName: "second");
7106 QQuickTextInput *third = view.rootObject()->findChild<QQuickTextInput *>(aName: "third");
7107 QVERIFY(first && second && third);
7108
7109 class FocusEventFilter : public QObject
7110 {
7111 public:
7112 using QObject::QObject;
7113
7114 QHash<QObject*, Qt::FocusReason> lastFocusReason;
7115 protected:
7116 bool eventFilter(QObject *o, QEvent *e)
7117 {
7118 if (e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) {
7119 QFocusEvent *fe = static_cast<QFocusEvent*>(e);
7120 lastFocusReason[o] = fe->reason();
7121 }
7122 return QObject::eventFilter(watched: o, event: e);
7123 }
7124 } eventFilter;
7125 first->installEventFilter(filterObj: &eventFilter);
7126 second->installEventFilter(filterObj: &eventFilter);
7127 third->installEventFilter(filterObj: &eventFilter);
7128
7129 view.show();
7130 QVERIFY(QTest::qWaitForWindowActive(&view));
7131
7132 QCOMPARE(qApp->focusObject(), first);
7133 // on some platforms we don't get ActiveWindowFocusReason; tolerate this,
7134 // it's not what we are testing in this test
7135 if (eventFilter.lastFocusReason[first] != Qt::ActiveWindowFocusReason) {
7136 QEXPECT_FAIL("", qPrintable(QString("No window activation event on the %1 platform")
7137 .arg(QGuiApplication::platformName())),
7138 Continue);
7139 }
7140 QCOMPARE(eventFilter.lastFocusReason[first], Qt::ActiveWindowFocusReason);
7141
7142 QTest::mouseClick(window: &view, button: Qt::LeftButton, stateKey: {},
7143 pos: (second->boundingRect().center() + second->position()).toPoint());
7144 QTRY_COMPARE(qApp->focusObject(), second);
7145 QCOMPARE(eventFilter.lastFocusReason[first], Qt::MouseFocusReason);
7146 QCOMPARE(eventFilter.lastFocusReason[second], Qt::MouseFocusReason);
7147
7148 QTest::keyClick(window: &view, key: Qt::Key_Tab);
7149 QCOMPARE(qApp->focusObject(), third);
7150 QCOMPARE(eventFilter.lastFocusReason[second], Qt::TabFocusReason);
7151 QCOMPARE(eventFilter.lastFocusReason[third], Qt::TabFocusReason);
7152
7153 QTest::keyClick(window: &view, key: Qt::Key_Backtab);
7154 QCOMPARE(qApp->focusObject(), second);
7155 QCOMPARE(eventFilter.lastFocusReason[third], Qt::BacktabFocusReason);
7156 QCOMPARE(eventFilter.lastFocusReason[second], Qt::BacktabFocusReason);
7157
7158 QTest::keyClick(window: &view, key: Qt::Key_Up);
7159 QCOMPARE(qApp->focusObject(), first);
7160 QCOMPARE(eventFilter.lastFocusReason[second], Qt::BacktabFocusReason);
7161 QCOMPARE(eventFilter.lastFocusReason[first], Qt::BacktabFocusReason);
7162
7163 QTest::keyClick(window: &view, key: Qt::Key_Down);
7164 QCOMPARE(qApp->focusObject(), second);
7165 QCOMPARE(eventFilter.lastFocusReason[second], Qt::TabFocusReason);
7166 QCOMPARE(eventFilter.lastFocusReason[first], Qt::TabFocusReason);
7167}
7168
7169QTEST_MAIN(tst_qquicktextinput)
7170
7171#include "tst_qquicktextinput.moc"
7172

source code of qtdeclarative/tests/auto/quick/qquicktextinput/tst_qquicktextinput.cpp