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
33#include <qtextedit.h>
34#include <qtextcursor.h>
35#include <qtextlist.h>
36#include <qdebug.h>
37#include <qapplication.h>
38#include <qclipboard.h>
39#include <qtextbrowser.h>
40#include <private/qwidgettextcontrol_p.h>
41#include <qscrollbar.h>
42#include <qtextobject.h>
43#include <qmenu.h>
44
45#include <qabstracttextdocumentlayout.h>
46#include <qtextdocumentfragment.h>
47
48#include "qplaintextedit.h"
49#include "../../../shared/platformclipboard.h"
50
51//Used in copyAvailable
52typedef QPair<Qt::Key, Qt::KeyboardModifier> keyPairType;
53typedef QList<keyPairType> pairListType;
54Q_DECLARE_METATYPE(keyPairType);
55
56QT_FORWARD_DECLARE_CLASS(QPlainTextEdit)
57
58class tst_QPlainTextEdit : public QObject
59{
60 Q_OBJECT
61public:
62 tst_QPlainTextEdit();
63
64public slots:
65 void init();
66 void cleanup();
67private slots:
68 void getSetCheck();
69#ifndef QT_NO_CLIPBOARD
70 void clearMustNotChangeClipboard();
71#endif
72 void clearMustNotResetRootFrameMarginToDefault();
73 void paragSeparatorOnPlaintextAppend();
74#ifndef QT_NO_CLIPBOARD
75 void selectAllSetsNotSelection();
76#endif
77 void asciiTab();
78 void setDocument();
79 void emptyAppend();
80 void appendOnEmptyDocumentShouldReuseInitialParagraph();
81 void cursorPositionChanged();
82 void setTextCursor();
83#ifndef QT_NO_CLIPBOARD
84 void undoAvailableAfterPaste();
85#endif
86 void undoRedoAvailableRepetition();
87 void appendShouldNotTouchTheSelection();
88 void backspace();
89 void shiftBackspace();
90 void undoRedo();
91 void preserveCharFormatInAppend();
92#ifndef QT_NO_CLIPBOARD
93 void copyAndSelectAllInReadonly();
94#endif
95 void charWithAltOrCtrlModifier_data();
96 void charWithAltOrCtrlModifier();
97 void noPropertiesOnDefaultTextEditCharFormat();
98 void setPlainTextShouldEmitTextChangedOnce();
99 void overwriteMode();
100 void shiftDownInLineLastShouldSelectToEnd_data();
101 void shiftDownInLineLastShouldSelectToEnd();
102 void undoRedoShouldRepositionTextEditCursor();
103 void lineWrapModes();
104#ifndef QT_NO_CURSOR
105 void mouseCursorShape();
106#endif
107 void implicitClear();
108 void undoRedoAfterSetContent();
109 void numPadKeyNavigation();
110 void moveCursor();
111#ifndef QT_NO_CLIPBOARD
112 void mimeDataReimplementations();
113#endif
114 void shiftEnterShouldInsertLineSeparator();
115 void selectWordsFromStringsContainingSeparators_data();
116 void selectWordsFromStringsContainingSeparators();
117#ifndef QT_NO_CLIPBOARD
118 void canPaste();
119 void copyAvailable_data();
120 void copyAvailable();
121#endif
122 void ensureCursorVisibleOnInitialShow();
123 void setTextInsideResizeEvent();
124 void colorfulAppend();
125 void ensureVisibleWithRtl();
126 void preserveCharFormatAfterSetPlainText();
127 void extraSelections();
128 void adjustScrollbars();
129 void textObscuredByScrollbars();
130 void setTextPreservesUndoRedoEnabled();
131 void wordWrapProperty();
132 void lineWrapProperty();
133 void selectionChanged();
134 void blockCountChanged();
135 void insertAndScrollToBottom();
136 void inputMethodQueryImHints_data();
137 void inputMethodQueryImHints();
138#ifndef QT_NO_REGEXP
139 void findWithRegExp();
140 void findBackwardWithRegExp();
141 void findWithRegExpReturnsFalseIfNoMoreResults();
142#endif
143#if QT_CONFIG(regularexpression)
144 void findWithRegularExpression();
145 void findBackwardWithRegularExpression();
146 void findWithRegularExpressionReturnsFalseIfNoMoreResults();
147#endif
148 void layoutAfterMultiLineRemove();
149 void undoCommandRemovesAndReinsertsBlock();
150 void taskQTBUG_43562_lineCountCrash();
151#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
152 void contextMenu();
153#endif
154 void inputMethodCursorRect();
155#if QT_CONFIG(scrollbar)
156 void updateAfterChangeCenterOnScroll();
157#endif
158 void updateCursorPositionAfterEdit();
159
160private:
161 void createSelection();
162 int blockCount() const;
163 int lineCount() const;
164
165 QPlainTextEdit *ed;
166 qreal rootFrameMargin;
167};
168
169// Testing get/set functions
170void tst_QPlainTextEdit::getSetCheck()
171{
172 QPlainTextEdit obj1;
173 // QTextDocument * QPlainTextEdit::document()
174 // void QPlainTextEdit::setDocument(QTextDocument *)
175 QTextDocument *var1 = new QTextDocument;
176 var1->setDocumentLayout(new QPlainTextDocumentLayout(var1));
177 obj1.setDocument(var1);
178 QCOMPARE(var1, obj1.document());
179 obj1.setDocument((QTextDocument *)0);
180 QVERIFY(var1 != obj1.document()); // QPlainTextEdit creates a new document when setting 0
181 QVERIFY((QTextDocument *)0 != obj1.document());
182 delete var1;
183
184
185 // bool QPlainTextEdit::tabChangesFocus()
186 // void QPlainTextEdit::setTabChangesFocus(bool)
187 obj1.setTabChangesFocus(false);
188 QCOMPARE(false, obj1.tabChangesFocus());
189 obj1.setTabChangesFocus(true);
190 QCOMPARE(true, obj1.tabChangesFocus());
191
192 // LineWrapMode QPlainTextEdit::lineWrapMode()
193 // void QPlainTextEdit::setLineWrapMode(LineWrapMode)
194 obj1.setLineWrapMode(QPlainTextEdit::LineWrapMode(QPlainTextEdit::NoWrap));
195 QCOMPARE(QPlainTextEdit::LineWrapMode(QPlainTextEdit::NoWrap), obj1.lineWrapMode());
196 obj1.setLineWrapMode(QPlainTextEdit::LineWrapMode(QPlainTextEdit::WidgetWidth));
197 QCOMPARE(QPlainTextEdit::LineWrapMode(QPlainTextEdit::WidgetWidth), obj1.lineWrapMode());
198// obj1.setLineWrapMode(QPlainTextEdit::LineWrapMode(QPlainTextEdit::FixedPixelWidth));
199// QCOMPARE(QPlainTextEdit::LineWrapMode(QPlainTextEdit::FixedPixelWidth), obj1.lineWrapMode());
200// obj1.setLineWrapMode(QPlainTextEdit::LineWrapMode(QPlainTextEdit::FixedColumnWidth));
201// QCOMPARE(QPlainTextEdit::LineWrapMode(QPlainTextEdit::FixedColumnWidth), obj1.lineWrapMode());
202
203
204 // bool QPlainTextEdit::overwriteMode()
205 // void QPlainTextEdit::setOverwriteMode(bool)
206 obj1.setOverwriteMode(false);
207 QCOMPARE(false, obj1.overwriteMode());
208 obj1.setOverwriteMode(true);
209 QCOMPARE(true, obj1.overwriteMode());
210
211 // int QPlainTextEdit::tabStopWidth()
212 // void QPlainTextEdit::setTabStopWidth(int)
213 obj1.setTabStopDistance(0);
214 QCOMPARE(0, obj1.tabStopDistance());
215 obj1.setTabStopDistance(-1);
216 QCOMPARE(0, obj1.tabStopDistance()); // Makes no sense to set a negative tabstop value
217 obj1.setTabStopDistance(std::numeric_limits<qreal>::max());
218 QCOMPARE(std::numeric_limits<qreal>::max(), obj1.tabStopDistance());
219}
220
221class QtTestDocumentLayout : public QAbstractTextDocumentLayout
222{
223 Q_OBJECT
224public:
225 inline QtTestDocumentLayout(QPlainTextEdit *edit, QTextDocument *doc, int &itCount)
226 : QAbstractTextDocumentLayout(doc), useBiggerSize(false), ed(edit), iterationCounter(itCount) {}
227
228 virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &) {}
229
230 virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const { return 0; }
231
232 virtual void documentChanged(int, int, int) {}
233
234 virtual int pageCount() const { return 1; }
235
236 virtual QSizeF documentSize() const { return usedSize; }
237
238 virtual QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); }
239 virtual QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); }
240
241 bool useBiggerSize;
242 QSize usedSize;
243
244 QPlainTextEdit *ed;
245
246 int &iterationCounter;
247};
248
249tst_QPlainTextEdit::tst_QPlainTextEdit()
250{}
251
252void tst_QPlainTextEdit::init()
253{
254 ed = new QPlainTextEdit(0);
255 rootFrameMargin = ed->document()->documentMargin();
256}
257
258void tst_QPlainTextEdit::cleanup()
259{
260 delete ed;
261 ed = 0;
262}
263
264
265void tst_QPlainTextEdit::createSelection()
266{
267 QTest::keyClicks(widget: ed, sequence: "Hello World");
268 /* go to start */
269#ifndef Q_OS_MAC
270 QTest::keyClick(widget: ed, key: Qt::Key_Home, modifier: Qt::ControlModifier);
271#else
272 QTest::keyClick(ed, Qt::Key_Home);
273#endif
274 QCOMPARE(ed->textCursor().position(), 0);
275 /* select until end of text */
276#ifndef Q_OS_MAC
277 QTest::keyClick(widget: ed, key: Qt::Key_End, modifier: Qt::ControlModifier | Qt::ShiftModifier);
278#else
279 QTest::keyClick(ed, Qt::Key_End, Qt::ShiftModifier);
280#endif
281 QCOMPARE(ed->textCursor().position(), 11);
282}
283#ifndef QT_NO_CLIPBOARD
284void tst_QPlainTextEdit::clearMustNotChangeClipboard()
285{
286 if (!PlatformClipboard::isAvailable())
287 QSKIP("Clipboard not working with cron-started unit tests");
288
289 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
290 QSKIP("Wayland: This fails. Figure out why.");
291
292 ed->textCursor().insertText(text: "Hello World");
293 QString txt("This is different text");
294 QApplication::clipboard()->setText(txt);
295 ed->clear();
296 QCOMPARE(QApplication::clipboard()->text(), txt);
297}
298#endif
299
300void tst_QPlainTextEdit::clearMustNotResetRootFrameMarginToDefault()
301{
302 QCOMPARE(ed->document()->rootFrame()->frameFormat().margin(), rootFrameMargin);
303 ed->clear();
304 QCOMPARE(ed->document()->rootFrame()->frameFormat().margin(), rootFrameMargin);
305}
306
307
308void tst_QPlainTextEdit::paragSeparatorOnPlaintextAppend()
309{
310 ed->appendPlainText(text: "Hello\nWorld");
311 int cnt = 0;
312 QTextBlock blk = ed->document()->begin();
313 while (blk.isValid()) {
314 ++cnt;
315 blk = blk.next();
316 }
317 QCOMPARE(cnt, 2);
318}
319
320#ifndef QT_NO_CLIPBOARD
321void tst_QPlainTextEdit::selectAllSetsNotSelection()
322{
323 if (!QApplication::clipboard()->supportsSelection())
324 QSKIP("Test only relevant for systems with selection");
325
326 QApplication::clipboard()->setText(QString("foobar"), mode: QClipboard::Selection);
327 QCOMPARE(QApplication::clipboard()->text(QClipboard::Selection), QString("foobar"));
328
329 ed->insertPlainText(text: "Hello World");
330 ed->selectAll();
331
332 QCOMPARE(QApplication::clipboard()->text(QClipboard::Selection), QString::fromLatin1("foobar"));
333}
334#endif
335
336void tst_QPlainTextEdit::asciiTab()
337{
338 QPlainTextEdit edit;
339 edit.setPlainText("\t");
340 edit.show();
341 qApp->processEvents();
342 QCOMPARE(edit.toPlainText().at(0), QChar('\t'));
343}
344
345void tst_QPlainTextEdit::setDocument()
346{
347 QTextDocument *document = new QTextDocument(ed);
348 document->setDocumentLayout(new QPlainTextDocumentLayout(document));
349 QTextCursor(document).insertText(text: "Test");
350 ed->setDocument(document);
351 QCOMPARE(ed->toPlainText(), QString("Test"));
352}
353
354
355int tst_QPlainTextEdit::blockCount() const
356{
357 int blocks = 0;
358 for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next())
359 ++blocks;
360 return blocks;
361}
362
363int tst_QPlainTextEdit::lineCount() const
364{
365 int lines = 0;
366 for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next()) {
367 ed->document()->documentLayout()->blockBoundingRect(block);
368 lines += block.layout()->lineCount();
369 }
370 return lines;
371}
372
373// Supporter issue #56783
374void tst_QPlainTextEdit::emptyAppend()
375{
376 ed->appendPlainText(text: "Blah");
377 QCOMPARE(blockCount(), 1);
378 ed->appendPlainText(text: QString());
379 QCOMPARE(blockCount(), 2);
380 ed->appendPlainText(text: QString(" "));
381 QCOMPARE(blockCount(), 3);
382}
383
384void tst_QPlainTextEdit::appendOnEmptyDocumentShouldReuseInitialParagraph()
385{
386 QCOMPARE(blockCount(), 1);
387 ed->appendPlainText(text: "Blah");
388 QCOMPARE(blockCount(), 1);
389}
390
391
392class CursorPositionChangedRecorder : public QObject
393{
394 Q_OBJECT
395public:
396 inline CursorPositionChangedRecorder(QPlainTextEdit *ed)
397 : editor(ed)
398 {
399 connect(sender: editor, SIGNAL(cursorPositionChanged()), receiver: this, SLOT(recordCursorPos()));
400 }
401
402 QList<int> cursorPositions;
403
404private slots:
405 void recordCursorPos()
406 {
407 cursorPositions.append(t: editor->textCursor().position());
408 }
409
410private:
411 QPlainTextEdit *editor;
412};
413
414void tst_QPlainTextEdit::cursorPositionChanged()
415{
416 QSignalSpy spy(ed, SIGNAL(cursorPositionChanged()));
417
418 spy.clear();
419 QTest::keyClick(widget: ed, key: Qt::Key_A);
420 QCOMPARE(spy.count(), 1);
421
422 QTextCursor cursor = ed->textCursor();
423 cursor.movePosition(op: QTextCursor::Start);
424 ed->setTextCursor(cursor);
425 cursor.movePosition(op: QTextCursor::End);
426 spy.clear();
427 cursor.insertText(text: "Test");
428 QCOMPARE(spy.count(), 0);
429
430 cursor.movePosition(op: QTextCursor::End);
431 ed->setTextCursor(cursor);
432 cursor.movePosition(op: QTextCursor::Start);
433 spy.clear();
434 cursor.insertText(text: "Test");
435 QCOMPARE(spy.count(), 1);
436
437 spy.clear();
438 QTest::keyClick(widget: ed, key: Qt::Key_Left);
439 QCOMPARE(spy.count(), 1);
440
441 CursorPositionChangedRecorder spy2(ed);
442 QVERIFY(ed->textCursor().position() > 0);
443 ed->setPlainText("Hello World");
444 QCOMPARE(spy2.cursorPositions.count(), 1);
445 QCOMPARE(spy2.cursorPositions.at(0), 0);
446 QCOMPARE(ed->textCursor().position(), 0);
447}
448
449void tst_QPlainTextEdit::setTextCursor()
450{
451 QSignalSpy spy(ed, SIGNAL(cursorPositionChanged()));
452
453 ed->setPlainText("Test");
454 QTextCursor cursor = ed->textCursor();
455 cursor.movePosition(op: QTextCursor::Start);
456 cursor.movePosition(op: QTextCursor::NextCharacter);
457
458 spy.clear();
459
460 ed->setTextCursor(cursor);
461 QCOMPARE(spy.count(), 1);
462}
463
464#ifndef QT_NO_CLIPBOARD
465void tst_QPlainTextEdit::undoAvailableAfterPaste()
466{
467 if (!PlatformClipboard::isAvailable())
468 QSKIP("Clipboard not working with cron-started unit tests");
469
470 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
471 QSKIP("Wayland: This fails. Figure out why.");
472
473 QSignalSpy spy(ed->document(), SIGNAL(undoAvailable(bool)));
474
475 const QString txt("Test");
476 QApplication::clipboard()->setText(txt);
477 ed->paste();
478 QVERIFY(spy.count() >= 1);
479 QCOMPARE(ed->toPlainText(), txt);
480}
481#endif
482
483class UndoRedoRecorder : public QObject
484{
485 Q_OBJECT
486public:
487 UndoRedoRecorder(QTextDocument *doc)
488 : undoRepetitions(false)
489 , redoRepetitions(false)
490 , undoCount(0)
491 , redoCount(0)
492 {
493 connect(sender: doc, SIGNAL(undoAvailable(bool)), receiver: this, SLOT(undoAvailable(bool)));
494 connect(sender: doc, SIGNAL(redoAvailable(bool)), receiver: this, SLOT(redoAvailable(bool)));
495 }
496
497 bool undoRepetitions;
498 bool redoRepetitions;
499
500private slots:
501 void undoAvailable(bool enabled) {
502 if (undoCount > 0 && enabled == lastUndoEnabled)
503 undoRepetitions = true;
504
505 ++undoCount;
506 lastUndoEnabled = enabled;
507 }
508
509 void redoAvailable(bool enabled) {
510 if (redoCount > 0 && enabled == lastRedoEnabled)
511 redoRepetitions = true;
512
513 ++redoCount;
514 lastRedoEnabled = enabled;
515 }
516
517private:
518 bool lastUndoEnabled;
519 bool lastRedoEnabled;
520
521 int undoCount;
522 int redoCount;
523};
524
525void tst_QPlainTextEdit::undoRedoAvailableRepetition()
526{
527 UndoRedoRecorder spy(ed->document());
528
529 ed->textCursor().insertText(text: "ABC\n\nDEF\n\nGHI\n");
530 ed->textCursor().insertText(text: "foo\n");
531 ed->textCursor().insertText(text: "bar\n");
532 ed->undo(); ed->undo(); ed->undo();
533 ed->redo(); ed->redo(); ed->redo();
534
535 QVERIFY(!spy.undoRepetitions);
536 QVERIFY(!spy.redoRepetitions);
537}
538
539void tst_QPlainTextEdit::appendShouldNotTouchTheSelection()
540{
541 QTextCursor cursor(ed->document());
542 QTextCharFormat fmt;
543 fmt.setForeground(Qt::blue);
544 cursor.insertText(text: "H", format: fmt);
545 fmt.setForeground(Qt::red);
546 cursor.insertText(text: "ey", format: fmt);
547
548 cursor.insertText(text: "some random text inbetween");
549
550 cursor.movePosition(op: QTextCursor::Start);
551 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
552 QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue));
553 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
554 QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red));
555 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
556 QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red));
557 QCOMPARE(cursor.selectedText(), QString("Hey"));
558
559 ed->setTextCursor(cursor);
560 QVERIFY(ed->textCursor().hasSelection());
561
562 ed->appendHtml(html: "<b>Some Bold Text</b>");
563 cursor.movePosition(op: QTextCursor::Start);
564 cursor.movePosition(op: QTextCursor::NextCharacter);
565 QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue));
566}
567
568void tst_QPlainTextEdit::backspace()
569{
570 QTextCursor cursor = ed->textCursor();
571
572 QTextListFormat listFmt;
573 listFmt.setStyle(QTextListFormat::ListDisc);
574 listFmt.setIndent(1);
575 cursor.insertList(format: listFmt);
576 cursor.insertText(text: "A");
577
578 ed->setTextCursor(cursor);
579
580 // delete 'A'
581 QTest::keyClick(widget: ed, key: Qt::Key_Backspace);
582 QVERIFY(ed->textCursor().currentList());
583 // delete list
584 QTest::keyClick(widget: ed, key: Qt::Key_Backspace);
585 QVERIFY(!ed->textCursor().currentList());
586 QCOMPARE(ed->textCursor().blockFormat().indent(), 1);
587 // outdent paragraph
588 QTest::keyClick(widget: ed, key: Qt::Key_Backspace);
589 QCOMPARE(ed->textCursor().blockFormat().indent(), 0);
590}
591
592void tst_QPlainTextEdit::shiftBackspace()
593{
594 QTextCursor cursor = ed->textCursor();
595
596 QTextListFormat listFmt;
597 listFmt.setStyle(QTextListFormat::ListDisc);
598 listFmt.setIndent(1);
599 cursor.insertList(format: listFmt);
600 cursor.insertText(text: "A");
601
602 ed->setTextCursor(cursor);
603
604 // delete 'A'
605 QTest::keyClick(widget: ed, key: Qt::Key_Backspace, modifier: Qt::ShiftModifier);
606 QVERIFY(ed->textCursor().currentList());
607 // delete list
608 QTest::keyClick(widget: ed, key: Qt::Key_Backspace, modifier: Qt::ShiftModifier);
609 QVERIFY(!ed->textCursor().currentList());
610 QCOMPARE(ed->textCursor().blockFormat().indent(), 1);
611 // outdent paragraph
612 QTest::keyClick(widget: ed, key: Qt::Key_Backspace, modifier: Qt::ShiftModifier);
613 QCOMPARE(ed->textCursor().blockFormat().indent(), 0);
614}
615
616void tst_QPlainTextEdit::undoRedo()
617{
618 ed->clear();
619 QTest::keyClicks(widget: ed, sequence: "abc d");
620 QCOMPARE(ed->toPlainText(), QString("abc d"));
621 ed->undo();
622 QCOMPARE(ed->toPlainText(), QString());
623 ed->redo();
624 QCOMPARE(ed->toPlainText(), QString("abc d"));
625#ifdef Q_OS_WIN
626 // shortcut for undo
627 QTest::keyClick(ed, Qt::Key_Backspace, Qt::AltModifier);
628 QCOMPARE(ed->toPlainText(), QString());
629 // shortcut for redo
630 QTest::keyClick(ed, Qt::Key_Backspace, Qt::ShiftModifier|Qt::AltModifier);
631 QCOMPARE(ed->toPlainText(), QString("abc d"));
632#endif
633}
634
635// Task #70465
636void tst_QPlainTextEdit::preserveCharFormatInAppend()
637{
638 ed->appendHtml(html: "First para");
639 ed->appendHtml(html: "<b>Second para</b>");
640 ed->appendHtml(html: "third para");
641
642 QTextCursor cursor(ed->textCursor());
643
644 cursor.movePosition(op: QTextCursor::Start);
645 cursor.movePosition(op: QTextCursor::NextCharacter);
646 QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Normal);
647 QCOMPARE(cursor.block().text(), QString("First para"));
648
649 cursor.movePosition(op: QTextCursor::NextBlock);
650 cursor.movePosition(op: QTextCursor::NextCharacter);
651 QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Bold);
652 QCOMPARE(cursor.block().text(), QString("Second para"));
653
654 cursor.movePosition(op: QTextCursor::NextBlock);
655 cursor.movePosition(op: QTextCursor::NextCharacter);
656 QCOMPARE(cursor.charFormat().fontWeight(), (int)QFont::Normal);
657 QCOMPARE(cursor.block().text(), QString("third para"));
658}
659
660#ifndef QT_NO_CLIPBOARD
661void tst_QPlainTextEdit::copyAndSelectAllInReadonly()
662{
663 if (!PlatformClipboard::isAvailable())
664 QSKIP("Clipboard not working with cron-started unit tests");
665
666 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
667 QSKIP("Wayland: This fails. Figure out why.");
668
669 ed->setReadOnly(true);
670 ed->setPlainText("Hello World");
671
672 QTextCursor cursor = ed->textCursor();
673 cursor.clearSelection();
674 ed->setTextCursor(cursor);
675 QVERIFY(!ed->textCursor().hasSelection());
676
677 QCOMPARE(ed->toPlainText(), QString("Hello World"));
678
679 // shouldn't do anything
680 QTest::keyClick(widget: ed, key: Qt::Key_A);
681
682 QCOMPARE(ed->toPlainText(), QString("Hello World"));
683
684 QTest::keyClick(widget: ed, key: Qt::Key_A, modifier: Qt::ControlModifier);
685
686 QVERIFY(ed->textCursor().hasSelection());
687
688 QApplication::clipboard()->setText(QString());
689 QVERIFY(QApplication::clipboard()->text().isEmpty());
690
691 QTest::keyClick(widget: ed, key: Qt::Key_C, modifier: Qt::ControlModifier);
692 QCOMPARE(QApplication::clipboard()->text(), QString("Hello World"));
693}
694#endif
695
696Q_DECLARE_METATYPE(Qt::KeyboardModifiers)
697
698// Test how QWidgetTextControlPrivate (used in QPlainTextEdit, QTextEdit)
699// handles input with modifiers.
700void tst_QPlainTextEdit::charWithAltOrCtrlModifier_data()
701{
702 QTest::addColumn<Qt::KeyboardModifiers>(name: "modifiers");
703 QTest::addColumn<bool>(name: "textExpected");
704
705 QTest::newRow(dataTag: "no-modifiers") << Qt::KeyboardModifiers() << true;
706 // Ctrl, Ctrl+Shift: No text (QTBUG-35734)
707 QTest::newRow(dataTag: "ctrl") << Qt::KeyboardModifiers(Qt::ControlModifier)
708 << false;
709 QTest::newRow(dataTag: "ctrl-shift") << Qt::KeyboardModifiers(Qt::ShiftModifier | Qt::ControlModifier)
710 << false;
711 QTest::newRow(dataTag: "alt") << Qt::KeyboardModifiers(Qt::AltModifier) << true;
712 // Alt-Ctrl (Alt-Gr on German keyboards, Task 129098): Expect text
713 QTest::newRow(dataTag: "alt-ctrl") << (Qt::AltModifier | Qt::ControlModifier) << true;
714}
715
716void tst_QPlainTextEdit::charWithAltOrCtrlModifier()
717{
718 QFETCH(Qt::KeyboardModifiers, modifiers);
719 QFETCH(bool, textExpected);
720
721 QTest::keyClick(widget: ed, key: Qt::Key_At, modifier: modifiers);
722 const QString expectedText = textExpected ? QLatin1String("@") : QString();
723 QCOMPARE(ed->toPlainText(), expectedText);
724}
725
726void tst_QPlainTextEdit::noPropertiesOnDefaultTextEditCharFormat()
727{
728 // there should be no properties set on the default/initial char format
729 // on a text edit. Font properties instead should be taken from the
730 // widget's font (in sync with defaultFont property in document) and the
731 // foreground color should be taken from the palette.
732 QCOMPARE(ed->textCursor().charFormat().properties().count(), 0);
733}
734
735void tst_QPlainTextEdit::setPlainTextShouldEmitTextChangedOnce()
736{
737 QSignalSpy spy(ed, SIGNAL(textChanged()));
738 ed->setPlainText("Yankee Doodle");
739 QCOMPARE(spy.count(), 1);
740 ed->setPlainText("");
741 QCOMPARE(spy.count(), 2);
742}
743
744void tst_QPlainTextEdit::overwriteMode()
745{
746 QVERIFY(!ed->overwriteMode());
747 QTest::keyClicks(widget: ed, sequence: "Some first text");
748
749 QCOMPARE(ed->toPlainText(), QString("Some first text"));
750
751 ed->setOverwriteMode(true);
752
753 QTextCursor cursor = ed->textCursor();
754 cursor.setPosition(pos: 5);
755 ed->setTextCursor(cursor);
756
757 QTest::keyClicks(widget: ed, sequence: "shiny");
758 QCOMPARE(ed->toPlainText(), QString("Some shiny text"));
759
760 cursor.movePosition(op: QTextCursor::End);
761 ed->setTextCursor(cursor);
762
763 QTest::keyClick(widget: ed, key: Qt::Key_Enter);
764
765 ed->setOverwriteMode(false);
766 QTest::keyClicks(widget: ed, sequence: "Second paragraph");
767
768 QCOMPARE(blockCount(), 2);
769
770 cursor.movePosition(op: QTextCursor::Start);
771 cursor.movePosition(op: QTextCursor::EndOfBlock);
772
773 QCOMPARE(cursor.position(), 15);
774 ed->setTextCursor(cursor);
775
776 ed->setOverwriteMode(true);
777
778 QTest::keyClicks(widget: ed, sequence: " blah");
779
780 QCOMPARE(blockCount(), 2);
781
782 QTextBlock block = ed->document()->begin();
783 QCOMPARE(block.text(), QString("Some shiny text blah"));
784 block = block.next();
785 QCOMPARE(block.text(), QString("Second paragraph"));
786}
787
788void tst_QPlainTextEdit::shiftDownInLineLastShouldSelectToEnd_data()
789{
790 // shift cursor-down in the last line should select to the end of the document
791
792 QTest::addColumn<QString>(name: "input");
793 QTest::addColumn<int>(name: "totalLineCount");
794
795 QTest::newRow(dataTag: "1") << QString("Foo\nBar") << 2;
796 QTest::newRow(dataTag: "2") << QString("Foo\nBar") + QChar(QChar::LineSeparator) + QString("Baz") << 3;
797}
798
799void tst_QPlainTextEdit::shiftDownInLineLastShouldSelectToEnd()
800{
801 QFETCH(QString, input);
802 QFETCH(int, totalLineCount);
803
804 ed->setPlainText(input);
805 ed->show();
806
807 // ensure we're layouted
808 for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next())
809 ed->document()->documentLayout()->blockBoundingRect(block);
810
811 QCOMPARE(blockCount(), 2);
812
813 int lineCount = 0;
814 for (QTextBlock block = ed->document()->begin(); block.isValid(); block = block.next())
815 lineCount += block.layout()->lineCount();
816 QCOMPARE(lineCount, totalLineCount);
817
818 QTextCursor cursor = ed->textCursor();
819 cursor.movePosition(op: QTextCursor::Start);
820 ed->setTextCursor(cursor);
821
822 for (int i = 0; i < lineCount; ++i) {
823 QTest::keyClick(widget: ed, key: Qt::Key_Down, modifier: Qt::ShiftModifier);
824 }
825
826 input.replace(before: QLatin1Char('\n'), after: QChar(QChar::ParagraphSeparator));
827 QCOMPARE(ed->textCursor().selectedText(), input);
828 QVERIFY(ed->textCursor().atEnd());
829
830 // also test that without shift modifier the cursor does not move to the end
831 // for Key_Down in the last line
832 cursor.movePosition(op: QTextCursor::Start);
833 ed->setTextCursor(cursor);
834 for (int i = 0; i < lineCount; ++i) {
835 QTest::keyClick(widget: ed, key: Qt::Key_Down);
836 }
837 QVERIFY(!ed->textCursor().atEnd());
838}
839
840void tst_QPlainTextEdit::undoRedoShouldRepositionTextEditCursor()
841{
842 ed->setPlainText("five\nlines\nin\nthis\ntextedit");
843 QTextCursor cursor = ed->textCursor();
844 cursor.movePosition(op: QTextCursor::Start);
845
846 ed->setUndoRedoEnabled(false);
847 ed->setUndoRedoEnabled(true);
848
849 QVERIFY(!ed->document()->isUndoAvailable());
850 QVERIFY(!ed->document()->isRedoAvailable());
851
852 cursor.insertText(text: "Blah");
853
854 QVERIFY(ed->document()->isUndoAvailable());
855 QVERIFY(!ed->document()->isRedoAvailable());
856
857 cursor.movePosition(op: QTextCursor::End);
858 ed->setTextCursor(cursor);
859
860 QVERIFY(QMetaObject::invokeMethod(ed, "undo"));
861
862 QVERIFY(!ed->document()->isUndoAvailable());
863 QVERIFY(ed->document()->isRedoAvailable());
864
865 QCOMPARE(ed->textCursor().position(), 0);
866
867 cursor.movePosition(op: QTextCursor::End);
868 ed->setTextCursor(cursor);
869
870 QVERIFY(QMetaObject::invokeMethod(ed, "redo"));
871
872 QVERIFY(ed->document()->isUndoAvailable());
873 QVERIFY(!ed->document()->isRedoAvailable());
874
875 QCOMPARE(ed->textCursor().position(), 4);
876}
877
878void tst_QPlainTextEdit::lineWrapModes()
879{
880 QWidget *window = new QWidget;
881 ed->setParent(window);
882 window->show();
883 ed->show();
884 ed->setPlainText("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");
885 ed->setLineWrapMode(QPlainTextEdit::NoWrap);
886 QCOMPARE(lineCount(), 1);
887 ed->setLineWrapMode(QPlainTextEdit::WidgetWidth);
888
889 // QPlainTextEdit does lazy line layout on resize, only for the visible blocks.
890 // We thus need to make it wide enough to show something visible.
891 int minimumWidth = 2 * ed->document()->documentMargin();
892 minimumWidth += ed->fontMetrics().horizontalAdvance(QLatin1Char('a'));
893 minimumWidth += ed->frameWidth();
894 ed->resize(w: minimumWidth, h: 1000);
895 QCOMPARE(lineCount(), 26);
896 ed->setParent(0);
897 delete window;
898}
899
900#ifndef QT_NO_CURSOR
901void tst_QPlainTextEdit::mouseCursorShape()
902{
903 // always show an IBeamCursor, see change 170146
904 QVERIFY(!ed->isReadOnly());
905 QCOMPARE(ed->viewport()->cursor().shape(), Qt::IBeamCursor);
906
907 ed->setReadOnly(true);
908 QCOMPARE(ed->viewport()->cursor().shape(), Qt::IBeamCursor);
909
910 ed->setPlainText("Foo");
911 QCOMPARE(ed->viewport()->cursor().shape(), Qt::IBeamCursor);
912}
913#endif
914
915void tst_QPlainTextEdit::implicitClear()
916{
917 // test that QPlainTextEdit::setHtml, etc. avoid calling clear() but instead call
918 // QTextDocument::setHtml/etc. instead, which also clear the contents and
919 // cached resource but preserve manually added resources. setHtml on a textedit
920 // should behave the same as on a document with respect to that.
921 // see also clearResources() autotest in qtextdocument
922
923 // regular resource for QTextDocument
924 QUrl testUrl(":/foobar");
925 QVariant testResource("hello world");
926
927 ed->document()->addResource(type: QTextDocument::ImageResource, name: testUrl, resource: testResource);
928 QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);
929
930 ed->setPlainText("Blah");
931 QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);
932
933 ed->setPlainText("<b>Blah</b>");
934 QVERIFY(ed->document()->resource(QTextDocument::ImageResource, testUrl) == testResource);
935
936 ed->clear();
937 QVERIFY(!ed->document()->resource(QTextDocument::ImageResource, testUrl).isValid());
938 QVERIFY(ed->toPlainText().isEmpty());
939}
940
941#ifndef QT_NO_CLIPBOARD
942void tst_QPlainTextEdit::copyAvailable_data()
943{
944 QTest::addColumn<pairListType>(name: "keystrokes");
945 QTest::addColumn<QList<bool> >(name: "copyAvailable");
946 QTest::addColumn<QString>(name: "function");
947
948 pairListType keystrokes;
949 QList<bool> copyAvailable;
950
951 keystrokes << qMakePair(x: Qt::Key_B, y: Qt::NoModifier) << qMakePair(x: Qt::Key_B, y: Qt::NoModifier)
952 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier);
953 copyAvailable << true ;
954 QTest::newRow(dataTag: QString("Case1 B,B, <- + shift | signals: true").toLatin1())
955 << keystrokes << copyAvailable << QString();
956
957 keystrokes.clear();
958 copyAvailable.clear();
959
960 keystrokes << qMakePair(x: Qt::Key_T, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
961 << qMakePair(x: Qt::Key_A, y: Qt::NoModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier);
962 copyAvailable << true << false;
963 QTest::newRow(dataTag: QString("Case2 T,A,A, <- + shift, cut() | signals: true, false").toLatin1())
964 << keystrokes << copyAvailable << QString("cut");
965
966 keystrokes.clear();
967 copyAvailable.clear();
968
969 keystrokes << qMakePair(x: Qt::Key_T, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
970 << qMakePair(x: Qt::Key_A, y: Qt::NoModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
971 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier);
972 copyAvailable << true;
973 QTest::newRow(dataTag: QString("Case3 T,A,A, <- + shift, <- + shift, <- + shift, copy() | signals: true").toLatin1())
974 << keystrokes << copyAvailable << QString("copy");
975
976 keystrokes.clear();
977 copyAvailable.clear();
978
979 keystrokes << qMakePair(x: Qt::Key_T, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
980 << qMakePair(x: Qt::Key_A, y: Qt::NoModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
981 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
982 << qMakePair(x: Qt::Key_X, y: Qt::ControlModifier);
983 copyAvailable << true << false;
984 QTest::newRow(dataTag: QString("Case4 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, paste() | signals: true, false").toLatin1())
985 << keystrokes << copyAvailable << QString("paste");
986
987 keystrokes.clear();
988 copyAvailable.clear();
989
990 keystrokes << qMakePair(x: Qt::Key_B, y: Qt::NoModifier) << qMakePair(x: Qt::Key_B, y: Qt::NoModifier)
991 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::NoModifier);
992 copyAvailable << true << false;
993 QTest::newRow(dataTag: QString("Case5 B,B, <- + shift, <- | signals: true, false").toLatin1())
994 << keystrokes << copyAvailable << QString();
995
996 keystrokes.clear();
997 copyAvailable.clear();
998
999 keystrokes << qMakePair(x: Qt::Key_B, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
1000 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::NoModifier)
1001 << qMakePair(x: Qt::Key_Right, y: Qt::ShiftModifier);
1002 copyAvailable << true << false << true << false;
1003 QTest::newRow(dataTag: QString("Case6 B,A, <- + shift, ->, <- + shift | signals: true, false, true, false").toLatin1())
1004 << keystrokes << copyAvailable << QString("cut");
1005
1006 keystrokes.clear();
1007 copyAvailable.clear();
1008
1009 keystrokes << qMakePair(x: Qt::Key_T, y: Qt::NoModifier) << qMakePair(x: Qt::Key_A, y: Qt::NoModifier)
1010 << qMakePair(x: Qt::Key_A, y: Qt::NoModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
1011 << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier) << qMakePair(x: Qt::Key_Left, y: Qt::ShiftModifier)
1012 << qMakePair(x: Qt::Key_X, y: Qt::ControlModifier);
1013 copyAvailable << true << false << true;
1014 QTest::newRow(dataTag: QString("Case7 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, undo() | signals: true, false, true").toLatin1())
1015 << keystrokes << copyAvailable << QString("undo");
1016}
1017
1018//Tests the copyAvailable slot for several cases
1019void tst_QPlainTextEdit::copyAvailable()
1020{
1021 QFETCH(pairListType,keystrokes);
1022 QFETCH(QList<bool>, copyAvailable);
1023 QFETCH(QString, function);
1024
1025#ifdef Q_OS_MAC
1026 QSKIP("QTBUG-22283: copyAvailable has never passed on Mac");
1027#endif
1028 ed->clear();
1029 QApplication::clipboard()->clear();
1030 QVERIFY(!ed->canPaste());
1031 QSignalSpy spyCopyAvailabe(ed, SIGNAL(copyAvailable(bool)));
1032
1033 //Execute Keystrokes
1034 foreach(keyPairType keyPair, keystrokes) {
1035 QTest::keyClick(widget: ed, key: keyPair.first, modifier: keyPair.second );
1036 }
1037
1038 //Execute ed->"function"
1039 if (function == "cut")
1040 ed->cut();
1041 else if (function == "copy")
1042 ed->copy();
1043 else if (function == "paste")
1044 ed->paste();
1045 else if (function == "undo")
1046 ed->paste();
1047 else if (function == "redo")
1048 ed->paste();
1049
1050 //Compare spied signals
1051 QEXPECT_FAIL("Case7 T,A,A, <- + shift, <- + shift, <- + shift, ctrl + x, undo() | signals: true, false, true",
1052 "Wrong undo selection behaviour. Should be fixed in some future release. (See task: 132482)", Abort);
1053 QCOMPARE(spyCopyAvailabe.count(), copyAvailable.count());
1054 for (int i=0;i<spyCopyAvailabe.count(); i++) {
1055 QVariant variantSpyCopyAvailable = spyCopyAvailabe.at(i).at(i: 0);
1056 QVERIFY2(variantSpyCopyAvailable.toBool() == copyAvailable.at(i), QString("Spied singnal: %1").arg(i).toLatin1());
1057 }
1058}
1059#endif
1060
1061void tst_QPlainTextEdit::undoRedoAfterSetContent()
1062{
1063 QVERIFY(!ed->document()->isUndoAvailable());
1064 QVERIFY(!ed->document()->isRedoAvailable());
1065 ed->setPlainText("Foobar");
1066 QVERIFY(!ed->document()->isUndoAvailable());
1067 QVERIFY(!ed->document()->isRedoAvailable());
1068 ed->setPlainText("<p>bleh</p>");
1069 QVERIFY(!ed->document()->isUndoAvailable());
1070 QVERIFY(!ed->document()->isRedoAvailable());
1071}
1072
1073void tst_QPlainTextEdit::numPadKeyNavigation()
1074{
1075 ed->setPlainText("Hello World");
1076 QCOMPARE(ed->textCursor().position(), 0);
1077 QTest::keyClick(widget: ed, key: Qt::Key_Right, modifier: Qt::KeypadModifier);
1078 QCOMPARE(ed->textCursor().position(), 1);
1079}
1080
1081void tst_QPlainTextEdit::moveCursor()
1082{
1083 ed->setPlainText("Test");
1084
1085 QSignalSpy cursorMovedSpy(ed, SIGNAL(cursorPositionChanged()));
1086
1087 QCOMPARE(ed->textCursor().position(), 0);
1088 ed->moveCursor(operation: QTextCursor::NextCharacter);
1089 QCOMPARE(ed->textCursor().position(), 1);
1090 QCOMPARE(cursorMovedSpy.count(), 1);
1091 ed->moveCursor(operation: QTextCursor::NextCharacter, mode: QTextCursor::KeepAnchor);
1092 QCOMPARE(ed->textCursor().position(), 2);
1093 QCOMPARE(cursorMovedSpy.count(), 2);
1094 QCOMPARE(ed->textCursor().selectedText(), QString("e"));
1095}
1096
1097class MyTextEdit : public QPlainTextEdit
1098{
1099public:
1100 inline MyTextEdit()
1101 : createMimeDataCallCount(0),
1102 canInsertCallCount(0),
1103 insertCallCount(0)
1104 {}
1105
1106 mutable int createMimeDataCallCount;
1107 mutable int canInsertCallCount;
1108 mutable int insertCallCount;
1109
1110 virtual QMimeData *createMimeDataFromSelection() const {
1111 createMimeDataCallCount++;
1112 return QPlainTextEdit::createMimeDataFromSelection();
1113 }
1114 virtual bool canInsertFromMimeData(const QMimeData *source) const {
1115 canInsertCallCount++;
1116 return QPlainTextEdit::canInsertFromMimeData(source);
1117 }
1118 virtual void insertFromMimeData(const QMimeData *source) {
1119 insertCallCount++;
1120 QPlainTextEdit::insertFromMimeData(source);
1121 }
1122
1123};
1124
1125#ifndef QT_NO_CLIPBOARD
1126void tst_QPlainTextEdit::mimeDataReimplementations()
1127{
1128 MyTextEdit ed;
1129 ed.setPlainText("Hello World");
1130
1131 QCOMPARE(ed.createMimeDataCallCount, 0);
1132 QCOMPARE(ed.canInsertCallCount, 0);
1133 QCOMPARE(ed.insertCallCount, 0);
1134
1135 ed.selectAll();
1136
1137 QCOMPARE(ed.createMimeDataCallCount, 0);
1138 QCOMPARE(ed.canInsertCallCount, 0);
1139 QCOMPARE(ed.insertCallCount, 0);
1140
1141 ed.copy();
1142
1143 QCOMPARE(ed.createMimeDataCallCount, 1);
1144 QCOMPARE(ed.canInsertCallCount, 0);
1145 QCOMPARE(ed.insertCallCount, 0);
1146
1147#ifdef QT_BUILD_INTERNAL
1148 QWidgetTextControl *control = ed.findChild<QWidgetTextControl *>();
1149 QVERIFY(control);
1150
1151 control->canInsertFromMimeData(source: QApplication::clipboard()->mimeData());
1152
1153 QCOMPARE(ed.createMimeDataCallCount, 1);
1154 QCOMPARE(ed.canInsertCallCount, 1);
1155 QCOMPARE(ed.insertCallCount, 0);
1156
1157 ed.paste();
1158
1159 QCOMPARE(ed.createMimeDataCallCount, 1);
1160 QCOMPARE(ed.canInsertCallCount, 1);
1161 QCOMPARE(ed.insertCallCount, 1);
1162#endif
1163}
1164#endif
1165
1166void tst_QPlainTextEdit::shiftEnterShouldInsertLineSeparator()
1167{
1168 QTest::keyClick(widget: ed, key: Qt::Key_A);
1169 QTest::keyClick(widget: ed, key: Qt::Key_Enter, modifier: Qt::ShiftModifier);
1170 QTest::keyClick(widget: ed, key: Qt::Key_B);
1171 QString expected;
1172 expected += 'a';
1173 expected += QChar::LineSeparator;
1174 expected += 'b';
1175 QCOMPARE(ed->textCursor().block().text(), expected);
1176}
1177
1178void tst_QPlainTextEdit::selectWordsFromStringsContainingSeparators_data()
1179{
1180 QTest::addColumn<QString>(name: "testString");
1181 QTest::addColumn<QString>(name: "selectedWord");
1182
1183 const ushort wordSeparators[] =
1184 {'.', ',', '?', '!', ':', ';', '-', '<', '>', '[', ']', '(', ')', '{', '}',
1185 '=', '\t', ushort(QChar::Nbsp)};
1186
1187 for (size_t i = 0, count = sizeof(wordSeparators) / sizeof(wordSeparators[0]); i < count; ++i) {
1188 const ushort u = wordSeparators[i];
1189 QByteArray rowName = QByteArrayLiteral("separator: ");
1190 if (u >= 32 && u < 128)
1191 rowName += char(u);
1192 else
1193 rowName += QByteArrayLiteral("0x") + QByteArray::number(u, base: 16);
1194 QTest::newRow(dataTag: rowName.constData()) << QString("foo") + QChar(u) + QString("bar") << QString("foo");
1195 }
1196}
1197
1198void tst_QPlainTextEdit::selectWordsFromStringsContainingSeparators()
1199{
1200 QFETCH(QString, testString);
1201 QFETCH(QString, selectedWord);
1202 ed->setPlainText(testString);
1203 QTextCursor cursor = ed->textCursor();
1204 cursor.movePosition(op: QTextCursor::StartOfLine);
1205 cursor.select(selection: QTextCursor::WordUnderCursor);
1206 QVERIFY(cursor.hasSelection());
1207 QCOMPARE(cursor.selection().toPlainText(), selectedWord);
1208 cursor.clearSelection();
1209}
1210
1211#ifndef QT_NO_CLIPBOARD
1212void tst_QPlainTextEdit::canPaste()
1213{
1214 if (!PlatformClipboard::isAvailable())
1215 QSKIP("Clipboard not working with cron-started unit tests");
1216
1217 if (QGuiApplication::platformName().startsWith(s: QLatin1String("wayland"), cs: Qt::CaseInsensitive))
1218 QSKIP("Wayland: This fails. Figure out why.");
1219
1220 QApplication::clipboard()->setText(QString());
1221 QVERIFY(!ed->canPaste());
1222 QApplication::clipboard()->setText("Test");
1223 QVERIFY(ed->canPaste());
1224 ed->setTextInteractionFlags(Qt::NoTextInteraction);
1225 QVERIFY(!ed->canPaste());
1226}
1227#endif
1228
1229void tst_QPlainTextEdit::ensureCursorVisibleOnInitialShow()
1230{
1231 QString manyPagesOfPlainText;
1232 for (int i = 0; i < 800; ++i)
1233 manyPagesOfPlainText += QLatin1String("Blah blah blah blah blah blah\n");
1234
1235 ed->setPlainText(manyPagesOfPlainText);
1236 QCOMPARE(ed->textCursor().position(), 0);
1237
1238 ed->moveCursor(operation: QTextCursor::End);
1239 ed->show();
1240 QVERIFY(ed->verticalScrollBar()->value() > 10);
1241
1242 ed->moveCursor(operation: QTextCursor::Start);
1243 QVERIFY(ed->verticalScrollBar()->value() < 10);
1244 ed->hide();
1245 ed->verticalScrollBar()->setValue(ed->verticalScrollBar()->maximum());
1246 ed->show();
1247 QCOMPARE(ed->verticalScrollBar()->value(), ed->verticalScrollBar()->maximum());
1248}
1249
1250class TestEdit : public QPlainTextEdit
1251{
1252public:
1253 TestEdit() : resizeEventCalled(false) {}
1254
1255 bool resizeEventCalled;
1256
1257protected:
1258 virtual void resizeEvent(QResizeEvent *e)
1259 {
1260 QPlainTextEdit::resizeEvent(e);
1261 setPlainText("<img src=qtextbrowser-resizeevent.png width=" + QString::number(size().width()) + "><br>Size is " + QString::number(size().width()) + " x " + QString::number(size().height()));
1262 resizeEventCalled = true;
1263 }
1264};
1265
1266void tst_QPlainTextEdit::setTextInsideResizeEvent()
1267{
1268 TestEdit edit;
1269 edit.show();
1270 edit.resize(w: 800, h: 600);
1271 QVERIFY(edit.resizeEventCalled);
1272}
1273
1274void tst_QPlainTextEdit::colorfulAppend()
1275{
1276 QTextCharFormat fmt;
1277
1278 fmt.setForeground(QBrush(Qt::red));
1279 ed->mergeCurrentCharFormat(modifier: fmt);
1280 ed->appendPlainText(text: "Red");
1281 fmt.setForeground(QBrush(Qt::blue));
1282 ed->mergeCurrentCharFormat(modifier: fmt);
1283 ed->appendPlainText(text: "Blue");
1284 fmt.setForeground(QBrush(Qt::green));
1285 ed->mergeCurrentCharFormat(modifier: fmt);
1286 ed->appendPlainText(text: "Green");
1287
1288 QCOMPARE(ed->document()->blockCount(), 3);
1289 QTextBlock block = ed->document()->begin();
1290 QCOMPARE(block.begin().fragment().text(), QString("Red"));
1291 QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::red);
1292 block = block.next();
1293 QCOMPARE(block.begin().fragment().text(), QString("Blue"));
1294 QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::blue);
1295 block = block.next();
1296 QCOMPARE(block.begin().fragment().text(), QString("Green"));
1297 QVERIFY(block.begin().fragment().charFormat().foreground().color() == Qt::green);
1298}
1299
1300void tst_QPlainTextEdit::ensureVisibleWithRtl()
1301{
1302 ed->setLayoutDirection(Qt::RightToLeft);
1303 ed->setLineWrapMode(QPlainTextEdit::NoWrap);
1304 QString txt(500, QChar(QLatin1Char('a')));
1305 QCOMPARE(txt.length(), 500);
1306 ed->setPlainText(txt);
1307 ed->resize(w: 100, h: 100);
1308 ed->show();
1309
1310 qApp->processEvents();
1311
1312 QVERIFY(ed->horizontalScrollBar()->maximum() > 0);
1313
1314 ed->moveCursor(operation: QTextCursor::Start);
1315 QCOMPARE(ed->horizontalScrollBar()->value(), ed->horizontalScrollBar()->maximum());
1316 ed->moveCursor(operation: QTextCursor::End);
1317 QCOMPARE(ed->horizontalScrollBar()->value(), 0);
1318 ed->moveCursor(operation: QTextCursor::Start);
1319 QCOMPARE(ed->horizontalScrollBar()->value(), ed->horizontalScrollBar()->maximum());
1320 ed->moveCursor(operation: QTextCursor::End);
1321 QCOMPARE(ed->horizontalScrollBar()->value(), 0);
1322}
1323
1324void tst_QPlainTextEdit::preserveCharFormatAfterSetPlainText()
1325{
1326 QTextCharFormat fmt;
1327 fmt.setForeground(QBrush(Qt::blue));
1328 ed->mergeCurrentCharFormat(modifier: fmt);
1329 ed->setPlainText("This is blue");
1330 ed->appendPlainText(text: "This should still be blue");
1331 QTextBlock block = ed->document()->begin();
1332 block = block.next();
1333 QCOMPARE(block.text(), QString("This should still be blue"));
1334 QCOMPARE(block.begin().fragment().charFormat().foreground().color(), QColor(Qt::blue));
1335}
1336
1337void tst_QPlainTextEdit::extraSelections()
1338{
1339 ed->setPlainText("Hello World");
1340
1341 QTextCursor c = ed->textCursor();
1342 c.movePosition(op: QTextCursor::Start);
1343 c.movePosition(op: QTextCursor::End, QTextCursor::KeepAnchor);
1344 const int endPos = c.position();
1345
1346 QTextEdit::ExtraSelection sel;
1347 sel.cursor = c;
1348 ed->setExtraSelections(QList<QTextEdit::ExtraSelection>() << sel);
1349
1350 c.movePosition(op: QTextCursor::Start);
1351 c.movePosition(op: QTextCursor::NextWord);
1352 const int wordPos = c.position();
1353 c.movePosition(op: QTextCursor::End, QTextCursor::KeepAnchor);
1354 sel.cursor = c;
1355 ed->setExtraSelections(QList<QTextEdit::ExtraSelection>() << sel);
1356
1357 QList<QTextEdit::ExtraSelection> selections = ed->extraSelections();
1358 QCOMPARE(selections.count(), 1);
1359 QCOMPARE(selections.at(0).cursor.position(), endPos);
1360 QCOMPARE(selections.at(0).cursor.anchor(), wordPos);
1361}
1362
1363void tst_QPlainTextEdit::adjustScrollbars()
1364{
1365// For some reason ff is defined to be << on Mac Panther / gcc 3.3
1366#undef ff
1367 QFont ff(ed->font());
1368 ff.setFamily("Tahoma");
1369 ff.setPointSize(11);
1370 ed->setFont(ff);
1371 ed->setMinimumSize(minw: 140, minh: 100);
1372 ed->setMaximumSize(maxw: 140, maxh: 100);
1373 ed->show();
1374 QLatin1String txt("\nabc def ghi jkl mno pqr stu vwx");
1375 ed->setPlainText(txt + txt + txt + txt);
1376
1377#ifdef Q_OS_WINRT
1378 QEXPECT_FAIL("", "WinRT does not support setMinimum/MaximumSize", Abort);
1379#endif
1380 QVERIFY(ed->verticalScrollBar()->maximum() > 0);
1381
1382 ed->moveCursor(operation: QTextCursor::End);
1383 int oldMaximum = ed->verticalScrollBar()->maximum();
1384 QTextCursor cursor = ed->textCursor();
1385 cursor.insertText(text: QLatin1String("\n"));
1386 cursor.deletePreviousChar();
1387 QCOMPARE(ed->verticalScrollBar()->maximum(), oldMaximum);
1388}
1389
1390class SignalReceiver : public QObject
1391{
1392 Q_OBJECT
1393public:
1394 SignalReceiver() : received(0) {}
1395
1396 int receivedSignals() const { return received; }
1397 QTextCharFormat charFormat() const { return format; }
1398
1399public slots:
1400 void charFormatChanged(const QTextCharFormat &tcf) { ++received; format = tcf; }
1401
1402private:
1403 QTextCharFormat format;
1404 int received;
1405};
1406
1407void tst_QPlainTextEdit::textObscuredByScrollbars()
1408{
1409 ed->textCursor().insertText(
1410 text: "ab cab cab c abca kjsdf lka sjd lfk jsal df j kasdf abc ab abc "
1411 "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 "
1412 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1413 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1414 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1415 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1416 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1417 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1418 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1419 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1420 "abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc abc "
1421 "ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab ab"
1422 );
1423 ed->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
1424 ed->show();
1425
1426 QSize documentSize = ed->document()->documentLayout()->documentSize().toSize();
1427 QSize viewportSize = ed->viewport()->size();
1428
1429 QVERIFY(documentSize.width() <= viewportSize.width());
1430}
1431
1432void tst_QPlainTextEdit::setTextPreservesUndoRedoEnabled()
1433{
1434 QVERIFY(ed->isUndoRedoEnabled());
1435
1436 ed->setPlainText("Test");
1437
1438 QVERIFY(ed->isUndoRedoEnabled());
1439
1440 ed->setUndoRedoEnabled(false);
1441 QVERIFY(!ed->isUndoRedoEnabled());
1442 ed->setPlainText("Test2");
1443 QVERIFY(!ed->isUndoRedoEnabled());
1444
1445 ed->setPlainText("<p>hello");
1446 QVERIFY(!ed->isUndoRedoEnabled());
1447}
1448
1449void tst_QPlainTextEdit::wordWrapProperty()
1450{
1451 {
1452 QPlainTextEdit edit;
1453 QTextDocument *doc = new QTextDocument(&edit);
1454 doc->setDocumentLayout(new QPlainTextDocumentLayout(doc));
1455 edit.setDocument(doc);
1456 edit.setWordWrapMode(QTextOption::NoWrap);
1457 QCOMPARE(doc->defaultTextOption().wrapMode(), QTextOption::NoWrap);
1458 }
1459 {
1460 QPlainTextEdit edit;
1461 QTextDocument *doc = new QTextDocument(&edit);
1462 doc->setDocumentLayout(new QPlainTextDocumentLayout(doc));
1463 edit.setWordWrapMode(QTextOption::NoWrap);
1464 edit.setDocument(doc);
1465 QCOMPARE(doc->defaultTextOption().wrapMode(), QTextOption::NoWrap);
1466 }
1467}
1468
1469void tst_QPlainTextEdit::lineWrapProperty()
1470{
1471 QCOMPARE(ed->wordWrapMode(), QTextOption::WrapAtWordBoundaryOrAnywhere);
1472 QCOMPARE(ed->lineWrapMode(), QPlainTextEdit::WidgetWidth);
1473 ed->setLineWrapMode(QPlainTextEdit::NoWrap);
1474 QCOMPARE(ed->lineWrapMode(), QPlainTextEdit::NoWrap);
1475 QCOMPARE(ed->wordWrapMode(), QTextOption::WrapAtWordBoundaryOrAnywhere);
1476 QCOMPARE(ed->document()->defaultTextOption().wrapMode(), QTextOption::NoWrap);
1477}
1478
1479void tst_QPlainTextEdit::selectionChanged()
1480{
1481 ed->setPlainText("Hello World");
1482
1483 ed->moveCursor(operation: QTextCursor::Start);
1484
1485 QSignalSpy selectionChangedSpy(ed, SIGNAL(selectionChanged()));
1486
1487 QTest::keyClick(widget: ed, key: Qt::Key_Right);
1488 QCOMPARE(ed->textCursor().position(), 1);
1489 QCOMPARE(selectionChangedSpy.count(), 0);
1490
1491 QTest::keyClick(widget: ed, key: Qt::Key_Right, modifier: Qt::ShiftModifier);
1492 QCOMPARE(ed->textCursor().position(), 2);
1493 QCOMPARE(selectionChangedSpy.count(), 1);
1494
1495 QTest::keyClick(widget: ed, key: Qt::Key_Right, modifier: Qt::ShiftModifier);
1496 QCOMPARE(ed->textCursor().position(), 3);
1497 QCOMPARE(selectionChangedSpy.count(), 2);
1498
1499 QTest::keyClick(widget: ed, key: Qt::Key_Right, modifier: Qt::ShiftModifier);
1500 QCOMPARE(ed->textCursor().position(), 4);
1501 QCOMPARE(selectionChangedSpy.count(), 3);
1502
1503 QTest::keyClick(widget: ed, key: Qt::Key_Right);
1504 QCOMPARE(ed->textCursor().position(), 4);
1505 QCOMPARE(selectionChangedSpy.count(), 4);
1506
1507 QTest::keyClick(widget: ed, key: Qt::Key_Right);
1508 QCOMPARE(ed->textCursor().position(), 5);
1509 QCOMPARE(selectionChangedSpy.count(), 4);
1510}
1511
1512void tst_QPlainTextEdit::blockCountChanged()
1513{
1514 QSignalSpy blockCountCpangedSpy(ed, SIGNAL(blockCountChanged(int)));
1515 ed->setPlainText("Hello");
1516 QCOMPARE(blockCountCpangedSpy.count(), 0);
1517 ed->setPlainText("Hello World");
1518 QCOMPARE(blockCountCpangedSpy.count(), 0);
1519 ed->setPlainText("Hello \n World \n this \n has \n more \n blocks \n than \n just \n one");
1520 QCOMPARE(blockCountCpangedSpy.count(), 1);
1521 ed->setPlainText("One");
1522 QCOMPARE(blockCountCpangedSpy.count(), 2);
1523 ed->setPlainText("One \n Two");
1524 QCOMPARE(blockCountCpangedSpy.count(), 3);
1525 ed->setPlainText("Three \n Four");
1526 QCOMPARE(blockCountCpangedSpy.count(), 3);
1527}
1528
1529
1530void tst_QPlainTextEdit::insertAndScrollToBottom()
1531{
1532 ed->setPlainText("First Line");
1533 ed->show();
1534 QString text;
1535 for(int i = 0; i < 2000; ++i) {
1536 text += QLatin1String("this is another line of text to be appended. It is quite long and will probably wrap around, meaning the number of lines is larger than the number of blocks in the text.\n");
1537 }
1538 QTextCursor cursor = ed->textCursor();
1539 cursor.beginEditBlock();
1540 cursor.insertText(text);
1541 cursor.endEditBlock();
1542 ed->verticalScrollBar()->setValue(ed->verticalScrollBar()->maximum());
1543 QCOMPARE(ed->verticalScrollBar()->value(), ed->verticalScrollBar()->maximum());
1544}
1545
1546Q_DECLARE_METATYPE(Qt::InputMethodHints)
1547void tst_QPlainTextEdit::inputMethodQueryImHints_data()
1548{
1549 QTest::addColumn<Qt::InputMethodHints>(name: "hints");
1550
1551 QTest::newRow(dataTag: "None") << static_cast<Qt::InputMethodHints>(Qt::ImhNone);
1552 QTest::newRow(dataTag: "Password") << static_cast<Qt::InputMethodHints>(Qt::ImhHiddenText);
1553 QTest::newRow(dataTag: "Normal") << static_cast<Qt::InputMethodHints>(Qt::ImhNoAutoUppercase | Qt::ImhNoPredictiveText | Qt::ImhSensitiveData);
1554}
1555
1556void tst_QPlainTextEdit::inputMethodQueryImHints()
1557{
1558 QFETCH(Qt::InputMethodHints, hints);
1559 ed->setInputMethodHints(hints);
1560
1561 QVariant value = ed->inputMethodQuery(property: Qt::ImHints);
1562 QCOMPARE(static_cast<Qt::InputMethodHints>(value.toInt()), hints);
1563}
1564
1565#ifndef QT_NO_REGEXP
1566void tst_QPlainTextEdit::findWithRegExp()
1567{
1568 ed->setPlainText(QStringLiteral("arbitrary text"));
1569 QRegExp rx("\\w{2}xt");
1570
1571 bool found = ed->find(exp: rx);
1572
1573 QVERIFY(found);
1574 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("text"));
1575}
1576
1577void tst_QPlainTextEdit::findBackwardWithRegExp()
1578{
1579 ed->setPlainText(QStringLiteral("arbitrary text"));
1580 QTextCursor cursor = ed->textCursor();
1581 cursor.movePosition(op: QTextCursor::End);
1582 ed->setTextCursor(cursor);
1583 QRegExp rx("a\\w*t");
1584
1585 bool found = ed->find(exp: rx, options: QTextDocument::FindBackward);
1586
1587 QVERIFY(found);
1588 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("arbit"));
1589}
1590
1591void tst_QPlainTextEdit::findWithRegExpReturnsFalseIfNoMoreResults()
1592{
1593 ed->setPlainText(QStringLiteral("arbitrary text"));
1594 QRegExp rx("t.xt");
1595 ed->find(exp: rx);
1596
1597 bool found = ed->find(exp: rx);
1598
1599 QVERIFY(!found);
1600 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("text"));
1601}
1602#endif
1603
1604#if QT_CONFIG(regularexpression)
1605void tst_QPlainTextEdit::findWithRegularExpression()
1606{
1607 ed->setPlainText(QStringLiteral("arbitrary text"));
1608 QRegularExpression rx("\\w{2}xt");
1609
1610 bool found = ed->find(exp: rx);
1611
1612 QVERIFY(found);
1613 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("text"));
1614}
1615
1616void tst_QPlainTextEdit::findBackwardWithRegularExpression()
1617{
1618 ed->setPlainText(QStringLiteral("arbitrary text"));
1619 QTextCursor cursor = ed->textCursor();
1620 cursor.movePosition(op: QTextCursor::End);
1621 ed->setTextCursor(cursor);
1622 QRegularExpression rx("a\\w*t");
1623
1624 bool found = ed->find(exp: rx, options: QTextDocument::FindBackward);
1625
1626 QVERIFY(found);
1627 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("arbit"));
1628}
1629
1630void tst_QPlainTextEdit::findWithRegularExpressionReturnsFalseIfNoMoreResults()
1631{
1632 ed->setPlainText(QStringLiteral("arbitrary text"));
1633 QRegularExpression rx("t.xt");
1634 ed->find(exp: rx);
1635
1636 bool found = ed->find(exp: rx);
1637
1638 QVERIFY(!found);
1639 QCOMPARE(ed->textCursor().selectedText(), QStringLiteral("text"));
1640}
1641#endif
1642
1643void tst_QPlainTextEdit::layoutAfterMultiLineRemove()
1644{
1645 ed->setVisible(true); // The widget must be visible to reproduce this bug.
1646
1647 QString contents;
1648 for (int i = 0; i < 5; ++i)
1649 contents.append(s: "\ttest\n");
1650
1651 ed->setPlainText(contents);
1652
1653 /*
1654 * Remove the tab from the beginning of lines 2-4, in an edit block. The
1655 * edit block is required for the bug to be reproduced.
1656 */
1657
1658 QTextCursor curs = ed->textCursor();
1659 curs.movePosition(op: QTextCursor::Start);
1660 curs.movePosition(op: QTextCursor::NextBlock);
1661
1662 curs.beginEditBlock();
1663 for (int i = 0; i < 3; ++i) {
1664 curs.deleteChar();
1665 curs.movePosition(op: QTextCursor::NextBlock);
1666 }
1667 curs.endEditBlock();
1668
1669 /*
1670 * Now, we're going to perform the following actions:
1671 *
1672 * - Move to the beginning of the document.
1673 * - Move down three times - this should put us at the front of block 3.
1674 * - Move to the end of the line.
1675 *
1676 * At this point, if the document layout is behaving correctly, we should
1677 * still be positioned on block 3. Verify that this is the case.
1678 */
1679
1680 curs.movePosition(op: QTextCursor::Start);
1681 curs.movePosition(op: QTextCursor::Down, QTextCursor::MoveAnchor, n: 3);
1682 curs.movePosition(op: QTextCursor::EndOfLine);
1683
1684 QCOMPARE(curs.blockNumber(), 3);
1685}
1686
1687void tst_QPlainTextEdit::undoCommandRemovesAndReinsertsBlock()
1688{
1689 ed->setVisible(true);
1690 ed->setPlainText(QStringLiteral("line1\nline2"));
1691 QCOMPARE(ed->document()->blockCount(), 2);
1692
1693 QTextCursor cursor = ed->textCursor();
1694 cursor.movePosition(op: QTextCursor::Start);
1695 cursor.movePosition(op: QTextCursor::NextBlock, QTextCursor::KeepAnchor);
1696 cursor.insertText(QStringLiteral("\n"));
1697 QCOMPARE(ed->document()->blockCount(), 2);
1698
1699 ed->undo();
1700 QCOMPARE(ed->document()->blockCount(), 2);
1701
1702 QTextBlock block;
1703 for (block = ed->document()->begin(); block != ed->document()->end(); block = block.next()) {
1704 QVERIFY(block.isValid());
1705 QCOMPARE(block.length(), 6);
1706 QVERIFY(block.layout()->lineForTextPosition(0).isValid());
1707 }
1708
1709}
1710
1711class ContentsChangedFunctor {
1712public:
1713 ContentsChangedFunctor(QPlainTextEdit *t) : textEdit(t) {}
1714 void operator()(int, int, int)
1715 {
1716 QTextCursor c(textEdit->textCursor());
1717 c.beginEditBlock();
1718 c.movePosition(op: QTextCursor::Start);
1719 c.movePosition(op: QTextCursor::End, QTextCursor::KeepAnchor);
1720 c.setCharFormat(QTextCharFormat());
1721 c.endEditBlock();
1722 }
1723
1724private:
1725 QPlainTextEdit *textEdit;
1726};
1727
1728void tst_QPlainTextEdit::taskQTBUG_43562_lineCountCrash()
1729{
1730 connect(sender: ed->document(), signal: &QTextDocument::contentsChange, slot: ContentsChangedFunctor(ed));
1731 // Don't crash
1732 QTest::keyClicks(widget: ed, sequence: "Some text");
1733 QTest::keyClick(widget: ed, key: Qt::Key_Left);
1734 QTest::keyClick(widget: ed, key: Qt::Key_Right);
1735 QTest::keyClick(widget: ed, key: Qt::Key_A);
1736 QTest::keyClick(widget: ed, key: Qt::Key_Left);
1737 QTest::keyClick(widget: ed, key: Qt::Key_Right);
1738 QTest::keyClick(widget: ed, key: Qt::Key_Space);
1739 QTest::keyClicks(widget: ed, sequence: "nd some more");
1740 disconnect(sender: ed->document(), SIGNAL(contentsChange(int, int, int)), receiver: 0, member: 0);
1741}
1742
1743#if !defined(QT_NO_CONTEXTMENU) && !defined(QT_NO_CLIPBOARD)
1744void tst_QPlainTextEdit::contextMenu()
1745{
1746 ed->appendHtml(QStringLiteral("Hello <a href='http://www.qt.io'>Qt</a>"));
1747
1748 QMenu *menu = ed->createStandardContextMenu();
1749 QVERIFY(menu);
1750 QAction *action = ed->findChild<QAction *>(QStringLiteral("link-copy"));
1751 QVERIFY(!action);
1752 delete menu;
1753 QVERIFY(!ed->findChild<QAction *>(QStringLiteral("link-copy")));
1754
1755 ed->setTextInteractionFlags(Qt::TextBrowserInteraction);
1756
1757 menu = ed->createStandardContextMenu();
1758 QVERIFY(menu);
1759 action = ed->findChild<QAction *>(QStringLiteral("link-copy"));
1760 QVERIFY(action);
1761 QVERIFY(!action->isEnabled());
1762 delete menu;
1763 QVERIFY(!ed->findChild<QAction *>(QStringLiteral("link-copy")));
1764
1765 QTextCursor cursor = ed->textCursor();
1766 cursor.setPosition(pos: ed->toPlainText().length() - 2);
1767 ed->setTextCursor(cursor);
1768
1769 menu = ed->createStandardContextMenu(position: ed->cursorRect().center());
1770 QVERIFY(menu);
1771 action = ed->findChild<QAction *>(QStringLiteral("link-copy"));
1772 QVERIFY(action);
1773 QVERIFY(action->isEnabled());
1774 delete menu;
1775 QVERIFY(!ed->findChild<QAction *>(QStringLiteral("link-copy")));
1776}
1777#endif // QT_NO_CONTEXTMENU && QT_NO_CLIPBOARD
1778
1779// QTBUG-51923: Verify that the cursor rectangle returned by the input
1780// method query correctly reflects the viewport offset.
1781void tst_QPlainTextEdit::inputMethodCursorRect()
1782{
1783 ed->setPlainText("Line1\nLine2Line3\nLine3");
1784 ed->moveCursor(operation: QTextCursor::End);
1785 const QRectF cursorRect = ed->cursorRect();
1786 const QVariant cursorRectV = ed->inputMethodQuery(property: Qt::ImCursorRectangle);
1787 QCOMPARE(cursorRectV.type(), QVariant::RectF);
1788 QCOMPARE(cursorRectV.toRect(), cursorRect.toRect());
1789}
1790
1791#if QT_CONFIG(scrollbar)
1792// QTBUG-64730: Verify that the scrollbar is updated after center on scroll was set
1793void tst_QPlainTextEdit::updateAfterChangeCenterOnScroll()
1794{
1795 ed->setPlainText("Line1\nLine2Line3\nLine3");
1796 ed->show();
1797 ed->setCenterOnScroll(true);
1798 const int maxWithCenterOnScroll = ed->verticalScrollBar()->maximum();
1799 ed->setCenterOnScroll(false);
1800 const int maxWithoutCenterOnScroll = ed->verticalScrollBar()->maximum();
1801 QVERIFY(maxWithCenterOnScroll > maxWithoutCenterOnScroll);
1802}
1803
1804#endif
1805
1806void tst_QPlainTextEdit::updateCursorPositionAfterEdit()
1807{
1808 QPlainTextEdit plaintextEdit;
1809 plaintextEdit.setPlainText("some text some text\nsome text some text");
1810
1811 const auto initialPosition = 1;
1812
1813 // select some text
1814 auto cursor = plaintextEdit.textCursor();
1815 cursor.setPosition(pos: initialPosition, mode: QTextCursor::MoveAnchor);
1816 cursor.setPosition(pos: initialPosition + 3, mode: QTextCursor::KeepAnchor);
1817 plaintextEdit.setTextCursor(cursor);
1818 QVERIFY(plaintextEdit.textCursor().hasSelection());
1819
1820 QTest::keyClick(widget: &plaintextEdit, key: Qt::Key_Delete);
1821 QTest::keyClick(widget: &plaintextEdit, key: Qt::Key_Down);
1822 QTest::keyClick(widget: &plaintextEdit, key: Qt::Key_Up);
1823
1824 // Moving the cursor down and up should bring it to the initial position
1825 QCOMPARE(plaintextEdit.textCursor().position(), initialPosition);
1826
1827 // Test the same with backspace
1828 cursor = plaintextEdit.textCursor();
1829 cursor.setPosition(pos: initialPosition + 3, mode: QTextCursor::KeepAnchor);
1830 plaintextEdit.setTextCursor(cursor);
1831 QVERIFY(plaintextEdit.textCursor().hasSelection());
1832
1833 QTest::keyClick(widget: &plaintextEdit, key: Qt::Key_Backspace);
1834 QTest::keyClick(widget: &plaintextEdit, key: Qt::Key_Down);
1835 QTest::keyClick(widget: &plaintextEdit, key: Qt::Key_Up);
1836
1837 // Moving the cursor down and up should bring it to the initial position
1838 QCOMPARE(plaintextEdit.textCursor().position(), initialPosition);
1839
1840 // Test insertion
1841 const QString txt("txt");
1842 QApplication::clipboard()->setText(txt);
1843 plaintextEdit.paste();
1844 QTest::keyClick(widget: &plaintextEdit, key: Qt::Key_Down);
1845 QTest::keyClick(widget: &plaintextEdit, key: Qt::Key_Up);
1846
1847 // The curser should move back to the end of the copied text
1848 QCOMPARE(plaintextEdit.textCursor().position(), initialPosition + txt.length());
1849}
1850
1851QTEST_MAIN(tst_QPlainTextEdit)
1852#include "tst_qplaintextedit.moc"
1853

source code of qtbase/tests/auto/widgets/widgets/qplaintextedit/tst_qplaintextedit.cpp