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
29
30#include <QtTest/QtTest>
31
32#include <qtextedit.h>
33#include <qtextcursor.h>
34#include <qtextlist.h>
35#include <qdebug.h>
36#include <qapplication.h>
37#include <qclipboard.h>
38#include <qtextbrowser.h>
39#include <private/qwidgettextcontrol_p.h>
40#include <qscrollbar.h>
41#include <qtextobject.h>
42#include <qtexttable.h>
43#include <qpainter.h>
44#include <qimagereader.h>
45#include <qimagewriter.h>
46#include <qcommonstyle.h>
47#include <qlayout.h>
48#include <qdir.h>
49#include <qpaintengine.h>
50
51#include <qabstracttextdocumentlayout.h>
52#include <qtextdocumentfragment.h>
53#include <qsyntaxhighlighter.h>
54
55#include "../../../shared/platformclipboard.h"
56#include "../../../shared/platforminputcontext.h"
57#include <private/qinputmethod_p.h>
58
59//Used in copyAvailable
60typedef QPair<Qt::Key, Qt::KeyboardModifier> keyPairType;
61typedef QList<keyPairType> pairListType;
62Q_DECLARE_METATYPE(keyPairType);
63
64Q_DECLARE_METATYPE(QList<QInputMethodEvent::Attribute>);
65
66QT_FORWARD_DECLARE_CLASS(QTextEdit)
67
68class tst_QTextEdit : public QObject
69{
70 Q_OBJECT
71public:
72 tst_QTextEdit();
73
74public slots:
75 void initTestCase();
76 void cleanupTestCase();
77 void init();
78 void cleanup();
79private slots:
80 void getSetCheck();
81 void inlineAttributesOnInsert();
82 void inlineAttributesOnSelection();
83 void inlineAttributeSymmetry();
84 void inlineAttributeSymmetryWithSelection();
85 void autoBulletList1();
86 void autoBulletList2();
87 void preserveCharFormatAfterNewline();
88#ifndef QT_NO_CLIPBOARD
89 void clearMustNotChangeClipboard();
90#endif
91 void clearMustNotResetRootFrameMarginToDefault();
92 void clearShouldPreserveTheCurrentCharFormat();
93 void clearShouldClearExtraSelections();
94 void paragSeparatorOnPlaintextAppend();
95 void layoutingLoop();
96#ifndef QT_NO_CLIPBOARD
97 void selectAllSetsNotSelection();
98#endif
99 void asciiTab();
100 void setDocument();
101 void setDocument_shared();
102 void mergeCurrentCharFormat();
103 void mergeCurrentBlockCharFormat();
104 void emptyAppend();
105 void appendOnEmptyDocumentShouldReuseInitialParagraph();
106 void cursorPositionChanged();
107 void setTextCursor();
108#ifndef QT_NO_CLIPBOARD
109 void undoAvailableAfterPaste();
110#endif
111 void undoRedoAvailableRepetition();
112 void appendShouldUseCurrentFormat();
113 void appendShouldNotTouchTheSelection();
114 void backspace();
115 void shiftBackspace();
116 void undoRedo();
117 void preserveCharFormatInAppend();
118#ifndef QT_NO_CLIPBOARD
119 void copyAndSelectAllInReadonly();
120#endif
121 void noPropertiesOnDefaultTextEditCharFormat();
122 void setPlainTextShouldUseCurrentCharFormat();
123 void setPlainTextShouldEmitTextChangedOnce();
124 void overwriteMode();
125 void shiftDownInLineLastShouldSelectToEnd_data();
126 void shiftDownInLineLastShouldSelectToEnd();
127 void undoRedoShouldRepositionTextEditCursor();
128 void lineWrapModes();
129#ifndef QT_NO_CURSOR
130 void mouseCursorShape();
131#endif
132 void implicitClear();
133 void undoRedoAfterSetContent();
134 void numPadKeyNavigation();
135 void moveCursor();
136#ifndef QT_NO_CLIPBOARD
137 void mimeDataReimplementations();
138#endif
139 void ctrlEnterShouldInsertLineSeparator_NOT();
140 void shiftEnterShouldInsertLineSeparator();
141 void selectWordsFromStringsContainingSeparators_data();
142 void selectWordsFromStringsContainingSeparators();
143#ifndef QT_NO_CLIPBOARD
144 void canPaste();
145 void copyAvailable_data();
146 void copyAvailable();
147#endif
148 void ensureCursorVisibleOnInitialShow();
149 void setHtmlInsideResizeEvent();
150 void colorfulAppend();
151 void ensureVisibleWithRtl();
152 void preserveCharFormatAfterSetPlainText();
153 void extraSelections();
154 void adjustScrollbars();
155 void currentCharFormatChanged();
156 void textObscuredByScrollbars();
157 void setTextPreservesUndoRedoEnabled();
158 void wordWrapProperty();
159 void lineWrapProperty();
160 void selectionChanged();
161#ifndef QT_NO_CLIPBOARD
162 void copyPasteBackgroundImage();
163 void copyPasteForegroundImage();
164#endif
165 void setText();
166 void cursorRect();
167#ifdef QT_BUILD_INTERNAL
168 void fullWidthSelection_data();
169 void fullWidthSelection();
170 void fullWidthSelection2();
171 void setDocumentPreservesPalette();
172#endif
173 void pasteFromQt3RichText();
174 void noWrapBackgrounds();
175 void preserveCharFormatAfterUnchangingSetPosition();
176 void twoSameInputMethodEvents();
177#ifndef QT_NO_CONTEXTMENU
178 void taskQTBUG_7902_contextMenuCrash();
179#endif
180 void bidiVisualMovement_data();
181 void bidiVisualMovement();
182
183 void bidiLogicalMovement_data();
184 void bidiLogicalMovement();
185
186 void inputMethodEvent();
187 void inputMethodSelection();
188 void inputMethodQuery();
189 void inputMethodQueryImHints_data();
190 void inputMethodQueryImHints();
191 void inputMethodCursorRect();
192
193 void highlightLongLine();
194
195 void countTextChangedOnRemove();
196
197#ifndef QT_NO_REGEXP
198 void findWithRegExp();
199 void findBackwardWithRegExp();
200 void findWithRegExpReturnsFalseIfNoMoreResults();
201#endif
202
203#if QT_CONFIG(regularexpression)
204 void findWithRegularExpression();
205 void findBackwardWithRegularExpression();
206 void findWithRegularExpressionReturnsFalseIfNoMoreResults();
207#endif
208
209#if QT_CONFIG(wheelevent)
210 void wheelEvent();
211#endif
212
213 void preeditCharFormat_data();
214 void preeditCharFormat();
215
216private:
217 void createSelection();
218 int blockCount() const;
219 void compareWidgetAndImage(QTextEdit &widget, const QString &imageFileName);
220
221 QTextEdit *ed;
222 qreal rootFrameMargin;
223 PlatformInputContext m_platformInputContext;
224 const QString m_fullWidthSelectionImagesFolder;
225};
226
227// Testing get/set functions
228void tst_QTextEdit::getSetCheck()
229{
230 QTextEdit obj1;
231 // QTextDocument * QTextEdit::document()
232 // void QTextEdit::setDocument(QTextDocument *)
233 QTextDocument *var1 = new QTextDocument;
234 obj1.setDocument(var1);
235 QCOMPARE(var1, obj1.document());
236 obj1.setDocument((QTextDocument *)0);
237 QVERIFY(var1 != obj1.document()); // QTextEdit creates a new document when setting 0
238 QVERIFY((QTextDocument *)0 != obj1.document());
239 delete var1;
240
241 // AutoFormatting QTextEdit::autoFormatting()
242 // void QTextEdit::setAutoFormatting(AutoFormatting)
243 obj1.setAutoFormatting(QTextEdit::AutoFormatting(QTextEdit::AutoNone));
244 QCOMPARE(QTextEdit::AutoFormatting(QTextEdit::AutoNone), obj1.autoFormatting());
245 obj1.setAutoFormatting(QTextEdit::AutoFormatting(QTextEdit::AutoBulletList));
246 QCOMPARE(QTextEdit::AutoFormatting(QTextEdit::AutoBulletList), obj1.autoFormatting());
247 obj1.setAutoFormatting(QTextEdit::AutoFormatting(QTextEdit::AutoAll));
248 QCOMPARE(QTextEdit::AutoFormatting(QTextEdit::AutoAll), obj1.autoFormatting());
249
250 // bool QTextEdit::tabChangesFocus()
251 // void QTextEdit::setTabChangesFocus(bool)
252 obj1.setTabChangesFocus(false);
253 QCOMPARE(false, obj1.tabChangesFocus());
254 obj1.setTabChangesFocus(true);
255 QCOMPARE(true, obj1.tabChangesFocus());
256
257 // LineWrapMode QTextEdit::lineWrapMode()
258 // void QTextEdit::setLineWrapMode(LineWrapMode)
259 obj1.setLineWrapMode(QTextEdit::LineWrapMode(QTextEdit::NoWrap));
260 QCOMPARE(QTextEdit::LineWrapMode(QTextEdit::NoWrap), obj1.lineWrapMode());
261 obj1.setLineWrapMode(QTextEdit::LineWrapMode(QTextEdit::WidgetWidth));
262 QCOMPARE(QTextEdit::LineWrapMode(QTextEdit::WidgetWidth), obj1.lineWrapMode());
263 obj1.setLineWrapMode(QTextEdit::LineWrapMode(QTextEdit::FixedPixelWidth));
264 QCOMPARE(QTextEdit::LineWrapMode(QTextEdit::FixedPixelWidth), obj1.lineWrapMode());
265 obj1.setLineWrapMode(QTextEdit::LineWrapMode(QTextEdit::FixedColumnWidth));
266 QCOMPARE(QTextEdit::LineWrapMode(QTextEdit::FixedColumnWidth), obj1.lineWrapMode());
267
268 // int QTextEdit::lineWrapColumnOrWidth()
269 // void QTextEdit::setLineWrapColumnOrWidth(int)
270 obj1.setLineWrapColumnOrWidth(0);
271 QCOMPARE(0, obj1.lineWrapColumnOrWidth());
272 obj1.setLineWrapColumnOrWidth(INT_MIN);
273 QCOMPARE(INT_MIN, obj1.lineWrapColumnOrWidth());
274 obj1.setLineWrapColumnOrWidth(INT_MAX);
275 QCOMPARE(INT_MAX, obj1.lineWrapColumnOrWidth());
276
277 // bool QTextEdit::overwriteMode()
278 // void QTextEdit::setOverwriteMode(bool)
279 obj1.setOverwriteMode(false);
280 QCOMPARE(false, obj1.overwriteMode());
281 obj1.setOverwriteMode(true);
282 QCOMPARE(true, obj1.overwriteMode());
283
284 // int QTextEdit::tabStopWidth()
285 // void QTextEdit::setTabStopWidth(int)
286 obj1.setTabStopDistance(0);
287 QCOMPARE(0, obj1.tabStopDistance());
288 obj1.setTabStopDistance(-1);
289 QCOMPARE(0, obj1.tabStopDistance()); // Makes no sense to set a negative tabstop value
290 obj1.setTabStopDistance(std::numeric_limits<qreal>::max());
291 QCOMPARE(std::numeric_limits<qreal>::max(), obj1.tabStopDistance());
292
293 // bool QTextEdit::acceptRichText()
294 // void QTextEdit::setAcceptRichText(bool)
295 obj1.setAcceptRichText(false);
296 QCOMPARE(false, obj1.acceptRichText());
297 obj1.setAcceptRichText(true);
298 QCOMPARE(true, obj1.acceptRichText());
299
300 // qreal QTextEdit::fontPointSize()
301 // void QTextEdit::setFontPointSize(qreal)
302 obj1.setFontPointSize(qreal(1.1));
303 QCOMPARE(qreal(1.1), obj1.fontPointSize());
304 // we currently assert in QFont::setPointSizeF for that
305 //obj1.setFontPointSize(0.0);
306 //QCOMPARE(1.1, obj1.fontPointSize()); // Should not accept 0.0 => keep old
307
308 // int QTextEdit::fontWeight()
309 // void QTextEdit::setFontWeight(int)
310 obj1.setFontWeight(1);
311 QCOMPARE(1, obj1.fontWeight()); // Range<1, 99>
312 obj1.setFontWeight(99);
313 QCOMPARE(99, obj1.fontWeight()); // Range<1, 99>
314 /* assertion in qfont.cpp
315 obj1.setFontWeight(INT_MIN);
316 QCOMPARE(1, obj1.fontWeight()); // Range<1, 99>
317 obj1.setFontWeight(INT_MAX);
318 QCOMPARE(99, obj1.fontWeight()); // Range<1, 99>
319 */
320
321 // bool QTextEdit::fontUnderline()
322 // void QTextEdit::setFontUnderline(bool)
323 obj1.setFontUnderline(false);
324 QCOMPARE(false, obj1.fontUnderline());
325 obj1.setFontUnderline(true);
326 QCOMPARE(true, obj1.fontUnderline());
327
328 // bool QTextEdit::fontItalic()
329 // void QTextEdit::setFontItalic(bool)
330 obj1.setFontItalic(false);
331 QCOMPARE(false, obj1.fontItalic());
332 obj1.setFontItalic(true);
333 QCOMPARE(true, obj1.fontItalic());
334}
335
336class QtTestDocumentLayout : public QAbstractTextDocumentLayout
337{
338 Q_OBJECT
339public:
340 inline QtTestDocumentLayout(QTextEdit *edit, QTextDocument *doc, int &itCount)
341 : QAbstractTextDocumentLayout(doc), useBiggerSize(false), ed(edit), iterationCounter(itCount) {}
342
343 virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &) {}
344
345 virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const { return 0; }
346
347 virtual void documentChanged(int, int, int) {}
348
349 virtual int pageCount() const { return 1; }
350
351 virtual QSizeF documentSize() const { return usedSize; }
352
353 virtual QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); }
354 virtual QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); }
355
356 bool useBiggerSize;
357 QSize usedSize;
358
359 QTextEdit *ed;
360
361 int &iterationCounter;
362};
363
364tst_QTextEdit::tst_QTextEdit() :
365 m_fullWidthSelectionImagesFolder(QFINDTESTDATA("fullWidthSelection"))
366{
367
368}
369
370void tst_QTextEdit::initTestCase()
371{
372 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
373 inputMethodPrivate->testContext = &m_platformInputContext;
374 QVERIFY2(!m_fullWidthSelectionImagesFolder.isEmpty(), qPrintable(QString::fromLatin1("Cannot locate 'fullWidthSelection' starting from %1").arg(QDir::currentPath())));
375}
376
377void tst_QTextEdit::cleanupTestCase()
378{
379 QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
380 inputMethodPrivate->testContext = 0;
381}
382
383void tst_QTextEdit::init()
384{
385 ed = new QTextEdit(0);
386 rootFrameMargin = ed->document()->documentMargin();
387}
388
389void tst_QTextEdit::cleanup()
390{
391 delete ed;
392 ed = 0;
393}
394
395void tst_QTextEdit::inlineAttributesOnInsert()
396{
397 const QColor blue(Qt::blue);
398 QVERIFY(ed->textCursor().charFormat().foreground().color() != blue);
399
400 ed->setTextColor(blue);
401 QTest::keyClick(widget: ed, key: Qt::Key_A);
402
403 QCOMPARE(ed->textCursor().charFormat().foreground().color(), blue);
404}
405
406void tst_QTextEdit::inlineAttributesOnSelection()
407{
408 createSelection();
409
410 ed->setFontItalic(true);
411
412 QVERIFY(ed->textCursor().charFormat().fontItalic());
413}
414
415void tst_QTextEdit::inlineAttributeSymmetry()
416{
417 ed->setFontPointSize(42.0);
418 QCOMPARE(double(ed->fontPointSize()), 42.0);
419
420 ed->setFontFamily("Test");
421 QCOMPARE(ed->fontFamily(), QString("Test"));
422
423 ed->setFontWeight(QFont::Bold);
424 QCOMPARE((int)ed->fontWeight(), (int)QFont::Bold);
425
426 ed->setFontUnderline(true);
427 QCOMPARE(ed->fontUnderline(), true);
428
429 ed->setFontItalic(true);
430 QCOMPARE(ed->fontItalic(), true);
431
432 ed->setTextColor(Qt::blue);
433 QCOMPARE(ed->textColor(), QColor(Qt::blue));
434
435 ed->setTextBackgroundColor(Qt::red);
436 QCOMPARE(ed->textBackgroundColor(), QColor(Qt::red));
437
438 ed->setAlignment(Qt::AlignRight);
439 QCOMPARE((int)ed->alignment(), (int)Qt::AlignRight);
440}
441
442void tst_QTextEdit::inlineAttributeSymmetryWithSelection()
443{
444 createSelection();
445
446 inlineAttributeSymmetry();
447}
448
449void tst_QTextEdit::autoBulletList1()
450{
451 ed->setAutoFormatting(QTextEdit::AutoBulletList);
452
453 QTest::keyClick(widget: ed, key: Qt::Key_Return);
454 QTest::keyClicks(widget: ed, sequence: "*This should become a list");
455
456 QVERIFY(ed->textCursor().currentList());
457 QCOMPARE(ed->textCursor().currentList()->format().style(), QTextListFormat::ListDisc);
458}
459
460void tst_QTextEdit::autoBulletList2()
461{
462 ed->setAutoFormatting(QTextEdit::AutoNone);
463 QTest::keyClick(widget: ed, key: Qt::Key_Return);
464 QTest::keyClicks(widget: ed, sequence: "*This should NOT become a list");
465
466 QVERIFY(!ed->textCursor().currentList());
467}
468
469void tst_QTextEdit::preserveCharFormatAfterNewline()
470{
471 ed->setTextColor(Qt::blue);
472 QTest::keyClicks(widget: ed, sequence: "Hello");
473
474 QTest::keyClick(widget: ed, key: Qt::Key_Return);
475
476 QCOMPARE(ed->textColor(), QColor(Qt::blue));
477}
478
479void tst_QTextEdit::createSelection()
480{
481 QTest::keyClicks(widget: ed, sequence: "Hello World");
482 /* go to start */
483#ifndef Q_OS_MAC
484 QTest::keyClick(widget: ed, key: Qt::Key_Home, modifier: Qt::ControlModifier);
485#else
486 QTest::keyClick(ed, Qt::Key_Home);
487#endif
488 QCOMPARE(ed->textCursor().position(), 0);
489 /* select until end of text */
490#ifndef Q_OS_MAC
491 QTest::keyClick(widget: ed, key: Qt::Key_End, modifier: Qt::ControlModifier | Qt::ShiftModifier);
492#else
493 QTest::keyClick(ed, Qt::Key_End, Qt::ShiftModifier);
494#endif
495 QCOMPARE(ed->textCursor().position(), 11);
496}
497
498#ifndef QT_NO_CLIPBOARD
499void tst_QTextEdit::clearMustNotChangeClipboard()
500{
501 if (!PlatformClipboard::isAvailable())
502 QSKIP("Clipboard not working with cron-started unit tests");
503
504 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
505 QSKIP("Wayland: This fails. Figure out why.");
506
507 ed->textCursor().insertText(text: "Hello World");
508 QString txt("This is different text");
509 QApplication::clipboard()->setText(txt);
510 ed->clear();
511 QCOMPARE(QApplication::clipboard()->text(), txt);
512}
513#endif
514
515void tst_QTextEdit::clearMustNotResetRootFrameMarginToDefault()
516{
517 QCOMPARE(ed->document()->rootFrame()->frameFormat().margin(), rootFrameMargin);
518 ed->clear();
519 QCOMPARE(ed->document()->rootFrame()->frameFormat().margin(), rootFrameMargin);
520}
521
522void tst_QTextEdit::clearShouldPreserveTheCurrentCharFormat()
523{
524 ed->setFontUnderline(true);
525 QVERIFY(ed->fontUnderline());
526 ed->clear();
527 QVERIFY(ed->fontUnderline());
528}
529
530void tst_QTextEdit::clearShouldClearExtraSelections()
531{
532 QTextEdit::ExtraSelection sel;
533 sel.cursor = ed->textCursor();
534 sel.format.setProperty(propertyId: QTextFormat::FullWidthSelection, value: true);
535 ed->setExtraSelections(QList<QTextEdit::ExtraSelection>() << sel);
536 QCOMPARE(ed->extraSelections().count(), 1);
537 ed->clear();
538 QCOMPARE(ed->extraSelections().count(), 0);
539}
540
541void tst_QTextEdit::paragSeparatorOnPlaintextAppend()
542{
543 ed->append(text: "Hello\nWorld");
544 int cnt = 0;
545 QTextBlock blk = ed->document()->begin();
546 while (blk.isValid()) {
547 ++cnt;
548 blk = blk.next();
549 }
550 QCOMPARE(cnt, 2);
551}
552
553void tst_QTextEdit::layoutingLoop()
554{
555 QPointer<QTextEdit> ed = new QTextEdit(0);
556 // this is a testcase for an ugly layouting problem, causing an infinite loop.
557 // QTextEdit's resizeEvent has a long comment about what and why it can happen.
558
559 int callsToSetPageSize = 0;
560
561 QTextDocument *doc = new QTextDocument;
562 QtTestDocumentLayout *lout = new QtTestDocumentLayout(ed, doc, callsToSetPageSize);
563 doc->setDocumentLayout(lout);
564 ed->setDocument(doc);
565
566 ed->show();
567 ed->resize(w: 100, h: 100);
568
569 qApp->processEvents();
570 delete doc;
571 delete ed;
572
573 // ###### should need less!
574 QVERIFY(callsToSetPageSize < 10);
575}
576
577#ifndef QT_NO_CLIPBOARD
578void tst_QTextEdit::selectAllSetsNotSelection()
579{
580 if (!QApplication::clipboard()->supportsSelection()) {
581 QSKIP("Test only relevant for systems with selection");
582 return;
583 }
584
585 QApplication::clipboard()->setText(QString("foobar"), mode: QClipboard::Selection);
586 QCOMPARE(QApplication::clipboard()->text(QClipboard::Selection), QString("foobar"));
587
588 ed->insertPlainText(text: "Hello World");
589 ed->selectAll();
590
591 QCOMPARE(QApplication::clipboard()->text(QClipboard::Selection), QString::fromLatin1("foobar"));
592}
593#endif
594void tst_QTextEdit::asciiTab()
595{
596 QTextEdit edit;
597 edit.setPlainText("\t");
598 edit.show();
599 qApp->processEvents();
600 QCOMPARE(edit.toPlainText().at(0), QChar('\t'));
601}
602
603
604void tst_QTextEdit::setDocument()
605{
606 QTextDocument *document = new QTextDocument(ed);
607 QCOMPARE(document->isModified(), false);
608 QCOMPARE(document->isUndoAvailable(), false);
609 QTextCursor(document).insertText(text: "Test");
610 QCOMPARE(document->isUndoAvailable(), true);
611 QCOMPARE(document->isModified(), true);
612 ed->setDocument(document);
613 QCOMPARE(ed->toPlainText(), QString("Test"));
614 QCOMPARE(document->isUndoAvailable(), true);
615 QCOMPARE(document->isModified(), true);
616}
617
618void tst_QTextEdit::setDocument_shared()
619{
620 QTextDocument *document = new QTextDocument(ed);
621 QCOMPARE(document->isModified(), false);
622 QCOMPARE(document->isUndoAvailable(), false);
623 QTextCursor(document).insertText(text: "Test");
624 QCOMPARE(document->isUndoAvailable(), true);
625 document->setModified(false);
626 ed->setDocument(document);
627 QCOMPARE(ed->toPlainText(), QString("Test"));
628 QCOMPARE(document->isUndoAvailable(), true);
629 QCOMPARE(document->isModified(), false);
630 QTextCursor(document).insertText(text: "Test2");
631 QCOMPARE(document->isModified(), true);
632 QTextEdit editor2;
633 editor2.setDocument(document);
634 QCOMPARE(document->isUndoAvailable(), true);
635 QCOMPARE(document->isModified(), true);
636}
637
638void tst_QTextEdit::mergeCurrentCharFormat()
639{
640 ed->setPlainText("Hello Test World");
641 QTextCursor cursor = ed->textCursor();
642 cursor.setPosition(pos: 7);
643 ed->setTextCursor(cursor);
644
645 QTextCharFormat mod;
646 mod.setFontItalic(true);
647 mod.setForeground(Qt::red);
648 ed->mergeCurrentCharFormat(modifier: mod);
649
650 cursor.movePosition(op: QTextCursor::Right);
651 cursor.movePosition(op: QTextCursor::Right);
652 // do NOT select the current word under the cursor, /JUST/
653 // call mergeCharFormat on the cursor
654 QVERIFY(!cursor.charFormat().fontItalic());
655 QVERIFY(cursor.charFormat().foreground().color() != Qt::red);
656}
657
658void tst_QTextEdit::mergeCurrentBlockCharFormat()
659{
660 ed->setPlainText("FirstLine\n\nThirdLine");
661 QTextCursor cursor = ed->textCursor();
662 cursor.movePosition(op: QTextCursor::Start);
663 cursor.movePosition(op: QTextCursor::Down);
664
665 // make sure we're in the empty second line
666 QVERIFY(cursor.atBlockStart());
667 QVERIFY(cursor.atBlockEnd());
668
669 ed->setTextCursor(cursor);
670
671 QTextCharFormat mod;
672 mod.setForeground(Qt::red);
673 ed->mergeCurrentCharFormat(modifier: mod);
674
675 QVERIFY(cursor.blockCharFormat().foreground().color() != Qt::red);
676 cursor.movePosition(op: QTextCursor::Up);
677 QVERIFY(cursor.blockCharFormat().foreground().color() != Qt::red);
678 cursor.movePosition(op: QTextCursor::Down);
679 cursor.movePosition(op: QTextCursor::Down);
680 QVERIFY(cursor.blockCharFormat().foreground().color() != Qt::red);
681}
682
683int tst_QTextEdit::blockCount() const
684{
685 int blocks = 0;
686 for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next())
687 ++blocks;
688 return blocks;
689}
690
691// Supporter issue #56783
692void tst_QTextEdit::emptyAppend()
693{
694 ed->append(text: "Blah");
695 QCOMPARE(blockCount(), 1);
696 ed->append(text: QString());
697 QCOMPARE(blockCount(), 2);
698 ed->append(text: QString(" "));
699 QCOMPARE(blockCount(), 3);
700}
701
702void tst_QTextEdit::appendOnEmptyDocumentShouldReuseInitialParagraph()
703{
704 QCOMPARE(blockCount(), 1);
705 ed->append(text: "Blah");
706 QCOMPARE(blockCount(), 1);
707}
708
709class CursorPositionChangedRecorder : public QObject
710{
711 Q_OBJECT
712public:
713 inline CursorPositionChangedRecorder(QTextEdit *ed)
714 : editor(ed)
715 {
716 connect(sender: editor, SIGNAL(cursorPositionChanged()), receiver: this, SLOT(recordCursorPos()));
717 }
718
719 QList<int> cursorPositions;
720
721private slots:
722 void recordCursorPos()
723 {
724 cursorPositions.append(t: editor->textCursor().position());
725 }
726
727private:
728 QTextEdit *editor;
729};
730
731void tst_QTextEdit::cursorPositionChanged()
732{
733 QSignalSpy spy(ed, SIGNAL(cursorPositionChanged()));
734 ed->show();
735
736 spy.clear();
737 QTest::keyClick(widget: ed, key: Qt::Key_A);
738 QCOMPARE(spy.count(), 1);
739
740 QTextCursor cursor = ed->textCursor();
741 cursor.movePosition(op: QTextCursor::Start);
742 ed->setTextCursor(cursor);
743 cursor.movePosition(op: QTextCursor::End);
744 spy.clear();
745 cursor.insertText(text: "Test");
746 QCOMPARE(spy.count(), 0);
747
748 cursor.movePosition(op: QTextCursor::End);
749 ed->setTextCursor(cursor);
750 cursor.movePosition(op: QTextCursor::Start);
751 spy.clear();
752 cursor.insertText(text: "Test");
753 QCOMPARE(spy.count(), 1);
754
755 spy.clear();
756 QTest::keyClick(widget: ed, key: Qt::Key_Left);
757 QCOMPARE(spy.count(), 1);
758
759 cursor.movePosition(op: QTextCursor::Start);
760 ed->setTextCursor(cursor);
761 spy.clear();
762 QVERIFY(!ed->textCursor().hasSelection());
763 QTest::mouseDClick(widget: ed->viewport(), button: Qt::LeftButton, stateKey: {}, pos: ed->cursorRect().center());
764 QVERIFY(ed->textCursor().hasSelection());
765
766 QCOMPARE(spy.count(), 1);
767
768 CursorPositionChangedRecorder spy2(ed);
769 QVERIFY(ed->textCursor().position() > 0);
770 ed->setPlainText("Hello World");
771 QCOMPARE(spy2.cursorPositions.count(), 1);
772 QCOMPARE(spy2.cursorPositions.at(0), 0);
773 QCOMPARE(ed->textCursor().position(), 0);
774}
775
776void tst_QTextEdit::setTextCursor()
777{
778 QSignalSpy spy(ed, SIGNAL(cursorPositionChanged()));
779
780 ed->setPlainText("Test");
781 QTextCursor cursor = ed->textCursor();
782 cursor.movePosition(op: QTextCursor::Start);
783 cursor.movePosition(op: QTextCursor::NextCharacter);
784
785 spy.clear();
786
787 ed->setTextCursor(cursor);
788 QCOMPARE(spy.count(), 1);
789}
790
791#ifndef QT_NO_CLIPBOARD
792void tst_QTextEdit::undoAvailableAfterPaste()
793{
794 if (!PlatformClipboard::isAvailable())
795 QSKIP("Clipboard not working with cron-started unit tests");
796
797 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
798 QSKIP("Wayland: This fails. Figure out why.");
799
800 QSignalSpy spy(ed->document(), SIGNAL(undoAvailable(bool)));
801
802 const QString txt("Test");
803 QApplication::clipboard()->setText(txt);
804 ed->paste();
805 QVERIFY(spy.count() >= 1);
806 QCOMPARE(ed->toPlainText(), txt);
807}
808#endif
809
810class UndoRedoRecorder : public QObject
811{
812 Q_OBJECT
813public:
814 UndoRedoRecorder(QTextDocument *doc)
815 : undoRepetitions(false)
816 , redoRepetitions(false)
817 , undoCount(0)
818 , redoCount(0)
819 {
820 connect(sender: doc, SIGNAL(undoAvailable(bool)), receiver: this, SLOT(undoAvailable(bool)));
821 connect(sender: doc, SIGNAL(redoAvailable(bool)), receiver: this, SLOT(redoAvailable(bool)));
822 }
823
824 bool undoRepetitions;
825 bool redoRepetitions;
826
827private slots:
828 void undoAvailable(bool enabled) {
829 if (undoCount > 0 && enabled == lastUndoEnabled)
830 undoRepetitions = true;
831
832 ++undoCount;
833 lastUndoEnabled = enabled;
834 }
835
836 void redoAvailable(bool enabled) {
837 if (redoCount > 0 && enabled == lastRedoEnabled)
838 redoRepetitions = true;
839
840 ++redoCount;
841 lastRedoEnabled = enabled;
842 }
843
844private:
845 bool lastUndoEnabled;
846 bool lastRedoEnabled;
847
848 int undoCount;
849 int redoCount;
850};
851
852void tst_QTextEdit::undoRedoAvailableRepetition()
853{
854 UndoRedoRecorder spy(ed->document());
855
856 ed->textCursor().insertText(text: "ABC\n\nDEF\n\nGHI\n");
857 ed->textCursor().insertText(text: "foo\n");
858 ed->textCursor().insertText(text: "bar\n");
859 ed->undo(); ed->undo(); ed->undo();
860 ed->redo(); ed->redo(); ed->redo();
861
862 QVERIFY(!spy.undoRepetitions);
863 QVERIFY(!spy.redoRepetitions);
864}
865
866void tst_QTextEdit::appendShouldUseCurrentFormat()
867{
868 ed->textCursor().insertText(text: "A");
869 QTextCharFormat fmt;
870 fmt.setForeground(Qt::blue);
871 fmt.setFontItalic(true);
872 ed->setCurrentCharFormat(fmt);
873 ed->append(text: "Hello");
874 const QColor blue(Qt::blue);
875
876 QTextCursor cursor(ed->document());
877
878 QVERIFY(cursor.movePosition(QTextCursor::NextCharacter));
879 QVERIFY(cursor.charFormat().foreground().color() != blue);
880 QVERIFY(!cursor.charFormat().fontItalic());
881
882 QVERIFY(cursor.movePosition(QTextCursor::NextBlock));
883
884 {
885 QTextCursor tmp = cursor;
886 tmp.movePosition(op: QTextCursor::End, QTextCursor::KeepAnchor);
887 QCOMPARE(tmp.selectedText(), QString::fromLatin1("Hello"));
888 }
889
890 QVERIFY(cursor.movePosition(QTextCursor::NextCharacter));
891 QCOMPARE(cursor.charFormat().foreground().color(), blue);
892 QVERIFY(cursor.charFormat().fontItalic());
893}
894
895void tst_QTextEdit::appendShouldNotTouchTheSelection()
896{
897 QTextCursor cursor(ed->document());
898 QTextCharFormat fmt;
899 fmt.setForeground(Qt::blue);
900 cursor.insertText(text: "H", format: fmt);
901 fmt.setForeground(Qt::red);
902 cursor.insertText(text: "ey", format: fmt);
903
904 cursor.insertText(text: "some random text inbetween");
905
906 cursor.movePosition(op: QTextCursor::Start);
907 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
908 QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue));
909 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
910 QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red));
911 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
912 QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red));
913 QCOMPARE(cursor.selectedText(), QString("Hey"));
914
915 ed->setTextCursor(cursor);
916 QVERIFY(ed->textCursor().hasSelection());
917
918 ed->append(text: "<b>Some Bold Text</b>");
919 cursor.movePosition(op: QTextCursor::Start);
920 cursor.movePosition(op: QTextCursor::NextCharacter);
921 QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue));
922}
923
924void tst_QTextEdit::backspace()
925{
926 QTextCursor cursor = ed->textCursor();
927
928 QTextListFormat listFmt;
929 listFmt.setStyle(QTextListFormat::ListDisc);
930 listFmt.setIndent(1);
931 cursor.insertList(format: listFmt);
932 cursor.insertText(text: "A");
933
934 ed->setTextCursor(cursor);
935
936 // delete 'A'
937 QTest::keyClick(widget: ed, key: Qt::Key_Backspace);
938 QVERIFY(ed->textCursor().currentList());
939 // delete list
940 QTest::keyClick(widget: ed, key: Qt::Key_Backspace);
941 QVERIFY(!ed->textCursor().currentList());
942 QCOMPARE(ed->textCursor().blockFormat().indent(), 1);
943 // outdent paragraph
944 QTest::keyClick(widget: ed, key: Qt::Key_Backspace);
945 QCOMPARE(ed->textCursor().blockFormat().indent(), 0);
946}
947
948void tst_QTextEdit::shiftBackspace()
949{
950 QTextCursor cursor = ed->textCursor();
951
952 QTextListFormat listFmt;
953 listFmt.setStyle(QTextListFormat::ListDisc);
954 listFmt.setIndent(1);
955 cursor.insertList(format: listFmt);
956 cursor.insertText(text: "A");
957
958 ed->setTextCursor(cursor);
959
960 // delete 'A'
961 QTest::keyClick(widget: ed, key: Qt::Key_Backspace, modifier: Qt::ShiftModifier);
962 QVERIFY(ed->textCursor().currentList());
963 // delete list
964 QTest::keyClick(widget: ed, key: Qt::Key_Backspace, modifier: Qt::ShiftModifier);
965 QVERIFY(!ed->textCursor().currentList());
966 QCOMPARE(ed->textCursor().blockFormat().indent(), 1);
967 // outdent paragraph
968 QTest::keyClick(widget: ed, key: Qt::Key_Backspace, modifier: Qt::ShiftModifier);
969 QCOMPARE(ed->textCursor().blockFormat().indent(), 0);
970}
971
972void tst_QTextEdit::undoRedo()
973{
974 ed->clear();
975 QTest::keyClicks(widget: ed, sequence: "abc d");
976 QCOMPARE(ed->toPlainText(), QString("abc d"));
977 ed->undo();
978 QCOMPARE(ed->toPlainText(), QString());
979 ed->redo();
980 QCOMPARE(ed->toPlainText(), QString("abc d"));
981#ifdef Q_OS_WIN
982 // shortcut for undo
983 QTest::keyClick(ed, Qt::Key_Backspace, Qt::AltModifier);
984 QCOMPARE(ed->toPlainText(), QString());
985 // shortcut for redo
986 QTest::keyClick(ed, Qt::Key_Backspace, Qt::ShiftModifier|Qt::AltModifier);
987 QCOMPARE(ed->toPlainText(), QString("abc d"));
988#endif
989}
990
991// Task #70465
992void tst_QTextEdit::preserveCharFormatInAppend()
993{
994 ed->append(text: "First para");
995 ed->append(text: "<b>Second para</b>");
996 ed->append(text: "third para");
997
998 QTextCursor cursor(ed->textCursor());
999
1000 cursor.movePosition(op: QTextCursor::Start);
1001 cursor.movePosition(op: QTextCursor::NextCharacter);
1002 QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Normal);
1003 QCOMPARE(cursor.block().text(), QString("First para"));
1004
1005 cursor.movePosition(op: QTextCursor::NextBlock);
1006 cursor.movePosition(op: QTextCursor::NextCharacter);
1007 QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Bold);
1008 QCOMPARE(cursor.block().text(), QString("Second para"));
1009
1010 cursor.movePosition(op: QTextCursor::NextBlock);
1011 cursor.movePosition(op: QTextCursor::NextCharacter);
1012 QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Normal);
1013 QCOMPARE(cursor.block().text(), QString("third para"));
1014}
1015
1016#ifndef QT_NO_CLIPBOARD
1017void tst_QTextEdit::copyAndSelectAllInReadonly()
1018{
1019 if (!PlatformClipboard::isAvailable())
1020 QSKIP("Clipboard not working with cron-started unit tests");
1021
1022 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1023 QSKIP("Wayland: This fails. Figure out why.");
1024
1025 ed->setReadOnly(true);
1026 ed->setPlainText("Hello World");
1027
1028 QTextCursor cursor = ed->textCursor();
1029 cursor.clearSelection();
1030 ed->setTextCursor(cursor);
1031 QVERIFY(!ed->textCursor().hasSelection());
1032
1033 QCOMPARE(ed->toPlainText(), QString("Hello World"));
1034
1035 // shouldn't do anything
1036 QTest::keyClick(widget: ed, key: Qt::Key_A);
1037
1038 QCOMPARE(ed->toPlainText(), QString("Hello World"));
1039
1040 QTest::keyClick(widget: ed, key: Qt::Key_A, modifier: Qt::ControlModifier);
1041
1042 QVERIFY(ed->textCursor().hasSelection());
1043
1044 QApplication::clipboard()->setText(QString());
1045 QVERIFY(QApplication::clipboard()->text().isEmpty());
1046
1047 QTest::keyClick(widget: ed, key: Qt::Key_C, modifier: Qt::ControlModifier);
1048 QCOMPARE(QApplication::clipboard()->text(), QString("Hello World"));
1049}
1050#endif
1051
1052void tst_QTextEdit::noPropertiesOnDefaultTextEditCharFormat()
1053{
1054 // there should be no properties set on the default/initial char format
1055 // on a text edit. Font properties instead should be taken from the
1056 // widget's font (in sync with defaultFont property in document) and the
1057 // foreground color should be taken from the palette.
1058 QCOMPARE(ed->currentCharFormat().properties().count(), 0);
1059}
1060
1061void tst_QTextEdit::setPlainTextShouldUseCurrentCharFormat()
1062{
1063 ed->setFontUnderline(true);
1064 ed->setPlainText("Hello World");
1065 QTextCursor cursor(ed->document());
1066 cursor.movePosition(op: QTextCursor::NextCharacter);
1067 QCOMPARE(cursor.charFormat().fontUnderline(), true);
1068
1069 ed->setHtml("<p style=\"color:blue\">Foo</p>");
1070 ed->setPlainText("Hello World");
1071 cursor = QTextCursor(ed->document());
1072 cursor.movePosition(op: QTextCursor::NextCharacter);
1073 QCOMPARE(cursor.charFormat().fontUnderline(), true);
1074}
1075
1076void tst_QTextEdit::setPlainTextShouldEmitTextChangedOnce()
1077{
1078 QSignalSpy spy(ed, SIGNAL(textChanged()));
1079 ed->setPlainText("Yankee Doodle");
1080 QCOMPARE(spy.count(), 1);
1081 ed->setPlainText("");
1082 QCOMPARE(spy.count(), 2);
1083}
1084
1085void tst_QTextEdit::overwriteMode()
1086{
1087 QVERIFY(!ed->overwriteMode());
1088 QTest::keyClicks(widget: ed, sequence: "Some first text");
1089
1090 QCOMPARE(ed->toPlainText(), QString("Some first text"));
1091
1092 ed->setOverwriteMode(true);
1093
1094 QTextCursor cursor = ed->textCursor();
1095 cursor.setPosition(pos: 5);
1096 ed->setTextCursor(cursor);
1097
1098 QTest::keyClicks(widget: ed, sequence: "shiny");
1099 QCOMPARE(ed->toPlainText(), QString("Some shiny text"));
1100
1101 cursor.movePosition(op: QTextCursor::End);
1102 ed->setTextCursor(cursor);
1103
1104 QTest::keyClick(widget: ed, key: Qt::Key_Enter);
1105
1106 ed->setOverwriteMode(false);
1107 QTest::keyClicks(widget: ed, sequence: "Second paragraph");
1108
1109 QCOMPARE(blockCount(), 2);
1110
1111 cursor.movePosition(op: QTextCursor::Start);
1112 cursor.movePosition(op: QTextCursor::EndOfBlock);
1113
1114 QCOMPARE(cursor.position(), 15);
1115 ed->setTextCursor(cursor);
1116
1117 ed->setOverwriteMode(true);
1118
1119 QTest::keyClicks(widget: ed, sequence: " blah");
1120
1121 QCOMPARE(blockCount(), 2);
1122
1123 QTextBlock block = ed->document()->begin();
1124 QCOMPARE(block.text(), QString("Some shiny text blah"));
1125 block = block.next();
1126 QCOMPARE(block.text(), QString("Second paragraph"));
1127}
1128
1129void tst_QTextEdit::shiftDownInLineLastShouldSelectToEnd_data()
1130{
1131 // shift cursor-down in the last line should select to the end of the document
1132
1133 QTest::addColumn<QString>(name: "input");
1134 QTest::addColumn<int>(name: "totalLineCount");
1135
1136 QTest::newRow(dataTag: "1") << QString("Foo\nBar") << 2;
1137 QTest::newRow(dataTag: "2") << QString("Foo\nBar") + QChar(QChar::LineSeparator) + QString("Baz") << 3;
1138}
1139
1140void tst_QTextEdit::shiftDownInLineLastShouldSelectToEnd()
1141{
1142 QFETCH(QString, input);
1143 QFETCH(int, totalLineCount);
1144
1145 ed->setPlainText(input);
1146 ed->show();
1147
1148 // ensure we're layouted
1149 ed->document()->documentLayout()->documentSize();
1150
1151 QCOMPARE(blockCount(), 2);
1152
1153 int lineCount = 0;
1154 for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next())
1155 lineCount += block.layout()->lineCount();
1156 QCOMPARE(lineCount, totalLineCount);
1157
1158 QTextCursor cursor = ed->textCursor();
1159 cursor.movePosition(op: QTextCursor::Start);
1160 ed->setTextCursor(cursor);
1161
1162 for (int i = 0; i < lineCount; ++i) {
1163 QTest::keyClick(widget: ed, key: Qt::Key_Down, modifier: Qt::ShiftModifier);
1164 }
1165
1166 input.replace(before: QLatin1Char('\n'), after: QChar(QChar::ParagraphSeparator));
1167 QCOMPARE(ed->textCursor().selectedText(), input);
1168 QVERIFY(ed->textCursor().atEnd());
1169
1170 // also test that without shift modifier the cursor does not move to the end
1171 // for Key_Down in the last line
1172 cursor.movePosition(op: QTextCursor::Start);
1173 ed->setTextCursor(cursor);
1174 for (int i = 0; i < lineCount; ++i) {
1175 QTest::keyClick(widget: ed, key: Qt::Key_Down);
1176 }
1177 QVERIFY(!ed->textCursor().atEnd());
1178}
1179
1180void tst_QTextEdit::undoRedoShouldRepositionTextEditCursor()
1181{
1182 ed->setPlainText("five\nlines\nin\nthis\ntextedit");
1183 QTextCursor cursor = ed->textCursor();
1184 cursor.movePosition(op: QTextCursor::Start);
1185
1186 ed->setUndoRedoEnabled(false);
1187 ed->setUndoRedoEnabled(true);
1188
1189 QVERIFY(!ed->document()->isUndoAvailable());
1190 QVERIFY(!ed->document()->isRedoAvailable());
1191
1192 cursor.insertText(text: "Blah");
1193
1194 QVERIFY(ed->document()->isUndoAvailable());
1195 QVERIFY(!ed->document()->isRedoAvailable());
1196
1197 cursor.movePosition(op: QTextCursor::End);
1198 ed->setTextCursor(cursor);
1199
1200 QVERIFY(QMetaObject::invokeMethod(ed, "undo"));
1201
1202 QVERIFY(!ed->document()->isUndoAvailable());
1203 QVERIFY(ed->document()->isRedoAvailable());
1204
1205 QCOMPARE(ed->textCursor().position(), 0);
1206
1207 cursor.movePosition(op: QTextCursor::End);
1208 ed->setTextCursor(cursor);
1209
1210 QVERIFY(QMetaObject::invokeMethod(ed, "redo"));
1211
1212 QVERIFY(ed->document()->isUndoAvailable());
1213 QVERIFY(!ed->document()->isRedoAvailable());
1214
1215 QCOMPARE(ed->textCursor().position(), 4);
1216}
1217
1218void tst_QTextEdit::lineWrapModes()
1219{
1220 ed->setLineWrapMode(QTextEdit::NoWrap);
1221 // NoWrap at the same time as having all lines that are all left aligned means we optimize to only layout once. The effect is that the width is always 0
1222 QCOMPARE(ed->document()->pageSize().width(), qreal(0));
1223
1224 QTextCursor cursor = QTextCursor(ed->document());
1225 cursor.insertText(text: QString("A simple line"));
1226 cursor.insertBlock();
1227 QTextBlockFormat fmt;
1228 fmt.setAlignment(Qt::AlignRight);
1229 cursor.mergeBlockFormat(modifier: fmt);
1230 cursor.insertText(text: QString("Another line"));
1231 ed->show(); // relayout;
1232 QVERIFY(ed->document()->pageSize().width() > qreal(0));
1233
1234 ed->setLineWrapColumnOrWidth(10);
1235 ed->setLineWrapMode(QTextEdit::FixedColumnWidth);
1236 QVERIFY(!qIsNull(ed->document()->pageSize().width()));
1237
1238 ed->setLineWrapColumnOrWidth(1000);
1239 ed->setLineWrapMode(QTextEdit::FixedPixelWidth);
1240 QCOMPARE(ed->document()->pageSize().width(), qreal(1000));
1241}
1242
1243#ifndef QT_NO_CURSOR
1244void tst_QTextEdit::mouseCursorShape()
1245{
1246 // always show an IBeamCursor, see change 170146
1247 QVERIFY(!ed->isReadOnly());
1248 QCOMPARE(ed->viewport()->cursor().shape(), Qt::IBeamCursor);
1249
1250 ed->setReadOnly(true);
1251 QCOMPARE(ed->viewport()->cursor().shape(), Qt::IBeamCursor);
1252
1253 ed->setPlainText("Foo");
1254 QCOMPARE(ed->viewport()->cursor().shape(), Qt::IBeamCursor);
1255}
1256#endif
1257
1258void tst_QTextEdit::implicitClear()
1259{
1260 // test that QTextEdit::setHtml, etc. avoid calling clear() but instead call
1261 // QTextDocument::setHtml/etc. instead, which also clear the contents and
1262 // cached resource but preserve manually added resources. setHtml on a textedit
1263 // should behave the same as on a document with respect to that.
1264 // see also clearResources() autotest in qtextdocument
1265
1266 // regular resource for QTextDocument
1267 QUrl testUrl(":/foobar");
1268 QVariant testResource("hello world");
1269
1270 ed->document()->addResource(type: QTextDocument::ImageResource, name: testUrl, resource: testResource);
1271 QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);
1272
1273 ed->setPlainText("Blah");
1274 QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);
1275
1276 ed->setPlainText("<b>Blah</b>");
1277 QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);
1278
1279 ed->clear();
1280 QVERIFY(!ed->document()->resource(QTextDocument::ImageResource, testUrl).isValid());
1281 QVERIFY(ed->toPlainText().isEmpty());
1282}
1283
1284#ifndef QT_NO_CLIPBOARD
1285void tst_QTextEdit::copyAvailable_data()
1286{
1287 QTest::addColumn<pairListType>(name: "keystrokes");
1288 QTest::addColumn<QList<bool> >(name: "copyAvailable");
1289 QTest::addColumn<QString>(name: "function");
1290
1291 pairListType keystrokes;
1292 QList<bool> copyAvailable;
1293
1294 keystrokes << qMakePair(x: Qt::Key_B, y: Qt::NoModifier) << qMakePair(x: Qt::Key_B, y: Qt::NoModifier)
1295 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier);
1296 copyAvailable << true ;
1297 QTest::newRow(dataTag: QString("Case1 B,B, <- + shift | signals: true").toLatin1())
1298 << keystrokes << copyAvailable << QString();
1299
1300 keystrokes.clear();
1301 copyAvailable.clear();
1302
1303 keystrokes << qMakePair(x: Qt::Key_T, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
1304 << qMakePair(x: Qt::Key_A, y: Qt::NoModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier);
1305 copyAvailable << true << false;
1306 QTest::newRow(dataTag: QString("Case2 T,A,A, <- + shift, cut() | signals: true, false").toLatin1())
1307 << keystrokes << copyAvailable << QString("cut");
1308
1309 keystrokes.clear();
1310 copyAvailable.clear();
1311
1312 keystrokes << qMakePair(x: Qt::Key_T, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
1313 << qMakePair(x: Qt::Key_A, y: Qt::NoModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
1314 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier);
1315 copyAvailable << true;
1316 QTest::newRow(dataTag: QString("Case3 T,A,A, <- + shift, <- + shift, <- + shift, copy() | signals: true").toLatin1())
1317 << keystrokes << copyAvailable << QString("copy");
1318
1319 keystrokes.clear();
1320 copyAvailable.clear();
1321
1322 keystrokes << qMakePair(x: Qt::Key_T, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
1323 << qMakePair(x: Qt::Key_A, y: Qt::NoModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
1324 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
1325 << qMakePair(x: Qt::Key_X, y: Qt::ControlModifier);
1326 copyAvailable << true << false;
1327 QTest::newRow(dataTag: QString("Case4 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, paste() | signals: true, false").toLatin1())
1328 << keystrokes << copyAvailable << QString("paste");
1329
1330 keystrokes.clear();
1331 copyAvailable.clear();
1332
1333 keystrokes << qMakePair(x: Qt::Key_B, y: Qt::NoModifier) << qMakePair(x: Qt::Key_B, y: Qt::NoModifier)
1334 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::NoModifier);
1335 copyAvailable << true << false;
1336 QTest::newRow(dataTag: QString("Case5 B,B, <- + shift, <- | signals: true, false").toLatin1())
1337 << keystrokes << copyAvailable << QString();
1338
1339 keystrokes.clear();
1340 copyAvailable.clear();
1341
1342 keystrokes << qMakePair(x: Qt::Key_B, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
1343 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::NoModifier)
1344 << qMakePair(x: Qt::Key_Right, y: Qt::ShiftModifier);
1345 copyAvailable << true << false << true << false;
1346 QTest::newRow(dataTag: QString("Case6 B,A, <- + shift, ->, <- + shift | signals: true, false, true, false").toLatin1())
1347 << keystrokes << copyAvailable << QString("cut");
1348
1349 keystrokes.clear();
1350 copyAvailable.clear();
1351
1352 keystrokes << qMakePair(x: Qt::Key_T, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
1353 << qMakePair(x: Qt::Key_A, y: Qt::NoModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
1354 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
1355 << qMakePair(x: Qt::Key_X, y: Qt::ControlModifier);
1356 copyAvailable << true << false << true;
1357 QTest::newRow(dataTag: QString("Case7 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, undo() | signals: true, false, true").toLatin1())
1358 << keystrokes << copyAvailable << QString("undo");
1359}
1360
1361//Tests the copyAvailable slot for several cases
1362void tst_QTextEdit::copyAvailable()
1363{
1364 QFETCH(pairListType,keystrokes);
1365 QFETCH(QList<bool>, copyAvailable);
1366 QFETCH(QString, function);
1367
1368#ifdef Q_OS_MAC
1369 QSKIP("QTBUG-22283: copyAvailable has never passed on Mac");
1370#endif
1371 ed->clear();
1372 QApplication::clipboard()->clear();
1373 QVERIFY(!ed->canPaste());
1374 QSignalSpy spyCopyAvailabe(ed, SIGNAL(copyAvailable(bool)));
1375
1376 //Execute Keystrokes
1377 foreach(keyPairType keyPair, keystrokes) {
1378 QTest::keyClick(widget: ed, key: keyPair.first, modifier: keyPair.second );
1379 }
1380
1381 //Execute ed->"function"
1382 if (function == "cut")
1383 ed->cut();
1384 else if (function == "copy")
1385 ed->copy();
1386 else if (function == "paste")
1387 ed->paste();
1388 else if (function == "undo")
1389 ed->paste();
1390 else if (function == "redo")
1391 ed->paste();
1392
1393 //Compare spied signals
1394 QEXPECT_FAIL("Case7 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, undo() | signals: true, false, true",
1395 "Wrong undo selection behaviour. Should be fixed in some future release. (See task: 132482)", Abort);
1396 QCOMPARE(spyCopyAvailabe.count(), copyAvailable.count());
1397 for (int i=0;i<spyCopyAvailabe.count(); i++) {
1398 QVariant variantSpyCopyAvailable = spyCopyAvailabe.at(i).at(i: 0);
1399 QVERIFY2(variantSpyCopyAvailable.toBool() == copyAvailable.at(i), QString("Spied singnal: %1").arg(i).toLatin1());
1400 }
1401}
1402#endif
1403
1404void tst_QTextEdit::undoRedoAfterSetContent()
1405{
1406 QVERIFY(!ed->document()->isUndoAvailable());
1407 QVERIFY(!ed->document()->isRedoAvailable());
1408 ed->setPlainText("Foobar");
1409 QVERIFY(!ed->document()->isUndoAvailable());
1410 QVERIFY(!ed->document()->isRedoAvailable());
1411 ed->setHtml("<p>bleh</p>");
1412 QVERIFY(!ed->document()->isUndoAvailable());
1413 QVERIFY(!ed->document()->isRedoAvailable());
1414}
1415
1416void tst_QTextEdit::numPadKeyNavigation()
1417{
1418 ed->setText("Hello World");
1419 QCOMPARE(ed->textCursor().position(), 0);
1420 QTest::keyClick(widget: ed, key: Qt::Key_Right, modifier: Qt::KeypadModifier);
1421 QCOMPARE(ed->textCursor().position(), 1);
1422}
1423
1424void tst_QTextEdit::moveCursor()
1425{
1426 ed->setText("Test");
1427
1428 QSignalSpy cursorMovedSpy(ed, SIGNAL(cursorPositionChanged()));
1429
1430 QCOMPARE(ed->textCursor().position(), 0);
1431 ed->moveCursor(operation: QTextCursor::NextCharacter);
1432 QCOMPARE(ed->textCursor().position(), 1);
1433 QCOMPARE(cursorMovedSpy.count(), 1);
1434 ed->moveCursor(operation: QTextCursor::NextCharacter, mode: QTextCursor::KeepAnchor);
1435 QCOMPARE(ed->textCursor().position(), 2);
1436 QCOMPARE(cursorMovedSpy.count(), 2);
1437 QCOMPARE(ed->textCursor().selectedText(), QString("e"));
1438}
1439
1440class MyTextEdit : public QTextEdit
1441{
1442public:
1443 inline MyTextEdit()
1444 : createMimeDataCallCount(0),
1445 canInsertCallCount(0),
1446 insertCallCount(0)
1447 {}
1448
1449 mutable int createMimeDataCallCount;
1450 mutable int canInsertCallCount;
1451 mutable int insertCallCount;
1452
1453 virtual QMimeData *createMimeDataFromSelection() const {
1454 createMimeDataCallCount++;
1455 return QTextEdit::createMimeDataFromSelection();
1456 }
1457 virtual bool canInsertFromMimeData(const QMimeData *source) const {
1458 canInsertCallCount++;
1459 return QTextEdit::canInsertFromMimeData(source);
1460 }
1461 virtual void insertFromMimeData(const QMimeData *source) {
1462 insertCallCount++;
1463 QTextEdit::insertFromMimeData(source);
1464 }
1465
1466};
1467
1468#ifndef QT_NO_CLIPBOARD
1469void tst_QTextEdit::mimeDataReimplementations()
1470{
1471 MyTextEdit ed;
1472 ed.setPlainText("Hello World");
1473
1474 QCOMPARE(ed.createMimeDataCallCount, 0);
1475 QCOMPARE(ed.canInsertCallCount, 0);
1476 QCOMPARE(ed.insertCallCount, 0);
1477
1478 ed.selectAll();
1479
1480 QCOMPARE(ed.createMimeDataCallCount, 0);
1481 QCOMPARE(ed.canInsertCallCount, 0);
1482 QCOMPARE(ed.insertCallCount, 0);
1483
1484 ed.copy();
1485
1486 QCOMPARE(ed.createMimeDataCallCount, 1);
1487 QCOMPARE(ed.canInsertCallCount, 0);
1488 QCOMPARE(ed.insertCallCount, 0);
1489
1490#ifdef QT_BUILD_INTERNAL
1491 QWidgetTextControl *control = ed.findChild<QWidgetTextControl *>();
1492 QVERIFY(control);
1493
1494 control->canInsertFromMimeData(source: QApplication::clipboard()->mimeData());
1495
1496 QCOMPARE(ed.createMimeDataCallCount, 1);
1497 QCOMPARE(ed.canInsertCallCount, 1);
1498 QCOMPARE(ed.insertCallCount, 0);
1499
1500 ed.paste();
1501
1502 QCOMPARE(ed.createMimeDataCallCount, 1);
1503 QCOMPARE(ed.canInsertCallCount, 1);
1504 QCOMPARE(ed.insertCallCount, 1);
1505#endif
1506}
1507#endif
1508
1509void tst_QTextEdit::ctrlEnterShouldInsertLineSeparator_NOT()
1510{
1511 QTest::keyClick(widget: ed, key: Qt::Key_A);
1512 QTest::keyClick(widget: ed, key: Qt::Key_Enter, modifier: Qt::ControlModifier);
1513 QTest::keyClick(widget: ed, key: Qt::Key_B);
1514 QString expected;
1515 expected += 'a';
1516// expected += QChar::LineSeparator; // do NOT insert
1517 expected += 'b';
1518 QCOMPARE(ed->textCursor().block().text(), expected);
1519}
1520
1521void tst_QTextEdit::shiftEnterShouldInsertLineSeparator()
1522{
1523 QTest::keyClick(widget: ed, key: Qt::Key_A);
1524 QTest::keyClick(widget: ed, key: Qt::Key_Enter, modifier: Qt::ShiftModifier);
1525 QTest::keyClick(widget: ed, key: Qt::Key_B);
1526 QString expected;
1527 expected += 'a';
1528 expected += QChar::LineSeparator;
1529 expected += 'b';
1530 QCOMPARE(ed->textCursor().block().text(), expected);
1531}
1532
1533void tst_QTextEdit::selectWordsFromStringsContainingSeparators_data()
1534{
1535 QTest::addColumn<QString>(name: "testString");
1536 QTest::addColumn<QString>(name: "selectedWord");
1537
1538 const ushort wordSeparators[] =
1539 {'.', ',', '?', '!', ':', ';', '-', '<', '>', '[', ']', '(', ')', '{', '}',
1540 '=', '\t', ushort(QChar::Nbsp)};
1541
1542 for (size_t i = 0, count = sizeof(wordSeparators) / sizeof(wordSeparators[0]); i < count; ++i) {
1543 const ushort u = wordSeparators[i];
1544 QByteArray rowName = QByteArrayLiteral("separator: ");
1545 if (u >= 32 && u < 128)
1546 rowName += char(u);
1547 else
1548 rowName += QByteArrayLiteral("0x") + QByteArray::number(u, base: 16);
1549 QTest::newRow(dataTag: rowName.constData()) << QString("foo") + QChar(u) + QString("bar") << QString("foo");
1550 }
1551}
1552
1553void tst_QTextEdit::selectWordsFromStringsContainingSeparators()
1554{
1555 QFETCH(QString, testString);
1556 QFETCH(QString, selectedWord);
1557 ed->setText(testString);
1558 QTextCursor cursor = ed->textCursor();
1559 cursor.movePosition(op: QTextCursor::StartOfLine);
1560 cursor.select(selection: QTextCursor::WordUnderCursor);
1561 QVERIFY(cursor.hasSelection());
1562 QCOMPARE(cursor.selection().toPlainText(), selectedWord);
1563 cursor.clearSelection();
1564}
1565
1566#ifndef QT_NO_CLIPBOARD
1567void tst_QTextEdit::canPaste()
1568{
1569 if (!PlatformClipboard::isAvailable())
1570 QSKIP("Clipboard not working with cron-started unit tests");
1571
1572 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1573 QSKIP("Wayland: This fails. Figure out why.");
1574
1575 QApplication::clipboard()->setText(QString());
1576 QVERIFY(!ed->canPaste());
1577 QApplication::clipboard()->setText("Test");
1578 QVERIFY(ed->canPaste());
1579 ed->setTextInteractionFlags(Qt::NoTextInteraction);
1580 QVERIFY(!ed->canPaste());
1581}
1582#endif
1583
1584void tst_QTextEdit::ensureCursorVisibleOnInitialShow()
1585{
1586 QString manyPagesOfPlainText;
1587 for (int i = 0; i < 800; ++i)
1588 manyPagesOfPlainText += QLatin1String("Blah blah blah blah blah blah\n");
1589
1590 ed->setPlainText(manyPagesOfPlainText);
1591 QCOMPARE(ed->textCursor().position(), 0);
1592
1593 ed->moveCursor(operation: QTextCursor::End);
1594 ed->show();
1595 QVERIFY(ed->verticalScrollBar()->value() > 10);
1596
1597 ed->moveCursor(operation: QTextCursor::Start);
1598 QVERIFY(ed->verticalScrollBar()->value() < 10);
1599 ed->hide();
1600 ed->verticalScrollBar()->setValue(ed->verticalScrollBar()->maximum());
1601 ed->show();
1602 QCOMPARE(ed->verticalScrollBar()->value(), ed->verticalScrollBar()->maximum());
1603}
1604
1605class TestEdit : public QTextEdit
1606{
1607public:
1608 TestEdit() : resizeEventCalled(false) {}
1609
1610 bool resizeEventCalled;
1611
1612protected:
1613 virtual void resizeEvent(QResizeEvent *e)
1614 {
1615 QTextEdit::resizeEvent(e);
1616 setHtml("<img src=qtextbrowser-resizeevent.png width=" + QString::number(size().width()) + "><br>Size is " + QString::number(size().width()) + " x " + QString::number(size().height()));
1617 resizeEventCalled = true;
1618 }
1619};
1620
1621void tst_QTextEdit::setHtmlInsideResizeEvent()
1622{
1623 TestEdit edit;
1624 edit.show();
1625 edit.resize(w: 800, h: 600);
1626 QVERIFY(edit.resizeEventCalled);
1627}
1628
1629void tst_QTextEdit::colorfulAppend()
1630{
1631 ed->setTextColor(Qt::red);
1632 ed->append(text: "Red");
1633 ed->setTextColor(Qt::blue);
1634 ed->append(text: "Blue");
1635 ed->setTextColor(Qt::green);
1636 ed->append(text: "Green");
1637
1638 QCOMPARE(ed->document()->blockCount(), 3);
1639 QTextBlock block = ed->document()->begin();
1640 QCOMPARE(block.begin().fragment().text(), QString("Red"));
1641 QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::red);
1642 block = block.next();
1643 QCOMPARE(block.begin().fragment().text(), QString("Blue"));
1644 QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::blue);
1645 block = block.next();
1646 QCOMPARE(block.begin().fragment().text(), QString("Green"));
1647 QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::green);
1648}
1649
1650void tst_QTextEdit::ensureVisibleWithRtl()
1651{
1652 ed->setLayoutDirection(Qt::RightToLeft);
1653 ed->setLineWrapMode(QTextEdit::NoWrap);
1654 QString txt(500, QChar(QLatin1Char('a')));
1655 QCOMPARE(txt.length(), 500);
1656 ed->setPlainText(txt);
1657 ed->resize(w: 100, h: 100);
1658 ed->show();
1659
1660 qApp->processEvents();
1661
1662 QVERIFY(ed->horizontalScrollBar()->maximum() > 0);
1663
1664 ed->moveCursor(operation: QTextCursor::Start);
1665 QCOMPARE(ed->horizontalScrollBar()->value(), ed->horizontalScrollBar()->maximum());
1666 ed->moveCursor(operation: QTextCursor::End);
1667 QCOMPARE(ed->horizontalScrollBar()->value(), 0);
1668 ed->moveCursor(operation: QTextCursor::Start);
1669 QCOMPARE(ed->horizontalScrollBar()->value(), ed->horizontalScrollBar()->maximum());
1670 ed->moveCursor(operation: QTextCursor::End);
1671 QCOMPARE(ed->horizontalScrollBar()->value(), 0);
1672}
1673
1674void tst_QTextEdit::preserveCharFormatAfterSetPlainText()
1675{
1676 ed->setTextColor(Qt::blue);
1677 ed->setPlainText("This is blue");
1678 ed->append(text: "This should still be blue");
1679 QTextBlock block = ed->document()->begin();
1680 block = block.next();
1681 QCOMPARE(block.text(), QString("This should still be blue"));
1682 QCOMPARE(block.begin().fragment().charFormat().foreground().color(), QColor(Qt::blue));
1683}
1684
1685void tst_QTextEdit::extraSelections()
1686{
1687 ed->setPlainText("Hello World");
1688
1689 QTextCursor c = ed->textCursor();
1690 c.movePosition(op: QTextCursor::Start);
1691 c.movePosition(op: QTextCursor::End, QTextCursor::KeepAnchor);
1692 const int endPos = c.position();
1693
1694 QTextEdit::ExtraSelection sel;
1695 sel.cursor = c;
1696 ed->setExtraSelections(QList<QTextEdit::ExtraSelection>() << sel);
1697
1698 c.movePosition(op: QTextCursor::Start);
1699 c.movePosition(op: QTextCursor::NextWord);
1700 const int wordPos = c.position();
1701 c.movePosition(op: QTextCursor::End, QTextCursor::KeepAnchor);
1702 sel.cursor = c;
1703 ed->setExtraSelections(QList<QTextEdit::ExtraSelection>() << sel);
1704
1705 QList<QTextEdit::ExtraSelection> selections = ed->extraSelections();
1706 QCOMPARE(selections.count(), 1);
1707 QCOMPARE(selections.at(0).cursor.position(), endPos);
1708 QCOMPARE(selections.at(0).cursor.anchor(), wordPos);
1709}
1710
1711void tst_QTextEdit::adjustScrollbars()
1712{
1713// For some reason ff is defined to be << on Mac Panther / gcc 3.3
1714#undef ff
1715 QFont ff(ed->font());
1716 ff.setFamily("Tahoma");
1717 ff.setPointSize(11);
1718 ed->setFont(ff);
1719 ed->setMinimumSize(minw: 140, minh: 100);
1720 ed->setMaximumSize(maxw: 140, maxh: 100);
1721 ed->show();
1722 QLatin1String txt("\nabc def ghi jkl mno pqr stu vwx");
1723 ed->setText(txt + txt + txt + txt);
1724
1725#ifdef Q_OS_WINRT
1726 QEXPECT_FAIL("", "setMinimum/MaximumSize does not work on WinRT", Abort);
1727#endif
1728 QVERIFY(ed->verticalScrollBar()->maximum() > 0);
1729
1730 ed->moveCursor(operation: QTextCursor::End);
1731 int oldMaximum = ed->verticalScrollBar()->maximum();
1732 QTextCursor cursor = ed->textCursor();
1733 cursor.insertText(text: QLatin1String("\n"));
1734 cursor.deletePreviousChar();
1735 QCOMPARE(ed->verticalScrollBar()->maximum(), oldMaximum);
1736}
1737
1738class SignalReceiver : public QObject
1739{
1740 Q_OBJECT
1741public:
1742 SignalReceiver() : received(0) {}
1743
1744 int receivedSignals() const { return received; }
1745 QTextCharFormat charFormat() const { return format; }
1746
1747public slots:
1748 void charFormatChanged(const QTextCharFormat &tcf) { ++received; format = tcf; }
1749
1750private:
1751 QTextCharFormat format;
1752 int received;
1753};
1754
1755void tst_QTextEdit::currentCharFormatChanged()
1756{
1757 QFont ff(ed->font());
1758 ff.setFamily("Tahoma");
1759 ff.setPointSize(11);
1760
1761 SignalReceiver receiver;
1762 QObject::connect(sender: ed, SIGNAL(currentCharFormatChanged(QTextCharFormat)) , receiver: &receiver, SLOT(charFormatChanged(QTextCharFormat)));
1763
1764 ed->show();
1765 ed->setCurrentFont(ff);
1766
1767 QVERIFY(receiver.receivedSignals() > 0);
1768 QCOMPARE(receiver.charFormat().font(), ff);
1769}
1770
1771void tst_QTextEdit::textObscuredByScrollbars()
1772{
1773 ed->textCursor().insertText(
1774 text: "ab cab cab c abca kjsdf lka sjd lfk jsal df j kasdf abc ab abc "
1775 "a b c d e f g h i j k l m n o p q r s t u v w x y z "
1776 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1777 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1778 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1779 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1780 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1781 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1782 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1783 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1784 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1785 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1786 );
1787 ed->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1788 ed->show();
1789
1790 QSize documentSize = ed->document()->documentLayout()->documentSize().toSize();
1791 QSize viewportSize = ed->viewport()->size();
1792
1793 QVERIFY(documentSize.width() <= viewportSize.width());
1794}
1795
1796void tst_QTextEdit::setTextPreservesUndoRedoEnabled()
1797{
1798 QVERIFY(ed->isUndoRedoEnabled());
1799
1800 ed->setPlainText("Test");
1801
1802 QVERIFY(ed->isUndoRedoEnabled());
1803
1804 ed->setUndoRedoEnabled(false);
1805 QVERIFY(!ed->isUndoRedoEnabled());
1806 ed->setPlainText("Test2");
1807 QVERIFY(!ed->isUndoRedoEnabled());
1808
1809 ed->setHtml("<p>hello");
1810 QVERIFY(!ed->isUndoRedoEnabled());
1811}
1812
1813void tst_QTextEdit::wordWrapProperty()
1814{
1815 {
1816 QTextEdit edit;
1817 QTextDocument *doc = new QTextDocument(&edit);
1818 edit.setDocument(doc);
1819 edit.setWordWrapMode(QTextOption::NoWrap);
1820 QCOMPARE(doc->defaultTextOption().wrapMode(), QTextOption::NoWrap);
1821 }
1822 {
1823 QTextEdit edit;
1824 QTextDocument *doc = new QTextDocument(&edit);
1825 edit.setWordWrapMode(QTextOption::NoWrap);
1826 edit.setDocument(doc);
1827 QCOMPARE(doc->defaultTextOption().wrapMode(), QTextOption::NoWrap);
1828 }
1829}
1830
1831void tst_QTextEdit::lineWrapProperty()
1832{
1833 QCOMPARE(ed->wordWrapMode(), QTextOption::WrapAtWordBoundaryOrAnywhere);
1834 QCOMPARE(ed->lineWrapMode(), QTextEdit::WidgetWidth);
1835 ed->setLineWrapMode(QTextEdit::NoWrap);
1836 QCOMPARE(ed->lineWrapMode(), QTextEdit::NoWrap);
1837 QCOMPARE(ed->wordWrapMode(), QTextOption::WrapAtWordBoundaryOrAnywhere);
1838 QCOMPARE(ed->document()->defaultTextOption().wrapMode(), QTextOption::NoWrap);
1839}
1840
1841void tst_QTextEdit::selectionChanged()
1842{
1843 ed->setPlainText("Hello World");
1844
1845 ed->moveCursor(operation: QTextCursor::Start);
1846
1847 QSignalSpy selectionChangedSpy(ed, SIGNAL(selectionChanged()));
1848
1849 QTest::keyClick(widget: ed, key: Qt::Key_Right);
1850 QCOMPARE(ed->textCursor().position(), 1);
1851 QCOMPARE(selectionChangedSpy.count(), 0);
1852
1853 QTest::keyClick(widget: ed, key: Qt::Key_Right, modifier: Qt::ShiftModifier);
1854 QCOMPARE(ed->textCursor().position(), 2);
1855 QCOMPARE(selectionChangedSpy.count(), 1);
1856
1857 QTest::keyClick(widget: ed, key: Qt::Key_Right, modifier: Qt::ShiftModifier);
1858 QCOMPARE(ed->textCursor().position(), 3);
1859 QCOMPARE(selectionChangedSpy.count(), 2);
1860
1861 QTest::keyClick(widget: ed, key: Qt::Key_Right, modifier: Qt::ShiftModifier);
1862 QCOMPARE(ed->textCursor().position(), 4);
1863 QCOMPARE(selectionChangedSpy.count(), 3);
1864
1865 QTest::keyClick(widget: ed, key: Qt::Key_Right);
1866 QCOMPARE(ed->textCursor().position(), 4);
1867 QCOMPARE(selectionChangedSpy.count(), 4);
1868
1869 QTest::keyClick(widget: ed, key: Qt::Key_Right);
1870 QCOMPARE(ed->textCursor().position(), 5);
1871 QCOMPARE(selectionChangedSpy.count(), 4);
1872}
1873
1874#ifndef QT_NO_CLIPBOARD
1875void tst_QTextEdit::copyPasteBackgroundImage()
1876{
1877 if (!PlatformClipboard::isAvailable())
1878 QSKIP("Native clipboard not working in this setup");
1879
1880 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1881 QSKIP("Wayland: This fails. Figure out why.");
1882
1883 QImage foo(16, 16, QImage::Format_ARGB32_Premultiplied);
1884 foo.save(fileName: "foo.png");
1885 ed->setHtml("<body><table><tr><td background=\"foo.png\">Foo</td></tr></table></body>");
1886
1887 ed->moveCursor(operation: QTextCursor::Start);
1888 ed->moveCursor(operation: QTextCursor::End, mode: QTextCursor::KeepAnchor);
1889
1890 ed->copy();
1891
1892 ed->moveCursor(operation: QTextCursor::End, mode: QTextCursor::MoveAnchor);
1893 ed->paste();
1894
1895 ed->moveCursor(operation: QTextCursor::Start);
1896
1897 ed->moveCursor(operation: QTextCursor::NextBlock);
1898 QTextTable *a = ed->textCursor().currentTable();
1899
1900 ed->moveCursor(operation: QTextCursor::End);
1901 ed->moveCursor(operation: QTextCursor::PreviousBlock);
1902 QTextTable *b = ed->textCursor().currentTable();
1903
1904 QVERIFY(a);
1905 QVERIFY(b);
1906 QVERIFY(a != b);
1907
1908 QBrush ba = a->cellAt(row: 0, col: 0).format().background();
1909 QBrush bb = b->cellAt(row: 0, col: 0).format().background();
1910
1911#ifdef Q_OS_WINRT
1912 QEXPECT_FAIL("", "Fails on WinRT - QTBUG-68297", Abort);
1913#endif
1914 QCOMPARE(ba.style(), Qt::TexturePattern);
1915 QCOMPARE(ba.style(), bb.style());
1916
1917 // we don't want a copy/paste of the background image to cause
1918 // a new image/pixmap to be created, it should use the cached resource
1919 // already in the document
1920 QVERIFY(ba.textureImage().cacheKey() == bb.textureImage().cacheKey() ||
1921 ba.texture().cacheKey() == bb.texture().cacheKey());
1922 QFile::remove(fileName: QLatin1String("foo.png"));
1923}
1924
1925void tst_QTextEdit::copyPasteForegroundImage()
1926{
1927 ed->clear();
1928
1929 QPixmap pix(20, 20);
1930 pix.fill(fillColor: Qt::blue);
1931
1932 QTextCharFormat fmt;
1933 {
1934 QBrush textureBrush;
1935 {
1936 textureBrush.setTexture(pix);
1937 }
1938 textureBrush.setStyle(Qt::TexturePattern);
1939 fmt.setForeground(textureBrush);
1940 }
1941 ed->textCursor().insertText(text: "Foobar", format: fmt);
1942
1943 ed->moveCursor(operation: QTextCursor::Start);
1944 ed->moveCursor(operation: QTextCursor::End, mode: QTextCursor::KeepAnchor);
1945
1946 ed->copy();
1947 ed->clear();
1948 ed->paste();
1949
1950 QBrush brush = ed->textCursor().charFormat().foreground();
1951 QCOMPARE(brush.style(), Qt::TexturePattern);
1952 QCOMPARE(brush.texture().cacheKey(), pix.cacheKey());
1953}
1954#endif
1955
1956void tst_QTextEdit::setText()
1957{
1958 QTextEdit browser;
1959 browser.setText(QLatin1String("hello"));
1960 QCOMPARE(browser.toPlainText(), QLatin1String("hello"));
1961 browser.setText(QLatin1String("<b>bold bold</b>"));
1962 QCOMPARE(browser.toPlainText(), QLatin1String("bold bold"));
1963 browser.setText(QLatin1String("with space"));
1964 QCOMPARE(browser.toPlainText(), QLatin1String("with space"));
1965}
1966
1967#ifdef QT_BUILD_INTERNAL
1968QT_BEGIN_NAMESPACE
1969// qfontdatabase.cpp
1970Q_AUTOTEST_EXPORT void qt_setQtEnableTestFont(bool value);
1971QT_END_NAMESPACE
1972#endif
1973
1974#ifdef QT_BUILD_INTERNAL
1975void tst_QTextEdit::fullWidthSelection_data()
1976{
1977 QTest::addColumn<int>(name: "cursorFrom");
1978 QTest::addColumn<int>(name: "cursorTo");
1979 QTest::addColumn<QString>(name: "imageFileName");
1980
1981 QTest::newRow(dataTag: "centered fully selected")
1982 << 0 << 15 << (m_fullWidthSelectionImagesFolder + QStringLiteral("/centered-fully-selected.png"));
1983 QTest::newRow(dataTag: "centered partly selected")
1984 << 2 << 15 << (m_fullWidthSelectionImagesFolder + QStringLiteral("/centered-partly-selected.png"));
1985 QTest::newRow(dataTag: "last char on line")
1986 << 42 << 44 << (m_fullWidthSelectionImagesFolder + QStringLiteral("/last-char-on-line.png"));
1987 QTest::newRow(dataTag: "last char on parag")
1988 << 545 << 548 << (m_fullWidthSelectionImagesFolder + QStringLiteral("/last-char-on-parag.png"));
1989 QTest::newRow(dataTag: "multiple full width lines")
1990 << 20 << 60 << (m_fullWidthSelectionImagesFolder + QStringLiteral("/multiple-full-width-lines.png"));
1991 QTest::newRow(dataTag: "single full width line")
1992 << 20 << 30 << (m_fullWidthSelectionImagesFolder + QStringLiteral("/single-full-width-line.png"));
1993}
1994#endif
1995
1996#ifdef QT_BUILD_INTERNAL
1997
1998// With the fix for QTBUG-78318 scaling of documentMargin is added. The testing framework
1999// forces qt_defaultDpi() to always return 96 DPI. For systems where the actual DPI differs
2000// (typically 72 DPI) this would now cause scaling of the documentMargin when
2001// drawing QTextEdit into QImage. In order to avoid the need of multiple reference PNGs
2002// for comparison we disable the Qt::AA_Use96Dpi attribute for these tests.
2003
2004struct ForceSystemDpiHelper {
2005 ForceSystemDpiHelper() { QCoreApplication::setAttribute(attribute: Qt::AA_Use96Dpi, on: false); }
2006 ~ForceSystemDpiHelper() { QCoreApplication::setAttribute(attribute: Qt::AA_Use96Dpi, on: old); }
2007 bool old = QCoreApplication::testAttribute(attribute: Qt::AA_Use96Dpi);
2008};
2009
2010void tst_QTextEdit::fullWidthSelection()
2011{
2012 ForceSystemDpiHelper useSystemDpi;
2013
2014 QFETCH(int, cursorFrom);
2015 QFETCH(int, cursorTo);
2016 QFETCH(QString, imageFileName);
2017
2018 // enable full-width-selection for our test widget.
2019 class FullWidthStyle : public QCommonStyle {
2020 int styleHint(StyleHint stylehint, const QStyleOption *opt, const QWidget *widget, QStyleHintReturn *returnData) const {
2021 if (stylehint == QStyle::SH_RichText_FullWidthSelection)
2022 return 1;
2023 return QCommonStyle::styleHint(sh: stylehint, opt, w: widget, shret: returnData);
2024 };
2025 };
2026 FullWidthStyle myStyle;
2027
2028 QPalette myPalette = myStyle.standardPalette();
2029 myPalette.setColor(acg: QPalette::All, acr: QPalette::HighlightedText, acolor: QColor(0,0,0,0));
2030 myPalette.setColor(acg: QPalette::All, acr: QPalette::Highlight, acolor: QColor(239,221,85));
2031
2032 QTextEdit widget;
2033 widget.document()->setDocumentMargin(2);
2034 widget.setPalette(myPalette);
2035 widget.setStyle(&myStyle);
2036 QTextCursor cursor = widget.textCursor();
2037 QTextBlockFormat bf1;
2038 bf1.setAlignment(Qt::AlignCenter);
2039 cursor.setBlockFormat(bf1);
2040
2041 // use the test font so we always know where stuff will end up.
2042 qt_setQtEnableTestFont(value: true);
2043 QFont testFont;
2044 testFont.setFamily("__Qt__Box__Engine__");
2045 testFont.setPixelSize(11);
2046 testFont.setWeight(QFont::Normal);
2047 QTextCharFormat cf;
2048 cf.setFont(testFont);
2049 cf.setForeground(QColor(0,0,0,0)); // tricky bit, this :)
2050 cursor.setCharFormat(cf);
2051
2052 // populate with some demo text.
2053 cursor.insertText(text: "centered");
2054 QTextBlockFormat bf;
2055 cursor.insertBlock(format: bf, charFormat: cf);
2056 cursor.insertText(text: "Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo cons.\nfoo\n");
2057
2058 // Usecase 1 for full-width-selections; the 'show-cursor-position' one.
2059 QList<QTextEdit::ExtraSelection> selections;
2060 QTextCursor cursor2(widget.document());
2061 QTextEdit::ExtraSelection es;
2062 cursor2.setPosition(pos: 300);
2063 es.cursor = cursor2;
2064 es.format.setProperty( propertyId: QTextFormat::FullWidthSelection, value: true );
2065 es.format.setBackground(QColor(255, 0, 0));
2066 selections.append(t: es);
2067 widget.setExtraSelections(selections);
2068
2069 // Usecase 2; show it based on the style.
2070 // Select part of the centered text and part of the next; which means we should show the part right of the centered text.
2071 cursor.setPosition(pos: cursorFrom);
2072 cursor.setPosition(pos: cursorTo, mode: QTextCursor::KeepAnchor);
2073 widget.setTextCursor(cursor);
2074
2075 compareWidgetAndImage(widget, imageFileName);
2076}
2077#endif
2078
2079#ifdef QT_BUILD_INTERNAL
2080void tst_QTextEdit::fullWidthSelection2()
2081{
2082 ForceSystemDpiHelper useSystemDpi;
2083
2084 QPalette myPalette;
2085 myPalette.setColor(acg: QPalette::All, acr: QPalette::HighlightedText, acolor: QColor(0,0,0,0));
2086 myPalette.setColor(acg: QPalette::All, acr: QPalette::Highlight, acolor: QColor(239,221,85));
2087 myPalette.setColor(acg: QPalette::All, acr: QPalette::Base, acolor: QColor(255,255,255));
2088
2089 QTextEdit widget;
2090 widget.setPalette(myPalette);
2091 widget.setLineWrapMode(QTextEdit::NoWrap);
2092 QTextCursor cursor = widget.textCursor();
2093
2094 // use the test font so we always know where stuff will end up.
2095 qt_setQtEnableTestFont(value: true);
2096 QFont testFont;
2097 testFont.setFamily("__Qt__Box__Engine__");
2098 testFont.setPixelSize(11);
2099 testFont.setWeight(QFont::Normal);
2100 QTextCharFormat cf;
2101 cf.setFont(testFont);
2102 cf.setForeground(QColor(0,0,0,0)); // tricky bit, this :)
2103 cursor.setCharFormat(cf);
2104 cursor.insertText(text: "foo");
2105
2106 QList<QTextEdit::ExtraSelection> selections;
2107 QTextCursor cursor2(widget.document());
2108 QTextEdit::ExtraSelection es;
2109 cursor2.setPosition(pos: 1);
2110 es.cursor = cursor2;
2111 es.format.setProperty( propertyId: QTextFormat::FullWidthSelection, value: true );
2112 es.format.setBackground(QColor(255, 0, 0));
2113 selections.append(t: es);
2114 widget.setExtraSelections(selections);
2115
2116 compareWidgetAndImage(widget, imageFileName: m_fullWidthSelectionImagesFolder + "/nowrap_long.png");
2117}
2118#endif
2119
2120void tst_QTextEdit::compareWidgetAndImage(QTextEdit &widget, const QString &imageFileName)
2121{
2122 widget.setGeometry(ax: 0, ay: 0, aw: 300, ah: 390);
2123 widget.setFrameShape(QFrame::NoFrame);
2124
2125 QImage image(300, 390, QImage::Format_RGB32);
2126 QPainter painter(&image);
2127 widget.viewport()->render(painter: &painter);
2128 painter.end();
2129 QImageReader reader(imageFileName, "PNG");
2130
2131 QImage original = reader.read();
2132
2133 QVERIFY2(!original.isNull(),
2134 qPrintable(QString::fromLatin1("Unable to read image %1: %2").arg(imageFileName, reader.errorString())));
2135 QCOMPARE(original.size(), image.size());
2136 QCOMPARE(image.depth(), 32);
2137 QCOMPARE(original.depth(), image.depth());
2138
2139 const int bytesPerLine = image.bytesPerLine();
2140 const int width = image.width();
2141 const int height = image.height();
2142
2143 for (int y = 0; y < height; ++y) {
2144 const QRgb * const firstLine = reinterpret_cast<const QRgb *>(image.scanLine(y));
2145 const QRgb * const secondLine = reinterpret_cast<const QRgb *>(original.scanLine(y));
2146
2147 if (memcmp(s1: firstLine, s2: secondLine, n: bytesPerLine) != 0) {
2148 for (int x = 0; x < width; ++x) {
2149 const QRgb a = firstLine[x];
2150 const QRgb b = secondLine[x];
2151 const bool same = qAbs(t: qRed(rgb: a) - qRed(rgb: b)) <= 20
2152 && qAbs(t: qGreen(rgb: a) - qGreen(rgb: b)) <= 20
2153 && qAbs(t: qBlue(rgb: a) - qBlue(rgb: b)) <= 20;
2154 if (!same) {
2155 QString fileName = imageFileName;
2156 QImageWriter writer("failed_"+ fileName.replace(before: '/',after: '_'), "PNG");
2157 writer.write(image);
2158 }
2159 QVERIFY(same);
2160 }
2161 }
2162 }
2163}
2164
2165void tst_QTextEdit::cursorRect()
2166{
2167 ed->show();
2168 ed->setPlainText("Hello Test World");
2169 ed->setCursorWidth(1);
2170 QCOMPARE(ed->cursorRect().width(), 1);
2171 ed->setCursorWidth(2);
2172 QCOMPARE(ed->cursorRect().width(), 2);
2173 ed->setCursorWidth(4);
2174 QCOMPARE(ed->cursorRect().width(), 4);
2175 ed->setCursorWidth(10);
2176 QCOMPARE(ed->cursorRect().width(), 10);
2177}
2178
2179#ifdef QT_BUILD_INTERNAL
2180void tst_QTextEdit::setDocumentPreservesPalette()
2181{
2182 QWidgetTextControl *control = ed->findChild<QWidgetTextControl *>();
2183 QVERIFY(control);
2184
2185 QPalette defaultPal = ed->palette();
2186 QPalette whitePal = ed->palette();
2187 whitePal.setColor(acg: QPalette::Active, acr: QPalette::Text, acolor: "white");
2188
2189
2190 QVERIFY(whitePal != ed->palette());
2191 ed->setPalette(whitePal);
2192 QVERIFY(whitePal.color(QPalette::Active, QPalette::Text)
2193 == ed->palette().color(QPalette::Active, QPalette::Text));
2194 QVERIFY(whitePal.color(QPalette::Active, QPalette::Text)
2195 == control->palette().color(QPalette::Active, QPalette::Text));
2196
2197 QTextDocument *newDoc = new QTextDocument(ed);
2198 ed->setDocument(newDoc);
2199 QCOMPARE(control->document(), newDoc);
2200 QVERIFY(whitePal.color(QPalette::Active, QPalette::Text)
2201 == control->palette().color(QPalette::Active, QPalette::Text));
2202}
2203#endif
2204
2205class PublicTextEdit : public QTextEdit
2206{
2207public:
2208 void publicInsertFromMimeData(const QMimeData *source)
2209 { insertFromMimeData(source); }
2210};
2211
2212void tst_QTextEdit::pasteFromQt3RichText()
2213{
2214 QByteArray richtext("<!--StartFragment--><p> QTextEdit is an ");
2215
2216 QMimeData mimeData;
2217 mimeData.setData(mimetype: "application/x-qrichtext", data: richtext);
2218
2219 static_cast<PublicTextEdit *>(ed)->publicInsertFromMimeData(source: &mimeData);
2220
2221 QCOMPARE(ed->toPlainText(), QString::fromLatin1(" QTextEdit is an "));
2222 ed->clear();
2223
2224 richtext = "<!--StartFragment--> QTextEdit is an ";
2225 mimeData.setData(mimetype: "application/x-qrichtext", data: richtext);
2226
2227 static_cast<PublicTextEdit *>(ed)->publicInsertFromMimeData(source: &mimeData);
2228
2229 QCOMPARE(ed->toPlainText(), QString::fromLatin1(" QTextEdit is an "));
2230}
2231
2232void tst_QTextEdit::noWrapBackgrounds()
2233{
2234 QWidget topLevel;
2235 QVBoxLayout *layout = new QVBoxLayout(&topLevel);
2236
2237 QTextEdit edit;
2238 edit.setLineWrapMode(QTextEdit::NoWrap);
2239
2240 // hide the cursor in order to make the image comparison below reliable
2241 edit.setCursorWidth(0);
2242
2243 QTextFrame *root = edit.document()->rootFrame();
2244 QTextFrameFormat frameFormat = root->frameFormat();
2245 frameFormat.setLeftMargin(2);
2246 frameFormat.setRightMargin(2);
2247 root->setFrameFormat(frameFormat);
2248
2249 QTextBlockFormat format;
2250 format.setBackground(Qt::red);
2251 edit.textCursor().setBlockFormat(format);
2252 edit.insertPlainText(text: QLatin1String(" \n \n \n \n"));
2253 edit.setFixedSize(w: 100, h: 200);
2254
2255 layout->addWidget(&edit);
2256 topLevel.show();
2257
2258 const QImage img = edit.viewport()->grab().toImage();
2259 QCOMPARE(img, img.mirrored(true, false));
2260}
2261
2262void tst_QTextEdit::preserveCharFormatAfterUnchangingSetPosition()
2263{
2264 QColor color(Qt::yellow);
2265 QTextEdit edit;
2266 edit.setTextColor(color);
2267
2268 QTextCursor c = edit.textCursor();
2269 c.setPosition(pos: c.position());
2270 edit.setTextCursor(c);
2271
2272 QCOMPARE(edit.textColor(), color);
2273}
2274
2275// Regression test for QTBUG-4696
2276void tst_QTextEdit::twoSameInputMethodEvents()
2277{
2278 ed->setText("testLine");
2279 ed->show();
2280 QList<QInputMethodEvent::Attribute> attributes;
2281 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
2282 ed->textCursor().position(),
2283 0,
2284 QVariant()));
2285
2286 QInputMethodEvent event("PreEditText", attributes);
2287 QApplication::sendEvent(receiver: ed, event: &event);
2288 QTRY_COMPARE(ed->document()->firstBlock().layout()->lineCount(), 1);
2289 QApplication::sendEvent(receiver: ed, event: &event);
2290 QCOMPARE(ed->document()->firstBlock().layout()->lineCount(), 1);
2291}
2292
2293#ifndef QT_NO_CONTEXTMENU
2294void tst_QTextEdit::taskQTBUG_7902_contextMenuCrash()
2295{
2296 QTextEdit *w = new QTextEdit;
2297 w->show();
2298 QVERIFY(QTest::qWaitForWindowExposed(w));
2299
2300 QTimer ti;
2301 w->connect(sender: &ti, SIGNAL(timeout()), receiver: w, SLOT(deleteLater()));
2302 ti.start(msec: 200);
2303
2304 QContextMenuEvent *cme = new QContextMenuEvent(QContextMenuEvent::Mouse, w->rect().center());
2305 qApp->postEvent(receiver: w->viewport(), event: cme);
2306
2307 QTest::qWait(ms: 300);
2308 // No crash, it's allright.
2309}
2310#endif
2311
2312void tst_QTextEdit::bidiVisualMovement_data()
2313{
2314 QTest::addColumn<QString>(name: "logical");
2315 QTest::addColumn<int>(name: "basicDir");
2316 QTest::addColumn<QList<int> >(name: "positionList");
2317
2318 QTest::newRow(dataTag: "Latin text")
2319 << QString::fromUtf8(str: "abc")
2320 << (int) QChar::DirL
2321 << (QList<int>() << 0 << 1 << 2 << 3);
2322 QTest::newRow(dataTag: "Hebrew text, one item")
2323 << QString::fromUtf8(str: "\327\220\327\221\327\222")
2324 << (int) QChar::DirR
2325 << (QList<int>() << 0 << 1 << 2 << 3);
2326 QTest::newRow(dataTag: "Hebrew text after Latin text")
2327 << QString::fromUtf8(str: "abc\327\220\327\221\327\222")
2328 << (int) QChar::DirL
2329 << (QList<int>() << 0 << 1 << 2 << 6 << 5 << 4 << 3);
2330 QTest::newRow(dataTag: "Latin text after Hebrew text")
2331 << QString::fromUtf8(str: "\327\220\327\221\327\222abc")
2332 << (int) QChar::DirR
2333 << (QList<int>() << 0 << 1 << 2 << 6 << 5 << 4 << 3);
2334 QTest::newRow(dataTag: "LTR, 3 items")
2335 << QString::fromUtf8(str: "abc\327\220\327\221\327\222abc")
2336 << (int) QChar::DirL
2337 << (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 9);
2338 QTest::newRow(dataTag: "RTL, 3 items")
2339 << QString::fromUtf8(str: "\327\220\327\221\327\222abc\327\220\327\221\327\222")
2340 << (int) QChar::DirR
2341 << (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 9);
2342 QTest::newRow(dataTag: "LTR, 4 items")
2343 << QString::fromUtf8(str: "abc\327\220\327\221\327\222abc\327\220\327\221\327\222")
2344 << (int) QChar::DirL
2345 << (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 12 << 11 << 10 << 9);
2346 QTest::newRow(dataTag: "RTL, 4 items")
2347 << QString::fromUtf8(str: "\327\220\327\221\327\222abc\327\220\327\221\327\222abc")
2348 << (int) QChar::DirR
2349 << (QList<int>() << 0 << 1 << 2 << 5 << 4 << 3 << 6 << 7 << 8 << 12 << 11 << 10 << 9);
2350}
2351
2352void tst_QTextEdit::bidiVisualMovement()
2353{
2354 QFETCH(QString, logical);
2355 QFETCH(int, basicDir);
2356 QFETCH(QList<int>, positionList);
2357
2358 ed->setText(logical);
2359
2360 QTextOption option = ed->document()->defaultTextOption();
2361 option.setTextDirection(basicDir == QChar::DirL ? Qt::LeftToRight : Qt::RightToLeft);
2362 ed->document()->setDefaultTextOption(option);
2363
2364 ed->document()->setDefaultCursorMoveStyle(Qt::VisualMoveStyle);
2365 ed->moveCursor(operation: QTextCursor::Start);
2366 ed->show();
2367
2368 bool moved;
2369 int i = 0, oldPos, newPos = 0;
2370
2371 do {
2372 oldPos = newPos;
2373 QCOMPARE(oldPos, positionList[i]);
2374 if (basicDir == QChar::DirL) {
2375 ed->moveCursor(operation: QTextCursor::Right);
2376 } else
2377 {
2378 ed->moveCursor(operation: QTextCursor::Left);
2379 }
2380 newPos = ed->textCursor().position();
2381 moved = (oldPos != newPos);
2382 i++;
2383 } while (moved);
2384
2385 QCOMPARE(i, positionList.size());
2386
2387 do {
2388 i--;
2389 oldPos = newPos;
2390 QCOMPARE(oldPos, positionList[i]);
2391 if (basicDir == QChar::DirL) {
2392 ed->moveCursor(operation: QTextCursor::Left);
2393 } else
2394 {
2395 ed->moveCursor(operation: QTextCursor::Right);
2396 }
2397 newPos = ed->textCursor().position();
2398 moved = (oldPos != newPos);
2399 } while (moved && i >= 0);
2400}
2401
2402void tst_QTextEdit::bidiLogicalMovement_data()
2403{
2404 bidiVisualMovement_data();
2405}
2406
2407void tst_QTextEdit::bidiLogicalMovement()
2408{
2409 QFETCH(QString, logical);
2410 QFETCH(int, basicDir);
2411 QFETCH(QList<int>, positionList);
2412
2413 ed->setText(logical);
2414
2415 QTextOption option = ed->document()->defaultTextOption();
2416 option.setTextDirection(basicDir == QChar::DirL ? Qt::LeftToRight : Qt::RightToLeft);
2417 ed->document()->setDefaultTextOption(option);
2418
2419 ed->document()->setDefaultCursorMoveStyle(Qt::LogicalMoveStyle);
2420 ed->moveCursor(operation: QTextCursor::Start);
2421 ed->show();
2422
2423 bool moved;
2424 int i = 0, oldPos, newPos = 0;
2425
2426 do {
2427 oldPos = newPos;
2428 QCOMPARE(oldPos, i);
2429 if (basicDir == QChar::DirL) {
2430 ed->moveCursor(operation: QTextCursor::Right);
2431 } else
2432 {
2433 ed->moveCursor(operation: QTextCursor::Left);
2434 }
2435 newPos = ed->textCursor().position();
2436 moved = (oldPos != newPos);
2437 i++;
2438 } while (moved);
2439
2440 QCOMPARE(i, positionList.size());
2441
2442 do {
2443 i--;
2444 oldPos = newPos;
2445 QCOMPARE(oldPos, i);
2446 if (basicDir == QChar::DirL) {
2447 ed->moveCursor(operation: QTextCursor::Left);
2448 } else
2449 {
2450 ed->moveCursor(operation: QTextCursor::Right);
2451 }
2452 newPos = ed->textCursor().position();
2453 moved = (oldPos != newPos);
2454 } while (moved && i >= 0);
2455}
2456
2457void tst_QTextEdit::inputMethodEvent()
2458{
2459 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
2460 QSKIP("Wayland: This fails. Figure out why.");
2461
2462 ed->show();
2463
2464 // test that text change with an input method event triggers change signal
2465 QSignalSpy spy(ed, SIGNAL(textChanged()));
2466
2467 QInputMethodEvent event;
2468 event.setCommitString(commitString: "text");
2469 QApplication::sendEvent(receiver: ed, event: &event);
2470 QCOMPARE(spy.count(), 1);
2471 QCOMPARE(ed->toPlainText(), QString("text"));
2472
2473 // test that input method gets chance to commit preedit when removing focus
2474 ed->setText("");
2475 QApplication::setActiveWindow(ed);
2476 QTRY_VERIFY(QApplication::focusWindow());
2477 QCOMPARE(qApp->focusObject(), ed);
2478
2479 m_platformInputContext.setCommitString("text");
2480 m_platformInputContext.m_commitCallCount = 0;
2481 QList<QInputMethodEvent::Attribute> attributes;
2482 QInputMethodEvent preeditEvent("preedit text", attributes);
2483 QApplication::sendEvent(receiver: ed, event: &preeditEvent);
2484
2485 ed->clearFocus();
2486 QCOMPARE(m_platformInputContext.m_commitCallCount, 1);
2487 QCOMPARE(ed->toPlainText(), QString("text"));
2488}
2489
2490void tst_QTextEdit::inputMethodSelection()
2491{
2492 ed->setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit.");
2493
2494 QSignalSpy selectionSpy(ed, SIGNAL(selectionChanged()));
2495 QTextCursor cursor = ed->textCursor();
2496 cursor.setPosition(pos: 0);
2497 cursor.setPosition(pos: 5, mode: QTextCursor::KeepAnchor);
2498 ed->setTextCursor(cursor);
2499
2500 QCOMPARE(selectionSpy.count(), 1);
2501 QCOMPARE(ed->textCursor().selectionStart(), 0);
2502 QCOMPARE(ed->textCursor().selectionEnd(), 5);
2503
2504 QList<QInputMethodEvent::Attribute> attributes;
2505 attributes << QInputMethodEvent::Attribute(QInputMethodEvent::Selection, 12, 5, QVariant());
2506 QInputMethodEvent event("", attributes);
2507 QApplication::sendEvent(receiver: ed, event: &event);
2508
2509 QCOMPARE(selectionSpy.count(), 2);
2510 QCOMPARE(ed->textCursor().selectionStart(), 12);
2511 QCOMPARE(ed->textCursor().selectionEnd(), 17);
2512}
2513
2514void tst_QTextEdit::inputMethodQuery()
2515{
2516 QString text("first line of text\nsecond line of text");
2517 ed->setText(text);
2518 ed->selectAll();
2519
2520 QInputMethodQueryEvent event(Qt::ImQueryInput | Qt::ImEnabled);
2521 QGuiApplication::sendEvent(receiver: ed, event: &event);
2522 int anchor = event.value(query: Qt::ImAnchorPosition).toInt();
2523 int position = event.value(query: Qt::ImCursorPosition).toInt();
2524 QCOMPARE(qAbs(position - anchor), text.length());
2525 QCOMPARE(event.value(Qt::ImEnabled).toBool(), true);
2526
2527 ed->setEnabled(false);
2528 QGuiApplication::sendEvent(receiver: ed, event: &event);
2529 QCOMPARE(event.value(Qt::ImEnabled).toBool(), false);
2530}
2531
2532Q_DECLARE_METATYPE(Qt::InputMethodHints)
2533void tst_QTextEdit::inputMethodQueryImHints_data()
2534{
2535 QTest::addColumn<Qt::InputMethodHints>(name: "hints");
2536
2537 QTest::newRow(dataTag: "None") << static_cast<Qt::InputMethodHints>(Qt::ImhNone);
2538 QTest::newRow(dataTag: "Password") << static_cast<Qt::InputMethodHints>(Qt::ImhHiddenText);
2539 QTest::newRow(dataTag: "Normal") << static_cast<Qt::InputMethodHints>(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
2540}
2541
2542void tst_QTextEdit::inputMethodQueryImHints()
2543{
2544 QFETCH(Qt::InputMethodHints, hints);
2545 ed->setInputMethodHints(hints);
2546
2547 QVariant value = ed->inputMethodQuery(property: Qt::ImHints);
2548 QCOMPARE(static_cast<Qt::InputMethodHints>(value.toInt()), hints);
2549}
2550
2551// QTBUG-51923: Verify that the cursor rectangle returned by the input
2552// method query correctly reflects the viewport offset.
2553void tst_QTextEdit::inputMethodCursorRect()
2554{
2555 ed->setPlainText("Line1\nLine2Line3\nLine3");
2556 ed->moveCursor(operation: QTextCursor::End);
2557 const QRectF cursorRect = ed->cursorRect();
2558 const QVariant cursorRectV = ed->inputMethodQuery(property: Qt::ImCursorRectangle);
2559 QCOMPARE(cursorRectV.type(), QVariant::RectF);
2560 QCOMPARE(cursorRectV.toRect(), cursorRect.toRect());
2561}
2562
2563void tst_QTextEdit::highlightLongLine()
2564{
2565 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
2566 QSKIP("Wayland: This fails. Figure out why.");
2567
2568 QTextEdit edit;
2569 edit.setAcceptRichText(false);
2570 edit.setWordWrapMode(QTextOption::NoWrap);
2571
2572 QString singeLongLine;
2573 for (int i = 0; i < 10000; ++i)
2574 singeLongLine += "0123456789";
2575 edit.setPlainText(singeLongLine);
2576
2577 class NumHighlighter : public QSyntaxHighlighter {
2578 public:
2579 explicit NumHighlighter(QTextDocument*doc) : QSyntaxHighlighter(doc) {};
2580 virtual void highlightBlock(const QString& text) {
2581 // odd number in bold
2582 QTextCharFormat format;
2583 format.setFontWeight(QFont::Bold);
2584 for (int i = 0; i < text.size(); ++i) {
2585 if (text.at(i).unicode() % 2)
2586 setFormat(start: i, count: 1, format);
2587 }
2588 }
2589 };
2590 NumHighlighter nh(edit.document());
2591 edit.show();
2592 QVERIFY(QTest::qWaitForWindowActive(edit.windowHandle()));
2593 QCoreApplication::processEvents();
2594 //If there is a quadratic behaviour, this would take forever.
2595 QVERIFY(true);
2596}
2597
2598//check for bug 15003, are there multiple textChanged() signals on remove?
2599void tst_QTextEdit::countTextChangedOnRemove()
2600{
2601 QTextEdit edit;
2602 edit.insertPlainText(text: "Hello");
2603
2604 QSignalSpy spy(&edit, SIGNAL(textChanged()));
2605
2606 QKeyEvent event(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
2607 QCoreApplication::instance()->notify(&edit, &event);
2608
2609 QCOMPARE(spy.count(), 1);
2610}
2611
2612#ifndef QT_NO_REGEXP
2613void tst_QTextEdit::findWithRegExp()
2614{
2615 ed->setHtml(QStringLiteral("arbitrary te<span style=\"color:#ff0000\">xt</span>"));
2616 QRegExp rx("\\w{2}xt");
2617
2618 bool found = ed->find(exp: rx);
2619
2620 QVERIFY(found);
2621 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("text"));
2622}
2623
2624void tst_QTextEdit::findBackwardWithRegExp()
2625{
2626 ed->setPlainText(QStringLiteral("arbitrary text"));
2627 QTextCursor cursor = ed->textCursor();
2628 cursor.movePosition(op: QTextCursor::End);
2629 ed->setTextCursor(cursor);
2630 QRegExp rx("a\\w*t");
2631
2632 bool found = ed->find(exp: rx, options: QTextDocument::FindBackward);
2633
2634 QVERIFY(found);
2635 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("arbit"));
2636}
2637
2638void tst_QTextEdit::findWithRegExpReturnsFalseIfNoMoreResults()
2639{
2640 ed->setPlainText(QStringLiteral("arbitrary text"));
2641 QRegExp rx("t.xt");
2642 ed->find(exp: rx);
2643
2644 bool found = ed->find(exp: rx);
2645
2646 QVERIFY(!found);
2647 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("text"));
2648}
2649#endif
2650
2651#if QT_CONFIG(regularexpression)
2652void tst_QTextEdit::findWithRegularExpression()
2653{
2654 ed->setHtml(QStringLiteral("arbitrary te<span style=\"color:#ff0000\">xt</span>"));
2655 QRegularExpression rx("\\w{2}xt");
2656
2657 bool found = ed->find(exp: rx);
2658
2659 QVERIFY(found);
2660 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("text"));
2661}
2662
2663void tst_QTextEdit::findBackwardWithRegularExpression()
2664{
2665 ed->setPlainText(QStringLiteral("arbitrary text"));
2666 QTextCursor cursor = ed->textCursor();
2667 cursor.movePosition(op: QTextCursor::End);
2668 ed->setTextCursor(cursor);
2669 QRegularExpression rx("a\\w*t");
2670
2671 bool found = ed->find(exp: rx, options: QTextDocument::FindBackward);
2672
2673 QVERIFY(found);
2674 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("arbit"));
2675}
2676
2677void tst_QTextEdit::findWithRegularExpressionReturnsFalseIfNoMoreResults()
2678{
2679 ed->setPlainText(QStringLiteral("arbitrary text"));
2680 QRegularExpression rx("t.xt");
2681 ed->find(exp: rx);
2682
2683 bool found = ed->find(exp: rx);
2684
2685 QVERIFY(!found);
2686 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("text"));
2687}
2688#endif
2689
2690#if QT_CONFIG(wheelevent)
2691
2692class TextEdit : public QTextEdit
2693{
2694public:
2695 TextEdit(QWidget *parent = 0)
2696 : QTextEdit(parent)
2697 {}
2698 void wheelEvent(QWheelEvent *event)
2699 {
2700 QTextEdit::wheelEvent(e: event);
2701 }
2702};
2703
2704void tst_QTextEdit::wheelEvent()
2705{
2706 TextEdit ed(0);
2707 ed.setPlainText(QStringLiteral("Line\nLine\nLine\n"));
2708 ed.setReadOnly(true);
2709
2710 float defaultFontSize = ed.font().pointSizeF();
2711 QWheelEvent wheelUp(QPointF(), QPointF(), QPoint(), QPoint(0, 120),
2712 Qt::NoButton, Qt::ControlModifier, Qt::NoScrollPhase, Qt::MouseEventNotSynthesized);
2713 ed.wheelEvent(event: &wheelUp);
2714
2715 QCOMPARE(defaultFontSize + 1, ed.font().pointSizeF());
2716
2717 QWheelEvent wheelHalfDown(QPointF(), QPointF(), QPoint(), QPoint(0, -60),
2718 Qt::NoButton, Qt::ControlModifier, Qt::NoScrollPhase, Qt::MouseEventNotSynthesized);
2719 ed.wheelEvent(event: &wheelHalfDown);
2720
2721 QCOMPARE(defaultFontSize + 0.5, ed.font().pointSizeF());
2722}
2723
2724#endif
2725
2726namespace {
2727 class MyPaintEngine : public QPaintEngine
2728 {
2729 public:
2730 bool begin(QPaintDevice *) override { return true; }
2731
2732 bool end() override { return true; }
2733
2734 void updateState(const QPaintEngineState &) override { }
2735
2736 void drawPixmap(const QRectF &, const QPixmap &, const QRectF &) override { }
2737
2738 void drawTextItem(const QPointF &, const QTextItem &textItem) override
2739 {
2740 itemFonts.append(t: qMakePair(x: textItem.text(), y: textItem.font()));
2741 }
2742
2743 Type type() const override { return User; }
2744
2745
2746 QList<QPair<QString, QFont> > itemFonts;
2747 };
2748
2749 class MyPaintDevice : public QPaintDevice
2750 {
2751 public:
2752 MyPaintDevice() : m_paintEngine(new MyPaintEngine)
2753 {
2754 }
2755
2756
2757 QPaintEngine *paintEngine () const
2758 {
2759 return m_paintEngine;
2760 }
2761
2762 int metric (QPaintDevice::PaintDeviceMetric metric) const {
2763 switch (metric) {
2764 case QPaintDevice::PdmWidth:
2765 case QPaintDevice::PdmHeight:
2766 case QPaintDevice::PdmWidthMM:
2767 case QPaintDevice::PdmHeightMM:
2768 case QPaintDevice::PdmNumColors:
2769 return INT_MAX;
2770 case QPaintDevice::PdmDepth:
2771 return 32;
2772 case QPaintDevice::PdmDpiX:
2773 case QPaintDevice::PdmDpiY:
2774 case QPaintDevice::PdmPhysicalDpiX:
2775 case QPaintDevice::PdmPhysicalDpiY:
2776 return 72;
2777 case QPaintDevice::PdmDevicePixelRatio:
2778 case QPaintDevice::PdmDevicePixelRatioScaled:
2779 ; // fall through
2780 }
2781 return 0;
2782 }
2783
2784 MyPaintEngine *m_paintEngine;
2785 };
2786}
2787
2788void tst_QTextEdit::preeditCharFormat_data()
2789{
2790 QTest::addColumn<QList<QInputMethodEvent::Attribute> >(name: "imeAttributes");
2791 QTest::addColumn<QStringList>(name: "substrings");
2792 QTest::addColumn<QList<bool> >(name: "boldnessList");
2793 QTest::addColumn<QList<bool> >(name: "italicnessList");
2794 QTest::addColumn<QList<int> >(name: "pointSizeList");
2795
2796 {
2797 QList<QInputMethodEvent::Attribute> attributes;
2798 {
2799 QTextCharFormat tcf;
2800 tcf.setFontPointSize(13);
2801 tcf.setFontItalic(true);
2802 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 1, 1, tcf));
2803 }
2804
2805 {
2806 QTextCharFormat tcf;
2807 tcf.setFontPointSize(8);
2808 tcf.setFontWeight(QFont::Normal);
2809 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
2810 }
2811
2812 QTest::newRow(dataTag: "Two formats, middle, in order")
2813 << attributes
2814 << (QStringList() << "P" << "r" << "eE" << "di" << "tText")
2815 << (QList<bool>() << true << true << true << false << true)
2816 << (QList<bool>() << false << true << false << false << false)
2817 << (QList<int>() << 20 << 13 << 20 << 8 << 20);
2818 }
2819
2820 {
2821 QList<QInputMethodEvent::Attribute> attributes;
2822 {
2823 QTextCharFormat tcf;
2824 tcf.setFontPointSize(8);
2825 tcf.setFontWeight(QFont::Normal);
2826 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
2827 }
2828
2829 {
2830 QTextCharFormat tcf;
2831 tcf.setFontPointSize(13);
2832 tcf.setFontItalic(true);
2833 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 1, 1, tcf));
2834 }
2835
2836 QTest::newRow(dataTag: "Two formats, middle, out of order")
2837 << attributes
2838 << (QStringList() << "P" << "r" << "eE" << "di" << "tText")
2839 << (QList<bool>() << true << true << true << false << true)
2840 << (QList<bool>() << false << true << false << false << false)
2841 << (QList<int>() << 20 << 13 << 20 << 8 << 20);
2842 }
2843
2844 {
2845 QList<QInputMethodEvent::Attribute> attributes;
2846 {
2847 QTextCharFormat tcf;
2848 tcf.setFontPointSize(13);
2849 tcf.setFontItalic(true);
2850 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, 1, tcf));
2851 }
2852
2853 {
2854 QTextCharFormat tcf;
2855 tcf.setFontPointSize(8);
2856 tcf.setFontWeight(QFont::Normal);
2857 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
2858 }
2859
2860 QTest::newRow(dataTag: "Two formats, front, in order")
2861 << attributes
2862 << (QStringList() << "P" << "reE" << "di" << "tText")
2863 << (QList<bool>() << true << true << false << true)
2864 << (QList<bool>() << true << false << false << false)
2865 << (QList<int>() << 13 << 20 << 8 << 20);
2866 }
2867
2868 {
2869 QList<QInputMethodEvent::Attribute> attributes;
2870 {
2871 QTextCharFormat tcf;
2872 tcf.setFontPointSize(8);
2873 tcf.setFontWeight(QFont::Normal);
2874 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 4, 2, tcf));
2875 }
2876
2877 {
2878 QTextCharFormat tcf;
2879 tcf.setFontPointSize(13);
2880 tcf.setFontItalic(true);
2881 attributes.append(t: QInputMethodEvent::Attribute(QInputMethodEvent::TextFormat, 0, 1, tcf));
2882 }
2883
2884 QTest::newRow(dataTag: "Two formats, front, out of order")
2885 << attributes
2886 << (QStringList() << "P" << "reE" << "di" << "tText")
2887 << (QList<bool>() << true << true << false << true)
2888 << (QList<bool>() << true << false << false << false)
2889 << (QList<int>() << 13 << 20 << 8 << 20);
2890 }
2891}
2892
2893void tst_QTextEdit::preeditCharFormat()
2894{
2895 QFETCH(QList<QInputMethodEvent::Attribute>, imeAttributes);
2896 QFETCH(QStringList, substrings);
2897 QFETCH(QList<bool>, boldnessList);
2898 QFETCH(QList<bool>, italicnessList);
2899 QFETCH(QList<int>, pointSizeList);
2900
2901 QTextEdit *w = new QTextEdit;
2902 w->show();
2903 QVERIFY(QTest::qWaitForWindowExposed(w));
2904
2905 // Set main char format
2906 {
2907 QTextCharFormat tcf;
2908 tcf.setFontPointSize(20);
2909 tcf.setFontWeight(QFont::Bold);
2910 w->mergeCurrentCharFormat(modifier: tcf);
2911 }
2912
2913 QList<QInputMethodEvent::Attribute> attributes;
2914 attributes.prepend(t: QInputMethodEvent::Attribute(QInputMethodEvent::Cursor,
2915 w->textCursor().position(),
2916 0,
2917 QVariant()));
2918
2919 attributes += imeAttributes;
2920
2921 QInputMethodEvent event("PreEditText", attributes);
2922 QApplication::sendEvent(receiver: w, event: &event);
2923
2924 MyPaintDevice device;
2925 {
2926 QPainter p(&device);
2927 w->document()->drawContents(painter: &p);
2928 }
2929
2930 QCOMPARE(device.m_paintEngine->itemFonts.size(), substrings.size());
2931 for (int i = 0; i < substrings.size(); ++i)
2932 QCOMPARE(device.m_paintEngine->itemFonts.at(i).first, substrings.at(i));
2933
2934 for (int i = 0; i < substrings.size(); ++i)
2935 QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.bold(), boldnessList.at(i));
2936
2937 for (int i = 0; i < substrings.size(); ++i)
2938 QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.italic(), italicnessList.at(i));
2939
2940 for (int i = 0; i < substrings.size(); ++i)
2941 QCOMPARE(device.m_paintEngine->itemFonts.at(i).second.pointSize(), pointSizeList.at(i));
2942
2943 delete w;
2944}
2945
2946QTEST_MAIN(tst_QTextEdit)
2947#include "tst_qtextedit.moc"
2948

source code of qtbase/tests/auto/widgets/widgets/qtextedit/tst_qtextedit.cpp