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 <qtextdocument.h>
34#include <qdebug.h>
35
36#include <qtextcursor.h>
37#include <qtextdocumentfragment.h>
38#include <qtextformat.h>
39#include <qtextobject.h>
40#include <qtexttable.h>
41#include <qabstracttextdocumentlayout.h>
42#include <qtextlist.h>
43#include <qtextcodec.h>
44#include <qguiapplication.h>
45#include <qurl.h>
46#include <qpainter.h>
47#include <qfontmetrics.h>
48#include <qimage.h>
49#include <qtextlayout.h>
50#include <QDomDocument>
51#include "common.h"
52
53// #define DEBUG_WRITE_OUTPUT
54
55QT_FORWARD_DECLARE_CLASS(QTextDocument)
56
57class tst_QTextDocument : public QObject
58{
59 Q_OBJECT
60
61public:
62 tst_QTextDocument();
63
64private slots:
65 void init();
66 void cleanup();
67 void cleanupTestCase();
68 void getSetCheck();
69 void isEmpty();
70 void find_data();
71 void find();
72 void find2();
73 void findWithRegExp_data();
74 void findWithRegExp();
75 void findWithRegularExpression_data();
76 void findWithRegularExpression();
77 void findMultiple();
78 void basicIsModifiedChecks();
79 void moreIsModified();
80 void isModified2();
81 void isModified3();
82 void isModified4();
83 void noundo_basicIsModifiedChecks();
84 void noundo_moreIsModified();
85 void noundo_isModified2();
86 void noundo_isModified3();
87 void mightBeRichText();
88 void mightBeRichText_data();
89
90 void task240325();
91
92 void preFont();
93
94 void stylesheetFont_data();
95 void stylesheetFont();
96
97 void toHtml_data();
98 void toHtml();
99 void toHtml2();
100
101 void setFragmentMarkersInHtmlExport();
102
103 void toHtmlBodyBgColor();
104 void toHtmlBodyBgColorRgba();
105 void toHtmlBodyBgColorTransparent();
106 void toHtmlRootFrameProperties();
107 void toHtmlLineHeightProperties();
108 void toHtmlDefaultFontSpacingProperties();
109 void capitalizationHtmlInExport();
110 void wordspacingHtmlExport();
111
112 void cursorPositionChanged();
113 void cursorPositionChangedOnSetText();
114
115 void textFrameIterator();
116
117 void codecForHtml();
118
119 void markContentsDirty();
120
121 void clonePreservesMetaInformation();
122 void clonePreservesPageSize();
123 void clonePreservesPageBreakPolicies();
124 void clonePreservesDefaultFont();
125 void clonePreservesRootFrameFormat();
126 void clonePreservesResources();
127 void clonePreservesUserStates();
128 void clonePreservesIndentWidth();
129 void clonePreservesFormatsWhenEmpty();
130 void blockCount();
131 void defaultStyleSheet();
132
133 void resolvedFontInEmptyFormat();
134
135 void defaultRootFrameMargin();
136
137 void clearResources();
138
139 void setPlainText();
140 void toPlainText_data();
141 void toPlainText();
142 void toRawText();
143
144 void deleteTextObjectsOnClear();
145
146 void maximumBlockCount();
147 void adjustSize();
148 void initialUserData();
149
150 void html_defaultFont();
151
152 void blockCountChanged();
153
154 void nonZeroDocumentLengthOnClear();
155
156 void setTextPreservesUndoRedoEnabled();
157
158 void firstLast();
159
160 void backgroundImage_toHtml();
161 void backgroundImage_toHtml2();
162 void backgroundImage_clone();
163 void backgroundImage_copy();
164
165 void documentCleanup();
166
167 void characterAt();
168 void revisions();
169 void revisionWithUndoCompressionAndUndo();
170
171 void testUndoCommandAdded();
172
173 void testUndoBlocks();
174
175 void receiveCursorPositionChangedAfterContentsChange();
176
177 void copiedFontSize();
178
179 void QTBUG25778_pixelSizeFromHtml();
180
181 void htmlExportImportBlockCount();
182
183 void QTBUG27354_spaceAndSoftSpace();
184 void baseUrl_data();
185 void baseUrl();
186
187 void QTBUG28998_linkColor();
188
189 void textCursorUsageWithinContentsChange();
190 void cssInheritance();
191
192 void lineHeightType();
193 void cssLineHeightMultiplier();
194
195 void fontTagFace();
196
197 void clearUndoRedoStacks();
198 void mergeFontFamilies();
199
200 void contentsChangeIndices_data();
201 void contentsChangeIndices();
202
203 void insertHtmlWithComments_data();
204 void insertHtmlWithComments();
205
206private:
207 void backgroundImage_checkExpectedHtml(const QTextDocument &doc);
208 void buildRegExpData();
209 static QString cssFontSizeString(const QFont &font);
210 void writeActualAndExpected(const char* testTag, const QString &actual, const QString &expected);
211
212 QTextDocument *doc;
213 QTextCursor cursor;
214 QFont defaultFont;
215 QString htmlHead;
216 QString htmlTail;
217};
218
219class MyAbstractTextDocumentLayout : public QAbstractTextDocumentLayout
220{
221public:
222 MyAbstractTextDocumentLayout(QTextDocument *doc) : QAbstractTextDocumentLayout(doc) {}
223 void draw(QPainter *, const PaintContext &) {}
224 int hitTest(const QPointF &, Qt::HitTestAccuracy) const { return 0; }
225 int pageCount() const { return 0; }
226 QSizeF documentSize() const { return QSizeF(); }
227 QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); }
228 QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); }
229 void documentChanged(int, int, int) {}
230};
231
232QString tst_QTextDocument::cssFontSizeString(const QFont &font)
233{
234 return font.pointSize() >= 0
235 ? QString::number(font.pointSizeF()) + QStringLiteral("pt")
236 : QString::number(font.pixelSize()) + QStringLiteral("px");
237}
238
239void tst_QTextDocument::writeActualAndExpected(const char *testTag, const QString &actual, const QString &expected)
240{
241#ifdef DEBUG_WRITE_OUTPUT
242 {
243 QFile out(QDir::temp().absoluteFilePath(QLatin1String(testTag) + QLatin1String("-actual.html")));
244 out.open(QFile::WriteOnly);
245 out.write(actual.toUtf8());
246 out.close();
247 } {
248 QFile out(QDir::temp().absoluteFilePath(QLatin1String(testTag) + QLatin1String("-expected.html")));
249 out.open(QFile::WriteOnly);
250 out.write(expected.toUtf8());
251 out.close();
252 }
253#else
254 Q_UNUSED(testTag)
255 Q_UNUSED(actual)
256 Q_UNUSED(expected)
257#endif
258}
259
260// Testing get/set functions
261void tst_QTextDocument::getSetCheck()
262{
263 QTextDocument obj1;
264 // QAbstractTextDocumentLayout * QTextDocument::documentLayout()
265 // void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *)
266 QPointer<MyAbstractTextDocumentLayout> var1 = new MyAbstractTextDocumentLayout(0);
267 obj1.setDocumentLayout(var1);
268 QCOMPARE(static_cast<QAbstractTextDocumentLayout *>(var1), obj1.documentLayout());
269 obj1.setDocumentLayout((QAbstractTextDocumentLayout *)0);
270 QVERIFY(var1.isNull());
271 QVERIFY(obj1.documentLayout());
272
273 // bool QTextDocument::useDesignMetrics()
274 // void QTextDocument::setUseDesignMetrics(bool)
275 obj1.setUseDesignMetrics(false);
276 QCOMPARE(false, obj1.useDesignMetrics());
277 obj1.setUseDesignMetrics(true);
278 QCOMPARE(true, obj1.useDesignMetrics());
279}
280
281tst_QTextDocument::tst_QTextDocument()
282{
283 QImage img(16, 16, QImage::Format_ARGB32_Premultiplied);
284 img.save(fileName: "foo.png");
285}
286
287void tst_QTextDocument::init()
288{
289 doc = new QTextDocument;
290 cursor = QTextCursor(doc);
291 defaultFont = QFont();
292
293 htmlHead = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
294 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
295 "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
296 "p, li { white-space: pre-wrap; }\n"
297 "</style></head>"
298 "<body style=\" font-family:'%1'; font-size:%2; font-weight:%3; font-style:%4;\">\n");
299 htmlHead = htmlHead
300 .arg(a: defaultFont.family())
301 .arg(a: cssFontSizeString(font: defaultFont))
302 .arg(a: defaultFont.weight() * 8)
303 .arg(a: (defaultFont.italic() ? "italic" : "normal"));
304
305 htmlTail = QString("</body></html>");
306}
307
308void tst_QTextDocument::cleanup()
309{
310 cursor = QTextCursor();
311 delete doc;
312 doc = 0;
313}
314
315void tst_QTextDocument::cleanupTestCase()
316{
317 QFile::remove(fileName: QLatin1String("foo.png"));
318}
319
320void tst_QTextDocument::isEmpty()
321{
322 QVERIFY(doc->isEmpty());
323}
324
325void tst_QTextDocument::find_data()
326{
327 QTest::addColumn<QString>(name: "haystack");
328 QTest::addColumn<QString>(name: "needle");
329 QTest::addColumn<int>(name: "flags");
330 QTest::addColumn<int>(name: "from");
331 QTest::addColumn<int>(name: "anchor");
332 QTest::addColumn<int>(name: "position");
333
334 QTest::newRow(dataTag: "1") << "Hello World" << "World" << int(QTextDocument::FindCaseSensitively) << 0 << 6 << 11;
335
336 QTest::newRow(dataTag: "2") << QString::fromLatin1(str: "Hello") + QString(QChar::ParagraphSeparator) + QString::fromLatin1(str: "World")
337 << "World" << int(QTextDocument::FindCaseSensitively) << 1 << 6 << 11;
338
339 QTest::newRow(dataTag: "3") << QString::fromLatin1(str: "Hello") + QString(QChar::ParagraphSeparator) + QString::fromLatin1(str: "World")
340 << "Hello" << int(QTextDocument::FindCaseSensitively | QTextDocument::FindBackward) << 10 << 0 << 5;
341 QTest::newRow(dataTag: "4wholewords") << QString::fromLatin1(str: "Hello Blah World")
342 << "Blah" << int(QTextDocument::FindWholeWords) << 0 << 6 << 10;
343 QTest::newRow(dataTag: "5wholewords") << QString::fromLatin1(str: "HelloBlahWorld")
344 << "Blah" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1;
345 QTest::newRow(dataTag: "6wholewords") << QString::fromLatin1(str: "HelloBlahWorld Blah Hah")
346 << "Blah" << int(QTextDocument::FindWholeWords) << 0 << 15 << 19;
347 QTest::newRow(dataTag: "7wholewords") << QString::fromLatin1(str: "HelloBlahWorld Blah Hah")
348 << "Blah" << int(QTextDocument::FindWholeWords | QTextDocument::FindBackward) << 23 << 15 << 19;
349 QTest::newRow(dataTag: "8wholewords") << QString::fromLatin1(str: "Hello: World\n")
350 << "orld" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1;
351
352 QTest::newRow(dataTag: "across-paragraphs") << QString::fromLatin1(str: "First Parag\nSecond Parag with a lot more text")
353 << "Parag" << int(QTextDocument::FindBackward)
354 << 15 << 6 << 11;
355
356 QTest::newRow(dataTag: "nbsp") << "Hello" + QString(QChar(QChar::Nbsp)) +"World" << " " << int(QTextDocument::FindCaseSensitively) << 0 << 5 << 6;
357
358 QTest::newRow(dataTag: "from-the-end") << "Hello World" << "Hello World" << int(QTextDocument::FindCaseSensitively| QTextDocument::FindBackward) << 11 << 0 << 11;
359
360 QTest::newRow(dataTag: "bw-cross-paras-1") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 7 << 3 << 4;
361 QTest::newRow(dataTag: "bw-cross-paras-2") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 6 << 3 << 4;
362 QTest::newRow(dataTag: "bw-cross-paras-3") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 5 << 3 << 4;
363 QTest::newRow(dataTag: "bw-cross-paras-4") << "a1\na2\nb1" << "a" << int(QTextDocument::FindBackward) << 3 << 0 << 1;
364 QTest::newRow(dataTag: "bw-cross-paras-5") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 5 << 1 << 2;
365 QTest::newRow(dataTag: "bw-cross-paras-6") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 4 << 1 << 2;
366 QTest::newRow(dataTag: "bw-cross-paras-7") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 3 << 1 << 2;
367 QTest::newRow(dataTag: "bw-cross-paras-8") << "xa\n\nb1" << "a" << int(QTextDocument::FindBackward) << 2 << 1 << 2;
368}
369
370void tst_QTextDocument::find()
371{
372 QFETCH(QString, haystack);
373 QFETCH(QString, needle);
374 QFETCH(int, flags);
375 QFETCH(int, from);
376 QFETCH(int, anchor);
377 QFETCH(int, position);
378
379 cursor.insertText(text: haystack);
380 cursor = doc->find(subString: needle, from, options: QTextDocument::FindFlags(flags));
381
382 if (anchor != -1) {
383 QCOMPARE(cursor.anchor(), anchor);
384 QCOMPARE(cursor.position(), position);
385 } else {
386 QVERIFY(cursor.isNull());
387 }
388
389 //search using a regular expression
390 QRegExp expr(needle);
391 expr.setPatternSyntax(QRegExp::FixedString);
392 QTextDocument::FindFlags flg(flags);
393 expr.setCaseSensitivity((flg & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
394 cursor = doc->find(expr, from, options: flg);
395
396 if (anchor != -1) {
397 QCOMPARE(cursor.anchor(), anchor);
398 QCOMPARE(cursor.position(), position);
399 } else {
400 QVERIFY(cursor.isNull());
401 }
402}
403
404void tst_QTextDocument::findWithRegExp_data()
405{
406 buildRegExpData();
407}
408
409void tst_QTextDocument::findWithRegExp()
410{
411 QFETCH(QString, haystack);
412 QFETCH(QString, needle);
413 QFETCH(int, flags);
414 QFETCH(int, from);
415 QFETCH(int, anchor);
416 QFETCH(int, position);
417
418 cursor.insertText(text: haystack);
419 //search using a regular expression
420 QRegExp expr(needle);
421 QTextDocument::FindFlags flg(flags);
422 expr.setCaseSensitivity((flg & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
423 cursor = doc->find(expr, from, options: flg);
424
425 if (anchor != -1) {
426 QCOMPARE(cursor.anchor(), anchor);
427 QCOMPARE(cursor.position(), position);
428 } else {
429 QVERIFY(cursor.isNull());
430 }
431}
432
433void tst_QTextDocument::findWithRegularExpression_data()
434{
435 buildRegExpData();
436}
437
438void tst_QTextDocument::findWithRegularExpression()
439{
440 QFETCH(QString, haystack);
441 QFETCH(QString, needle);
442 QFETCH(int, flags);
443 QFETCH(int, from);
444 QFETCH(int, anchor);
445 QFETCH(int, position);
446
447 cursor.insertText(text: haystack);
448 //search using a regular expression
449 QRegularExpression expr(needle);
450 QTextDocument::FindFlags flg(flags);
451 cursor = doc->find(expr, from, options: flg);
452
453 if (anchor != -1) {
454 QCOMPARE(cursor.anchor(), anchor);
455 QCOMPARE(cursor.position(), position);
456 } else {
457 QVERIFY(cursor.isNull());
458 }
459}
460
461void tst_QTextDocument::find2()
462{
463 doc->setPlainText("aaa");
464 cursor.movePosition(op: QTextCursor::Start);
465 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
466 QTextCursor hit = doc->find(subString: "a", cursor);
467 QCOMPARE(hit.position(), 2);
468 QCOMPARE(hit.anchor(), 1);
469}
470
471void tst_QTextDocument::findMultiple()
472{
473 const QString text("foo bar baz foo bar baz");
474 doc->setPlainText(text);
475
476 cursor.movePosition(op: QTextCursor::Start);
477 cursor = doc->find(subString: "bar", cursor);
478 QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
479 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
480 cursor = doc->find(subString: "bar", cursor);
481 QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
482 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
483
484 cursor.movePosition(op: QTextCursor::End);
485 cursor = doc->find(subString: "bar", cursor, options: QTextDocument::FindBackward);
486 QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
487 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
488 cursor = doc->find(subString: "bar", cursor, options: QTextDocument::FindBackward);
489 QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
490 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
491
492
493 QRegExp expr("bar");
494 expr.setPatternSyntax(QRegExp::FixedString);
495
496 cursor.movePosition(op: QTextCursor::End);
497 cursor = doc->find(expr, cursor, options: QTextDocument::FindBackward);
498 QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
499 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
500 cursor = doc->find(expr, cursor, options: QTextDocument::FindBackward);
501 QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
502 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
503
504 cursor.movePosition(op: QTextCursor::Start);
505 cursor = doc->find(expr, cursor);
506 QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
507 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
508 cursor = doc->find(expr, cursor);
509 QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
510 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
511
512 QRegularExpression regularExpression("bar");
513
514 cursor.movePosition(op: QTextCursor::End);
515 cursor = doc->find(expr: regularExpression, cursor, options: QTextDocument::FindBackward);
516 QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
517 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
518 cursor = doc->find(expr: regularExpression, cursor, options: QTextDocument::FindBackward);
519 QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
520 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
521
522 cursor.movePosition(op: QTextCursor::Start);
523 cursor = doc->find(expr: regularExpression, cursor);
524 QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
525 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
526 cursor = doc->find(expr: regularExpression, cursor);
527 QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
528 QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
529}
530
531void tst_QTextDocument::basicIsModifiedChecks()
532{
533 QSignalSpy spy(doc, SIGNAL(modificationChanged(bool)));
534
535 QVERIFY(!doc->isModified());
536 cursor.insertText(text: "Hello World");
537 QVERIFY(doc->isModified());
538 QCOMPARE(spy.count(), 1);
539 QVERIFY(spy.takeFirst().at(0).toBool());
540
541 doc->undo();
542 QVERIFY(!doc->isModified());
543 QCOMPARE(spy.count(), 1);
544 QVERIFY(!spy.takeFirst().at(0).toBool());
545
546 doc->redo();
547 QVERIFY(doc->isModified());
548 QCOMPARE(spy.count(), 1);
549 QVERIFY(spy.takeFirst().at(0).toBool());
550}
551
552void tst_QTextDocument::moreIsModified()
553{
554 QVERIFY(!doc->isModified());
555
556 cursor.insertText(text: "Hello");
557 QVERIFY(doc->isModified());
558
559 doc->undo();
560 QVERIFY(!doc->isModified());
561
562 cursor.insertText(text: "Hello");
563
564 doc->undo();
565 QVERIFY(!doc->isModified());
566}
567
568void tst_QTextDocument::isModified2()
569{
570 // reported on qt4-preview-feedback
571 QVERIFY(!doc->isModified());
572
573 cursor.insertText(text: "Hello");
574 QVERIFY(doc->isModified());
575
576 doc->setModified(false);
577 QVERIFY(!doc->isModified());
578
579 cursor.insertText(text: "Hello");
580 QVERIFY(doc->isModified());
581}
582
583void tst_QTextDocument::isModified3()
584{
585 QVERIFY(!doc->isModified());
586
587 doc->setUndoRedoEnabled(false);
588 doc->setUndoRedoEnabled(true);
589
590 cursor.insertText(text: "Hello");
591
592 QVERIFY(doc->isModified());
593 doc->undo();
594 QVERIFY(!doc->isModified());
595}
596
597void tst_QTextDocument::isModified4()
598{
599 QVERIFY(!doc->isModified());
600
601 cursor.insertText(text: "Hello");
602 cursor.insertText(text: "World");
603
604 doc->setModified(false);
605
606 QVERIFY(!doc->isModified());
607
608 cursor.insertText(text: "Again");
609 QVERIFY(doc->isModified());
610
611 doc->undo();
612 QVERIFY(!doc->isModified());
613 doc->undo();
614 QVERIFY(doc->isModified());
615
616 doc->redo();
617 QVERIFY(!doc->isModified());
618 doc->redo();
619 QVERIFY(doc->isModified());
620
621 doc->undo();
622 QVERIFY(!doc->isModified());
623 doc->undo();
624 QVERIFY(doc->isModified());
625
626 //task 197769
627 cursor.insertText(text: "Hello");
628 QVERIFY(doc->isModified());
629}
630
631void tst_QTextDocument::noundo_basicIsModifiedChecks()
632{
633 doc->setUndoRedoEnabled(false);
634 QSignalSpy spy(doc, SIGNAL(modificationChanged(bool)));
635
636 QVERIFY(!doc->isModified());
637 cursor.insertText(text: "Hello World");
638 QVERIFY(doc->isModified());
639 QCOMPARE(spy.count(), 1);
640 QVERIFY(spy.takeFirst().at(0).toBool());
641
642 doc->undo();
643 QVERIFY(doc->isModified());
644 QCOMPARE(spy.count(), 0);
645
646 doc->redo();
647 QVERIFY(doc->isModified());
648 QCOMPARE(spy.count(), 0);
649}
650
651void tst_QTextDocument::task240325()
652{
653 doc->setHtml("<html><img width=\"100\" height=\"100\" align=\"right\"/>Foobar Foobar Foobar Foobar</html>");
654
655 QImage img(1000, 7000, QImage::Format_ARGB32_Premultiplied);
656 QPainter p(&img);
657 QFontMetrics fm(p.font());
658
659 // Set page size to contain image and one "Foobar"
660 doc->setPageSize(QSize(100 + fm.horizontalAdvance("Foobar")*2, 1000));
661
662 // Force layout
663 doc->drawContents(painter: &p);
664
665 QCOMPARE(doc->blockCount(), 1);
666 for (QTextBlock block = doc->begin() ; block!=doc->end() ; block = block.next()) {
667 QTextLayout *layout = block.layout();
668#ifdef Q_OS_ANDROID
669 QEXPECT_FAIL("", "QTBUG-69242", Abort);
670#endif
671 QCOMPARE(layout->lineCount(), 4);
672
673 for (int lineIdx=0;lineIdx<layout->lineCount();++lineIdx) {
674 QTextLine line = layout->lineAt(i: lineIdx);
675
676 QString text = block.text().mid(position: line.textStart(), n: line.textLength()).trimmed();
677
678 // Remove start token
679 if (lineIdx == 0)
680 text = text.mid(position: 1);
681
682 QCOMPARE(text, QString::fromLatin1("Foobar"));
683 }
684 }
685}
686
687void tst_QTextDocument::stylesheetFont_data()
688{
689 QTest::addColumn<QString>(name: "stylesheet");
690 QTest::addColumn<QFont>(name: "font");
691
692 {
693 QFont font;
694 font.setBold(true);
695 font.setPixelSize(64);
696
697 QTest::newRow(dataTag: "Regular font specification")
698 << "font-size: 64px; font-weight: bold;"
699 << font;
700 }
701
702
703 {
704 QFont font;
705 font.setBold(true);
706 font.setPixelSize(64);
707
708 QTest::newRow(dataTag: "Shorthand font specification")
709 << "font: normal bold 64px Arial;"
710 << font;
711 }
712
713}
714
715void tst_QTextDocument::stylesheetFont()
716{
717 QFETCH(QString, stylesheet);
718 QFETCH(QFont, font);
719
720 QString html = QString::fromLatin1(str: "<html>"
721 "<body>"
722 "<div style=\"%1\" >"
723 "Foobar"
724 "</div>"
725 "</body>"
726 "</html>").arg(a: stylesheet);
727
728 qDebug() << html;
729 doc->setHtml(html);
730 QCOMPARE(doc->blockCount(), 1);
731
732 // First and only block
733 QTextBlock block = doc->firstBlock();
734
735 QString text = block.text();
736 QCOMPARE(text, QString::fromLatin1("Foobar"));
737
738 QFont actualFont = block.charFormat().font();
739
740 QCOMPARE(actualFont.bold(), font.bold());
741 QCOMPARE(actualFont.pixelSize(), font.pixelSize());
742}
743
744void tst_QTextDocument::preFont()
745{
746 const QFont font = QFontDatabase::systemFont(type: QFontDatabase::FixedFont);
747 const QString html = QString::fromLatin1( str: "<html>"
748 "<body>"
749 "<pre>"
750 "Foobar"
751 "</pre>"
752 "</body>"
753 "</html>");
754
755 doc->setHtml(html);
756 QCOMPARE(doc->blockCount(), 1);
757
758 // First and only block
759 QTextBlock block = doc->firstBlock();
760
761 QString text = block.text();
762 QCOMPARE(text, QString::fromLatin1("Foobar"));
763
764 QFont actualFont = block.charFormat().font();
765 QCOMPARE(actualFont.family(), font.family());
766}
767
768void tst_QTextDocument::noundo_moreIsModified()
769{
770 doc->setUndoRedoEnabled(false);
771 QVERIFY(!doc->isModified());
772
773 cursor.insertText(text: "Hello");
774 QVERIFY(doc->isModified());
775
776 doc->undo();
777 QVERIFY(doc->isModified());
778
779 cursor.insertText(text: "Hello");
780
781 doc->undo();
782 QVERIFY(doc->isModified());
783}
784
785void tst_QTextDocument::noundo_isModified2()
786{
787 // reported on qt4-preview-feedback
788 QVERIFY(!doc->isModified());
789
790 cursor.insertText(text: "Hello");
791 QVERIFY(doc->isModified());
792
793 doc->setModified(false);
794 QVERIFY(!doc->isModified());
795
796 cursor.insertText(text: "Hello");
797 QVERIFY(doc->isModified());
798}
799
800void tst_QTextDocument::noundo_isModified3()
801{
802 doc->setUndoRedoEnabled(false);
803 QVERIFY(!doc->isModified());
804
805 cursor.insertText(text: "Hello");
806
807 QVERIFY(doc->isModified());
808 doc->undo();
809 QVERIFY(doc->isModified());
810}
811
812void tst_QTextDocument::mightBeRichText_data()
813{
814 const char qtDocuHeader[] = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
815 "<!DOCTYPE html\n"
816 " PUBLIC ""-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"
817 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">";
818 QVERIFY(Qt::mightBeRichText(QString::fromLatin1(qtDocuHeader)));
819 QTest::addColumn<QString>(name: "input");
820 QTest::addColumn<bool>(name: "result");
821
822 QTest::newRow(dataTag: "documentation-header") << QString("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
823 "<!DOCTYPE html\n"
824 " PUBLIC ""-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"
825 "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">")
826 << true;
827 QTest::newRow(dataTag: "br-nospace") << QString("Test <br/> new line") << true;
828 QTest::newRow(dataTag: "br-space") << QString("Test <br /> new line") << true;
829 QTest::newRow(dataTag: "br-invalidspace") << QString("Test <br/ > new line") << false;
830 QTest::newRow(dataTag: "invalid closing tag") << QString("Test <br/ line") << false;
831}
832
833void tst_QTextDocument::mightBeRichText()
834{
835 QFETCH(QString, input);
836 QFETCH(bool, result);
837 QCOMPARE(result, Qt::mightBeRichText(input));
838}
839
840Q_DECLARE_METATYPE(QTextDocumentFragment)
841
842#define CREATE_DOC_AND_CURSOR() \
843 QTextDocument doc; \
844 doc.setDefaultFont(defaultFont); \
845 QTextCursor cursor(&doc);
846
847void tst_QTextDocument::toHtml_data()
848{
849 QTest::addColumn<QTextDocumentFragment>(name: "input");
850 QTest::addColumn<QString>(name: "expectedOutput");
851
852 {
853 CREATE_DOC_AND_CURSOR();
854
855 cursor.insertText(text: "Blah");
856
857 QTest::newRow(dataTag: "simple") << QTextDocumentFragment(&doc) << QString("<p DEFAULTBLOCKSTYLE>Blah</p>");
858 }
859
860 {
861 CREATE_DOC_AND_CURSOR();
862
863 cursor.insertText(text: "&<>");
864
865 QTest::newRow(dataTag: "entities") << QTextDocumentFragment(&doc) << QString("<p DEFAULTBLOCKSTYLE>&amp;&lt;&gt;</p>");
866 }
867
868 {
869 CREATE_DOC_AND_CURSOR();
870
871 QTextCharFormat fmt;
872 fmt.setFontFamily("Times");
873 cursor.insertText(text: "Blah", format: fmt);
874
875 QTest::newRow(dataTag: "font-family") << QTextDocumentFragment(&doc)
876 << QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:'Times';\">Blah</span></p>");
877 }
878
879 {
880 CREATE_DOC_AND_CURSOR();
881
882 QTextCharFormat fmt;
883 fmt.setFontFamily("Foo's Family");
884 cursor.insertText(text: "Blah", format: fmt);
885
886 QTest::newRow(dataTag: "font-family-with-quotes1") << QTextDocumentFragment(&doc)
887 << QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:&quot;Foo's Family&quot;;\">Blah</span></p>");
888 }
889
890 {
891 CREATE_DOC_AND_CURSOR();
892
893 QTextCharFormat fmt;
894 fmt.setFontFamily("Foo\"s Family");
895 cursor.insertText(text: "Blah", format: fmt);
896
897 QTest::newRow(dataTag: "font-family-with-quotes2") << QTextDocumentFragment(&doc)
898 << QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:'Foo&quot;s Family';\">Blah</span></p>");
899 }
900
901 {
902 CREATE_DOC_AND_CURSOR();
903
904 QTextCharFormat fmt;
905 fmt.setFontFamily("Times");
906 fmt.setFontFamilies(QStringList{ "Times", "serif" });
907 cursor.insertText(text: "Blah", format: fmt);
908
909 QTest::newRow(dataTag: "font-family-with-fallback") << QTextDocumentFragment(&doc)
910 << QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:'Times','serif';\">Blah</span></p>");
911 }
912
913 {
914 CREATE_DOC_AND_CURSOR();
915
916 QTextBlockFormat fmt;
917 fmt.setNonBreakableLines(true);
918 cursor.insertBlock(format: fmt);
919 cursor.insertText(text: "Blah");
920
921 QTest::newRow(dataTag: "pre") << QTextDocumentFragment(&doc)
922 <<
923 QString("EMPTYBLOCK") +
924 QString("<pre DEFAULTBLOCKSTYLE>Blah</pre>");
925 }
926
927 {
928 CREATE_DOC_AND_CURSOR();
929
930 QTextCharFormat fmt;
931 fmt.setFontPointSize(40);
932 cursor.insertText(text: "Blah", format: fmt);
933
934 QTest::newRow(dataTag: "font-size") << QTextDocumentFragment(&doc)
935 << QString("<p DEFAULTBLOCKSTYLE><span style=\" font-size:40pt;\">Blah</span></p>");
936 }
937
938 {
939 CREATE_DOC_AND_CURSOR();
940
941 QTextCharFormat fmt;
942 fmt.setProperty(propertyId: QTextFormat::FontSizeIncrement, value: 2);
943 cursor.insertText(text: "Blah", format: fmt);
944
945 QTest::newRow(dataTag: "logical-font-size") << QTextDocumentFragment(&doc)
946 << QString("<p DEFAULTBLOCKSTYLE><span style=\" font-size:x-large;\">Blah</span></p>");
947 }
948
949 {
950 CREATE_DOC_AND_CURSOR();
951
952 cursor.insertText(text: "Foo");
953
954 QTextCharFormat fmt;
955 fmt.setFontPointSize(40);
956 cursor.insertBlock(format: QTextBlockFormat(), charFormat: fmt);
957
958 fmt.clearProperty(propertyId: QTextFormat::FontPointSize);
959 cursor.insertText(text: "Blub", format: fmt);
960
961 QTest::newRow(dataTag: "no-font-size") << QTextDocumentFragment(&doc)
962 << QString("<p DEFAULTBLOCKSTYLE>Foo</p>\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blub</p>");
963 }
964
965 {
966 CREATE_DOC_AND_CURSOR();
967
968 QTextBlockFormat fmt;
969 fmt.setLayoutDirection(Qt::RightToLeft);
970 cursor.insertBlock(format: fmt);
971 cursor.insertText(text: "Blah");
972
973 QTest::newRow(dataTag: "rtl") << QTextDocumentFragment(&doc)
974 <<
975 QString("EMPTYBLOCK") +
976 QString("<p dir='rtl' DEFAULTBLOCKSTYLE>Blah</p>");
977 }
978
979 {
980 CREATE_DOC_AND_CURSOR();
981
982 QTextBlockFormat fmt;
983 fmt.setAlignment(Qt::AlignJustify);
984 cursor.insertBlock(format: fmt);
985 cursor.insertText(text: "Blah");
986
987 QTest::newRow(dataTag: "blockalign") << QTextDocumentFragment(&doc)
988 <<
989 QString("EMPTYBLOCK") +
990 QString("<p align=\"justify\" DEFAULTBLOCKSTYLE>Blah</p>");
991 }
992
993 {
994 CREATE_DOC_AND_CURSOR();
995
996 QTextBlockFormat fmt;
997 fmt.setAlignment(Qt::AlignCenter);
998 cursor.insertBlock(format: fmt);
999 cursor.insertText(text: "Blah");
1000
1001 QTest::newRow(dataTag: "blockalign2") << QTextDocumentFragment(&doc)
1002 <<
1003 QString("EMPTYBLOCK") +
1004 QString("<p align=\"center\" DEFAULTBLOCKSTYLE>Blah</p>");
1005 }
1006
1007 {
1008 CREATE_DOC_AND_CURSOR();
1009
1010 QTextBlockFormat fmt;
1011 fmt.setAlignment(Qt::AlignRight | Qt::AlignAbsolute);
1012 cursor.insertBlock(format: fmt);
1013 cursor.insertText(text: "Blah");
1014
1015 QTest::newRow(dataTag: "blockalign3") << QTextDocumentFragment(&doc)
1016 <<
1017 QString("EMPTYBLOCK") +
1018 QString("<p align=\"right\" DEFAULTBLOCKSTYLE>Blah</p>");
1019 }
1020
1021 {
1022 CREATE_DOC_AND_CURSOR();
1023
1024 QTextBlockFormat fmt;
1025 fmt.setBackground(QColor("#0000ff"));
1026 cursor.insertBlock(format: fmt);
1027 cursor.insertText(text: "Blah");
1028
1029 QTest::newRow(dataTag: "bgcolor") << QTextDocumentFragment(&doc)
1030 << QString("EMPTYBLOCK") +
1031 QString("<p OPENDEFAULTBLOCKSTYLE background-color:#0000ff;\">Blah</p>");
1032 }
1033
1034 {
1035 CREATE_DOC_AND_CURSOR();
1036
1037 QTextBlockFormat fmt;
1038 fmt.setBackground(QColor(255, 0, 0, 51));
1039 cursor.insertBlock(format: fmt);
1040 cursor.insertText(text: "Blah");
1041
1042 QTest::newRow(dataTag: "bgcolor-rgba") << QTextDocumentFragment(&doc)
1043 << QString("EMPTYBLOCK") +
1044 QString("<p OPENDEFAULTBLOCKSTYLE background-color:rgba(255,0,0,0.2);\">Blah</p>");
1045 }
1046
1047 {
1048 CREATE_DOC_AND_CURSOR();
1049
1050 QTextBlockFormat fmt;
1051 fmt.setBackground(QColor(255, 0, 0, 0));
1052 cursor.insertBlock(format: fmt);
1053 cursor.insertText(text: "Blah");
1054
1055 QTest::newRow(dataTag: "bgcolor-transparent") << QTextDocumentFragment(&doc)
1056 << QString("EMPTYBLOCK") +
1057 QString("<p OPENDEFAULTBLOCKSTYLE background-color:transparent;\">Blah</p>");
1058 }
1059
1060 {
1061 CREATE_DOC_AND_CURSOR();
1062
1063 QTextCharFormat fmt;
1064 fmt.setFontWeight(40);
1065 cursor.insertText(text: "Blah", format: fmt);
1066
1067 QTest::newRow(dataTag: "font-weight") << QTextDocumentFragment(&doc)
1068 << QString("<p DEFAULTBLOCKSTYLE><span style=\" font-weight:320;\">Blah</span></p>");
1069 }
1070
1071 {
1072 CREATE_DOC_AND_CURSOR();
1073
1074 QTextCharFormat fmt;
1075 fmt.setFontItalic(true);
1076 cursor.insertText(text: "Blah", format: fmt);
1077
1078 QTest::newRow(dataTag: "font-italic") << QTextDocumentFragment(&doc)
1079 << QString("<p DEFAULTBLOCKSTYLE><span style=\" font-style:italic;\">Blah</span></p>");
1080 }
1081
1082 {
1083 CREATE_DOC_AND_CURSOR();
1084
1085 QTextCharFormat fmt;
1086 fmt.setFontUnderline(true);
1087 fmt.setFontOverline(false);
1088 cursor.insertText(text: "Blah", format: fmt);
1089
1090 QTest::newRow(dataTag: "text-decoration-1") << QTextDocumentFragment(&doc)
1091 << QString("<p DEFAULTBLOCKSTYLE><span style=\" text-decoration: underline;\">Blah</span></p>");
1092 }
1093
1094 {
1095 CREATE_DOC_AND_CURSOR();
1096
1097 QTextCharFormat fmt;
1098 fmt.setForeground(QColor("#00ff00"));
1099 cursor.insertText(text: "Blah", format: fmt);
1100
1101 QTest::newRow(dataTag: "color") << QTextDocumentFragment(&doc)
1102 << QString("<p DEFAULTBLOCKSTYLE><span style=\" color:#00ff00;\">Blah</span></p>");
1103 }
1104
1105 {
1106 CREATE_DOC_AND_CURSOR();
1107
1108 QTextCharFormat fmt;
1109 fmt.setForeground(QColor(0, 255, 0, 51));
1110 cursor.insertText(text: "Blah", format: fmt);
1111
1112 QTest::newRow(dataTag: "color-rgba") << QTextDocumentFragment(&doc)
1113 << QString("<p DEFAULTBLOCKSTYLE><span style=\" color:rgba(0,255,0,0.2);\">Blah</span></p>");
1114 }
1115
1116 {
1117 CREATE_DOC_AND_CURSOR();
1118
1119 QTextCharFormat fmt;
1120 fmt.setForeground(QColor(0, 255, 0, 0));
1121 cursor.insertText(text: "Blah", format: fmt);
1122
1123 QTest::newRow(dataTag: "color-transparent") << QTextDocumentFragment(&doc)
1124 << QString("<p DEFAULTBLOCKSTYLE><span style=\" color:transparent;\">Blah</span></p>");
1125 }
1126
1127 {
1128 CREATE_DOC_AND_CURSOR();
1129
1130 QTextCharFormat fmt;
1131 fmt.setBackground(QColor("#00ff00"));
1132 cursor.insertText(text: "Blah", format: fmt);
1133
1134 QTest::newRow(dataTag: "span-bgcolor") << QTextDocumentFragment(&doc)
1135 << QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Blah</span></p>");
1136 }
1137
1138 {
1139 CREATE_DOC_AND_CURSOR();
1140
1141 QTextCharFormat fmt;
1142 fmt.setBackground(QColor(0, 255, 0, 51));
1143 cursor.insertText(text: "Blah", format: fmt);
1144
1145 QTest::newRow(dataTag: "span-bgcolor-rgba") << QTextDocumentFragment(&doc)
1146 << QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:rgba(0,255,0,0.2);\">Blah</span></p>");
1147 }
1148
1149 {
1150 CREATE_DOC_AND_CURSOR();
1151
1152 QTextCharFormat fmt;
1153 fmt.setBackground(QColor(0, 255, 0, 0));
1154 cursor.insertText(text: "Blah", format: fmt);
1155
1156 QTest::newRow(dataTag: "span-bgcolor-transparent") << QTextDocumentFragment(&doc)
1157 << QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:transparent;\">Blah</span></p>");
1158 }
1159
1160 {
1161 CREATE_DOC_AND_CURSOR();
1162
1163 QTextCharFormat fmt;
1164 fmt.setVerticalAlignment(QTextCharFormat::AlignSubScript);
1165 cursor.insertText(text: "Blah", format: fmt);
1166
1167 QTest::newRow(dataTag: "valign-sub") << QTextDocumentFragment(&doc)
1168 << QString("<p DEFAULTBLOCKSTYLE><span style=\" vertical-align:sub;\">Blah</span></p>");
1169
1170 }
1171
1172 {
1173 CREATE_DOC_AND_CURSOR();
1174
1175 QTextCharFormat fmt;
1176 fmt.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
1177 cursor.insertText(text: "Blah", format: fmt);
1178
1179 QTest::newRow(dataTag: "valign-super") << QTextDocumentFragment(&doc)
1180 << QString("<p DEFAULTBLOCKSTYLE><span style=\" vertical-align:super;\">Blah</span></p>");
1181
1182 }
1183
1184 {
1185 CREATE_DOC_AND_CURSOR();
1186
1187 QTextCharFormat fmt;
1188 fmt.setAnchor(true);
1189 fmt.setAnchorNames({"blub"});
1190 cursor.insertText(text: "Blah", format: fmt);
1191
1192 QTest::newRow(dataTag: "named anchor") << QTextDocumentFragment(&doc)
1193 << QString("<p DEFAULTBLOCKSTYLE><a name=\"blub\"></a>Blah</p>");
1194 }
1195
1196 {
1197 CREATE_DOC_AND_CURSOR();
1198
1199 QTextCharFormat fmt;
1200 fmt.setAnchor(true);
1201 fmt.setAnchorHref("http://www.kde.org/");
1202 cursor.insertText(text: "Blah", format: fmt);
1203
1204 QTest::newRow(dataTag: "href anchor") << QTextDocumentFragment(&doc)
1205 << QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/\">Blah</a></p>");
1206 }
1207
1208 {
1209 CREATE_DOC_AND_CURSOR();
1210
1211 QTextCharFormat fmt;
1212 fmt.setAnchor(true);
1213 fmt.setAnchorHref("http://www.kde.org/?a=1&b=2");
1214 cursor.insertText(text: "Blah", format: fmt);
1215
1216 QTest::newRow(dataTag: "href anchor with &") << QTextDocumentFragment(&doc)
1217 << QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/?a=1&amp;b=2\">Blah</a></p>");
1218 }
1219
1220 {
1221 CREATE_DOC_AND_CURSOR();
1222
1223 QTextCharFormat fmt;
1224 fmt.setAnchor(true);
1225 fmt.setAnchorHref("http://www.kde.org/?a='&b=\"");
1226 cursor.insertText(text: "Blah", format: fmt);
1227
1228 QTest::newRow(dataTag: "href anchor with ' and \"") << QTextDocumentFragment(&doc)
1229 << QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/?a='&amp;b=&quot;\">Blah</a></p>");
1230 }
1231
1232 {
1233 CREATE_DOC_AND_CURSOR();
1234
1235 cursor.insertTable(rows: 2, cols: 2);
1236
1237 QTest::newRow(dataTag: "simpletable") << QTextDocumentFragment(&doc)
1238 << QString("<table border=\"1\" cellspacing=\"2\">"
1239 "\n<tr>\n<td></td>\n<td></td></tr>"
1240 "\n<tr>\n<td></td>\n<td></td></tr>"
1241 "</table>");
1242 }
1243
1244 {
1245 CREATE_DOC_AND_CURSOR();
1246
1247 QTextTable *table = cursor.insertTable(rows: 1, cols: 4);
1248 table->mergeCells(row: 0, col: 0, numRows: 1, numCols: 2);
1249 table->mergeCells(row: 0, col: 2, numRows: 1, numCols: 2);
1250
1251 QTest::newRow(dataTag: "tablespans") << QTextDocumentFragment(&doc)
1252 << QString("<table border=\"1\" cellspacing=\"2\">"
1253 "\n<tr>\n<td colspan=\"2\"></td>\n<td colspan=\"2\"></td></tr>"
1254 "</table>");
1255 }
1256
1257 {
1258 CREATE_DOC_AND_CURSOR();
1259
1260 QTextTableFormat fmt;
1261 fmt.setBorder(1);
1262 fmt.setCellSpacing(3);
1263 fmt.setCellPadding(3);
1264 fmt.setBackground(QColor("#ff00ff"));
1265 fmt.setWidth(QTextLength(QTextLength::PercentageLength, 50));
1266 fmt.setAlignment(Qt::AlignHCenter);
1267 fmt.setPosition(QTextFrameFormat::FloatRight);
1268 cursor.insertTable(rows: 2, cols: 2, format: fmt);
1269
1270 QTest::newRow(dataTag: "tableattrs") << QTextDocumentFragment(&doc)
1271 << QString("<table border=\"1\" style=\" float: right;\" align=\"center\" width=\"50%\" cellspacing=\"3\" cellpadding=\"3\" bgcolor=\"#ff00ff\">"
1272 "\n<tr>\n<td></td>\n<td></td></tr>"
1273 "\n<tr>\n<td></td>\n<td></td></tr>"
1274 "</table>");
1275 }
1276
1277 {
1278 CREATE_DOC_AND_CURSOR();
1279
1280 QTextTableFormat fmt;
1281 fmt.setBorder(1);
1282 fmt.setCellSpacing(3);
1283 fmt.setCellPadding(3);
1284 fmt.setBackground(QColor("#ff00ff"));
1285 fmt.setWidth(QTextLength(QTextLength::PercentageLength, 50));
1286 fmt.setAlignment(Qt::AlignHCenter);
1287 fmt.setPosition(QTextFrameFormat::FloatRight);
1288 fmt.setLeftMargin(25);
1289 fmt.setBottomMargin(35);
1290 cursor.insertTable(rows: 2, cols: 2, format: fmt);
1291
1292 QTest::newRow(dataTag: "tableattrs2") << QTextDocumentFragment(&doc)
1293 << QString("<table border=\"1\" style=\" float: right; margin-top:0px; margin-bottom:35px; margin-left:25px; margin-right:0px;\" align=\"center\" width=\"50%\" cellspacing=\"3\" cellpadding=\"3\" bgcolor=\"#ff00ff\">"
1294 "\n<tr>\n<td></td>\n<td></td></tr>"
1295 "\n<tr>\n<td></td>\n<td></td></tr>"
1296 "</table>");
1297 }
1298
1299 {
1300 CREATE_DOC_AND_CURSOR();
1301
1302 QTextTableFormat fmt;
1303 fmt.setHeaderRowCount(2);
1304 cursor.insertTable(rows: 4, cols: 2, format: fmt);
1305
1306 QTest::newRow(dataTag: "tableheader") << QTextDocumentFragment(&doc)
1307 << QString("<table border=\"1\" cellspacing=\"2\">"
1308 "<thead>\n<tr>\n<td></td>\n<td></td></tr>"
1309 "\n<tr>\n<td></td>\n<td></td></tr></thead>"
1310 "\n<tr>\n<td></td>\n<td></td></tr>"
1311 "\n<tr>\n<td></td>\n<td></td></tr>"
1312 "</table>");
1313 }
1314
1315 {
1316 CREATE_DOC_AND_CURSOR();
1317
1318 QTextTable *table = cursor.insertTable(rows: 2, cols: 2);
1319 QTextTable *subTable = table->cellAt(row: 0, col: 1).firstCursorPosition().insertTable(rows: 1, cols: 1);
1320 subTable->cellAt(row: 0, col: 0).firstCursorPosition().insertText(text: "Hey");
1321
1322 QTest::newRow(dataTag: "nestedtable") << QTextDocumentFragment(&doc)
1323 << QString("<table border=\"1\" cellspacing=\"2\">"
1324 "\n<tr>\n<td></td>\n<td>\n<table border=\"1\" cellspacing=\"2\">\n<tr>\n<td>\n<p DEFAULTBLOCKSTYLE>Hey</p></td></tr></table></td></tr>"
1325 "\n<tr>\n<td></td>\n<td></td></tr>"
1326 "</table>");
1327 }
1328
1329 {
1330 CREATE_DOC_AND_CURSOR();
1331
1332 QTextTableFormat fmt;
1333 QVector<QTextLength> widths;
1334 widths.append(t: QTextLength());
1335 widths.append(t: QTextLength(QTextLength::PercentageLength, 30));
1336 widths.append(t: QTextLength(QTextLength::FixedLength, 40));
1337 fmt.setColumnWidthConstraints(widths);
1338 cursor.insertTable(rows: 1, cols: 3, format: fmt);
1339
1340 QTest::newRow(dataTag: "colwidths") << QTextDocumentFragment(&doc)
1341 << QString("<table border=\"1\" cellspacing=\"2\">"
1342 "\n<tr>\n<td></td>\n<td width=\"30%\"></td>\n<td width=\"40\"></td></tr>"
1343 "</table>");
1344 }
1345
1346 // ### rowspan/colspan tests, once texttable api for that is back again
1347 //
1348 {
1349 CREATE_DOC_AND_CURSOR();
1350
1351 QTextTable *table = cursor.insertTable(rows: 1, cols: 1);
1352 QTextCursor cellCurs = table->cellAt(row: 0, col: 0).firstCursorPosition();
1353 QTextCharFormat fmt;
1354 fmt.setBackground(QColor("#ffffff"));
1355 cellCurs.mergeBlockCharFormat(modifier: fmt);
1356
1357 QTest::newRow(dataTag: "cellproperties") << QTextDocumentFragment(&doc)
1358 << QString("<table border=\"1\" cellspacing=\"2\">"
1359 "\n<tr>\n<td bgcolor=\"#ffffff\"></td></tr>"
1360 "</table>");
1361 }
1362
1363 {
1364 CREATE_DOC_AND_CURSOR();
1365
1366 // ### fixme: use programmatic api as soon as we can create floats through it
1367 const char html[] = "<html><body>Blah<img src=\"image.png\" width=\"10\" height=\"20\" style=\"float: right;\" />Blubb</body></html>";
1368
1369 QTest::newRow(dataTag: "image") << QTextDocumentFragment::fromHtml(html: QString::fromLatin1(str: html))
1370 << QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah<img src=\"image.png\" width=\"10\" height=\"20\" style=\"float: right;\" />Blubb</p>");
1371 }
1372
1373 {
1374 CREATE_DOC_AND_CURSOR();
1375
1376 QTextImageFormat fmt;
1377 fmt.setName("foo");
1378 fmt.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1379 cursor.insertImage(format: fmt);
1380
1381 QTest::newRow(dataTag: "image-align-middle") << QTextDocumentFragment(&doc)
1382 << QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" style=\"vertical-align: middle;\" /></p>");
1383 }
1384
1385 {
1386 CREATE_DOC_AND_CURSOR();
1387
1388 QTextImageFormat fmt;
1389 fmt.setName("foo");
1390 fmt.setVerticalAlignment(QTextCharFormat::AlignTop);
1391 cursor.insertImage(format: fmt);
1392
1393 QTest::newRow(dataTag: "image-align-top") << QTextDocumentFragment(&doc)
1394 << QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" style=\"vertical-align: top;\" /></p>");
1395 }
1396
1397 {
1398 CREATE_DOC_AND_CURSOR();
1399
1400 QTextImageFormat fmt;
1401 fmt.setName("foo");
1402 cursor.insertImage(format: fmt);
1403 cursor.insertImage(format: fmt);
1404
1405 QTest::newRow(dataTag: "2images") << QTextDocumentFragment(&doc)
1406 << QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" /><img src=\"foo\" /></p>");
1407 }
1408
1409 {
1410 CREATE_DOC_AND_CURSOR();
1411
1412 QString txt = QLatin1String("Blah");
1413 txt += QChar::LineSeparator;
1414 txt += QLatin1String("Bar");
1415 cursor.insertText(text: txt);
1416
1417 QTest::newRow(dataTag: "linebreaks") << QTextDocumentFragment(&doc)
1418 << QString("<p DEFAULTBLOCKSTYLE>Blah<br />Bar</p>");
1419 }
1420
1421 {
1422 CREATE_DOC_AND_CURSOR();
1423
1424 QTextBlockFormat fmt;
1425 fmt.setTopMargin(10);
1426 fmt.setBottomMargin(20);
1427 fmt.setLeftMargin(30);
1428 fmt.setRightMargin(40);
1429 cursor.insertBlock(format: fmt);
1430 cursor.insertText(text: "Blah");
1431
1432 QTest::newRow(dataTag: "blockmargins") << QTextDocumentFragment(&doc)
1433 <<
1434 QString("EMPTYBLOCK") +
1435 QString("<p style=\" margin-top:10px; margin-bottom:20px; margin-left:30px; margin-right:40px; -qt-block-indent:0; text-indent:0px;\">Blah</p>");
1436 }
1437
1438 {
1439 CREATE_DOC_AND_CURSOR();
1440
1441 QTextList *list = cursor.insertList(style: QTextListFormat::ListDisc);
1442 cursor.insertText(text: "Blubb");
1443 cursor.insertBlock();
1444 cursor.insertText(text: "Blah");
1445 QCOMPARE(list->count(), 2);
1446
1447 QTest::newRow(dataTag: "lists") << QTextDocumentFragment(&doc)
1448 <<
1449 QString("EMPTYBLOCK") +
1450 QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li DEFAULTBLOCKSTYLE>Blubb</li>\n<li DEFAULTBLOCKSTYLE>Blah</li></ul>");
1451 }
1452
1453 {
1454 CREATE_DOC_AND_CURSOR();
1455
1456 QTextList *list = cursor.insertList(style: QTextListFormat::ListDisc);
1457 cursor.insertText(text: "Blubb");
1458
1459 cursor.insertBlock();
1460
1461 QTextCharFormat blockCharFmt;
1462 blockCharFmt.setForeground(QColor("#0000ff"));
1463 cursor.mergeBlockCharFormat(modifier: blockCharFmt);
1464
1465 QTextCharFormat fmt;
1466 fmt.setForeground(QColor("#ff0000"));
1467 cursor.insertText(text: "Blah", format: fmt);
1468 QCOMPARE(list->count(), 2);
1469
1470 QTest::newRow(dataTag: "charfmt-for-list-item") << QTextDocumentFragment(&doc)
1471 <<
1472 QString("EMPTYBLOCK") +
1473 QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li DEFAULTBLOCKSTYLE>Blubb</li>\n<li style=\" color:#0000ff;\" DEFAULTBLOCKSTYLE><span style=\" color:#ff0000;\">Blah</span></li></ul>");
1474 }
1475
1476 {
1477 CREATE_DOC_AND_CURSOR();
1478
1479 QTextBlockFormat fmt;
1480 fmt.setIndent(3);
1481 fmt.setTextIndent(30);
1482 cursor.insertBlock(format: fmt);
1483 cursor.insertText(text: "Test");
1484
1485 QTest::newRow(dataTag: "block-indent") << QTextDocumentFragment(&doc)
1486 <<
1487 QString("EMPTYBLOCK") +
1488 QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:3; text-indent:30px;\">Test</p>");
1489 }
1490
1491 {
1492 CREATE_DOC_AND_CURSOR();
1493
1494 QTextListFormat fmt;
1495 fmt.setStyle(QTextListFormat::ListDisc);
1496 fmt.setIndent(4);
1497 cursor.insertList(format: fmt);
1498 cursor.insertText(text: "Blah");
1499
1500 QTest::newRow(dataTag: "list-indent") << QTextDocumentFragment(&doc)
1501 <<
1502 QString("EMPTYBLOCK") +
1503 QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 4;\"><li DEFAULTBLOCKSTYLE>Blah</li></ul>");
1504 }
1505
1506 {
1507 CREATE_DOC_AND_CURSOR();
1508
1509 cursor.insertBlock();
1510
1511
1512 QTest::newRow(dataTag: "emptyblock") << QTextDocumentFragment(&doc)
1513 // after insertBlock() we /do/ have two blocks in the document, so also expect
1514 // these in the html output
1515 << QString("EMPTYBLOCK") + QString("EMPTYBLOCK");
1516 }
1517
1518 {
1519 CREATE_DOC_AND_CURSOR();
1520
1521 // if you press enter twice in an empty textedit and then insert 'Test'
1522 // you actually get three visible paragraphs, two empty leading ones and
1523 // a third with the actual text. the corresponding html representation
1524 // therefore should also contain three paragraphs.
1525
1526 cursor.insertBlock();
1527 QTextCharFormat fmt;
1528 fmt.setForeground(QColor("#00ff00"));
1529 fmt.setProperty(propertyId: QTextFormat::FontSizeIncrement, value: 1);
1530 cursor.mergeBlockCharFormat(modifier: fmt);
1531
1532 fmt.setProperty(propertyId: QTextFormat::FontSizeIncrement, value: 2);
1533 cursor.insertText(text: "Test", format: fmt);
1534
1535 QTest::newRow(dataTag: "blockcharfmt") << QTextDocumentFragment(&doc)
1536 << QString("EMPTYBLOCK<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" font-size:x-large; color:#00ff00;\">Test</span></p>");
1537 }
1538
1539 {
1540 CREATE_DOC_AND_CURSOR();
1541
1542 QTextCharFormat fmt;
1543 fmt.setForeground(QColor("#00ff00"));
1544 cursor.setBlockCharFormat(fmt);
1545 fmt.setForeground(QColor("#0000ff"));
1546 cursor.insertText(text: "Test", format: fmt);
1547
1548 QTest::newRow(dataTag: "blockcharfmt2") << QTextDocumentFragment(&doc)
1549 << QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><span style=\" color:#0000ff;\">Test</span></p>");
1550 }
1551
1552 {
1553 QTest::newRow(dataTag: "horizontal-ruler") << QTextDocumentFragment::fromHtml(html: "<hr />")
1554 <<
1555 QString("EMPTYBLOCK") +
1556 QString("<hr />");
1557 }
1558 {
1559 QTest::newRow(dataTag: "horizontal-ruler-with-width") << QTextDocumentFragment::fromHtml(html: "<hr width=\"50%\"/>")
1560 <<
1561 QString("EMPTYBLOCK") +
1562 QString("<hr width=\"50%\"/>");
1563 }
1564 {
1565 CREATE_DOC_AND_CURSOR();
1566
1567 QTextFrame *mainFrame = cursor.currentFrame();
1568
1569 QTextFrameFormat ffmt;
1570 ffmt.setBorder(1);
1571 ffmt.setPosition(QTextFrameFormat::FloatRight);
1572 ffmt.setMargin(2);
1573 ffmt.setWidth(100);
1574 ffmt.setHeight(50);
1575 ffmt.setBackground(QColor("#00ff00"));
1576 cursor.insertFrame(format: ffmt);
1577 cursor.insertText(text: "Hello World");
1578 cursor = mainFrame->lastCursorPosition();
1579
1580 QTest::newRow(dataTag: "frame") << QTextDocumentFragment(&doc)
1581 << QString("<table border=\"1\" style=\"-qt-table-type: frame; float: right; margin-top:2px; margin-bottom:2px; margin-left:2px; margin-right:2px;\" width=\"100\" height=\"50\" bgcolor=\"#00ff00\">\n<tr>\n<td style=\"border: none;\">\n<p DEFAULTBLOCKSTYLE>Hello World</p></td></tr></table>");
1582 }
1583
1584 {
1585 CREATE_DOC_AND_CURSOR();
1586
1587 QTextCharFormat fmt;
1588 fmt.setForeground(QColor("#00ff00"));
1589// fmt.setBackground(QColor("#0000ff"));
1590 cursor.setBlockCharFormat(fmt);
1591
1592 fmt.setForeground(QBrush());
1593// fmt.setBackground(QBrush());
1594 cursor.insertText(text: "Test", format: fmt);
1595
1596// QTest::newRow("nostylebrush") << QTextDocumentFragment(&doc) << QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; color:#00ff00; -qt-blockcharfmt-background-color:#0000ff;\">Test</p>");
1597 QTest::newRow(dataTag: "nostylebrush") << QTextDocumentFragment(&doc) << QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Test</p>");
1598 }
1599
1600 {
1601 CREATE_DOC_AND_CURSOR();
1602
1603 QTextTable *table = cursor.insertTable(rows: 2, cols: 2);
1604 table->mergeCells(row: 0, col: 0, numRows: 1, numCols: 2);
1605 QTextTableFormat fmt = table->format();
1606 QVector<QTextLength> widths;
1607 widths.append(t: QTextLength(QTextLength::FixedLength, 20));
1608 widths.append(t: QTextLength(QTextLength::FixedLength, 40));
1609 fmt.setColumnWidthConstraints(widths);
1610 table->setFormat(fmt);
1611
1612 QTest::newRow(dataTag: "mergedtablecolwidths") << QTextDocumentFragment(&doc)
1613 << QString("<table border=\"1\" cellspacing=\"2\">"
1614 "\n<tr>\n<td colspan=\"2\"></td></tr>"
1615 "\n<tr>\n<td width=\"20\"></td>\n<td width=\"40\"></td></tr>"
1616 "</table>");
1617 }
1618
1619 {
1620 CREATE_DOC_AND_CURSOR();
1621
1622 QTextCharFormat fmt;
1623
1624 cursor.insertText(text: "Blah\nGreen yellow green");
1625 cursor.setPosition(pos: 0);
1626 cursor.setPosition(pos: 23, mode: QTextCursor::KeepAnchor);
1627 fmt.setBackground(Qt::green);
1628 cursor.mergeCharFormat(modifier: fmt);
1629 cursor.clearSelection();
1630 cursor.setPosition(pos: 11);
1631 cursor.setPosition(pos: 17, mode: QTextCursor::KeepAnchor);
1632 fmt.setBackground(Qt::yellow);
1633 cursor.mergeCharFormat(modifier: fmt);
1634 cursor.clearSelection();
1635
1636 QTest::newRow(dataTag: "multiparagraph-bgcolor") << QTextDocumentFragment(&doc)
1637 << QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Blah</span></p>\n"
1638 "<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Green </span>"
1639 "<span style=\" background-color:#ffff00;\">yellow</span>"
1640 "<span style=\" background-color:#00ff00;\"> green</span></p>");
1641 }
1642
1643 {
1644 CREATE_DOC_AND_CURSOR();
1645
1646 QTextBlockFormat fmt;
1647 fmt.setBackground(QColor("#0000ff"));
1648 cursor.insertBlock(format: fmt);
1649
1650 QTextCharFormat charfmt;
1651 charfmt.setBackground(QColor("#0000ff"));
1652 cursor.insertText(text: "Blah", format: charfmt);
1653
1654 QTest::newRow(dataTag: "nospan-bgcolor") << QTextDocumentFragment(&doc)
1655 << QString("EMPTYBLOCK") +
1656 QString("<p OPENDEFAULTBLOCKSTYLE background-color:#0000ff;\"><span style=\" background-color:#0000ff;\">Blah</span></p>");
1657 }
1658
1659 {
1660 CREATE_DOC_AND_CURSOR();
1661
1662 QTextTable *table = cursor.insertTable(rows: 2, cols: 2);
1663 QTextCharFormat fmt = table->cellAt(row: 0, col: 0).format();
1664 fmt.setVerticalAlignment(QTextCharFormat::AlignMiddle);
1665 table->cellAt(row: 0, col: 0).setFormat(fmt);
1666 fmt = table->cellAt(row: 0, col: 1).format();
1667 fmt.setVerticalAlignment(QTextCharFormat::AlignTop);
1668 table->cellAt(row: 0, col: 1).setFormat(fmt);
1669 fmt = table->cellAt(row: 1, col: 0).format();
1670 fmt.setVerticalAlignment(QTextCharFormat::AlignBottom);
1671 table->cellAt(row: 1, col: 0).setFormat(fmt);
1672
1673 table->cellAt(row: 0, col: 0).firstCursorPosition().insertText(text: "Blah");
1674
1675 QTest::newRow(dataTag: "table-vertical-alignment") << QTextDocumentFragment(&doc)
1676 << QString("<table border=\"1\" cellspacing=\"2\">"
1677 "\n<tr>\n<td style=\" vertical-align:middle;\">\n"
1678 "<p DEFAULTBLOCKSTYLE>Blah</p></td>"
1679 "\n<td style=\" vertical-align:top;\"></td></tr>"
1680 "\n<tr>\n<td style=\" vertical-align:bottom;\"></td>"
1681 "\n<td></td></tr>"
1682 "</table>");
1683 }
1684
1685 {
1686 CREATE_DOC_AND_CURSOR();
1687
1688 QTextTable *table = cursor.insertTable(rows: 2, cols: 2);
1689 QTextTableCellFormat fmt = table->cellAt(row: 0, col: 0).format().toTableCellFormat();
1690 fmt.setLeftPadding(1);
1691 table->cellAt(row: 0, col: 0).setFormat(fmt);
1692 fmt = table->cellAt(row: 0, col: 1).format().toTableCellFormat();
1693 fmt.setRightPadding(1);
1694 table->cellAt(row: 0, col: 1).setFormat(fmt);
1695 fmt = table->cellAt(row: 1, col: 0).format().toTableCellFormat();
1696 fmt.setTopPadding(1);
1697 table->cellAt(row: 1, col: 0).setFormat(fmt);
1698 fmt = table->cellAt(row: 1, col: 1).format().toTableCellFormat();
1699 fmt.setBottomPadding(1);
1700 table->cellAt(row: 1, col: 1).setFormat(fmt);
1701
1702 table->cellAt(row: 0, col: 0).firstCursorPosition().insertText(text: "Blah");
1703
1704 QTest::newRow(dataTag: "table-cell-paddings") << QTextDocumentFragment(&doc)
1705 << QString("<table border=\"1\" cellspacing=\"2\">"
1706 "\n<tr>\n<td style=\" padding-left:1;\">\n"
1707 "<p DEFAULTBLOCKSTYLE>Blah</p></td>"
1708 "\n<td style=\" padding-right:1;\"></td></tr>"
1709 "\n<tr>\n<td style=\" padding-top:1;\"></td>"
1710 "\n<td style=\" padding-bottom:1;\"></td></tr>"
1711 "</table>");
1712 }
1713
1714 {
1715 CREATE_DOC_AND_CURSOR();
1716
1717 QTextTableFormat fmt;
1718 fmt.setBorderBrush(QColor("#0000ff"));
1719 fmt.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
1720 cursor.insertTable(rows: 2, cols: 2, format: fmt);
1721
1722 QTest::newRow(dataTag: "tableborder") << QTextDocumentFragment(&doc)
1723 << QString("<table border=\"1\" style=\" border-color:#0000ff; border-style:solid;\" cellspacing=\"2\">"
1724 "\n<tr>\n<td></td>\n<td></td></tr>"
1725 "\n<tr>\n<td></td>\n<td></td></tr>"
1726 "</table>");
1727 }
1728
1729 {
1730 CREATE_DOC_AND_CURSOR();
1731
1732 cursor.insertBlock();
1733 cursor.insertText(text: "Foo");
1734
1735 cursor.block().setUserState(42);
1736
1737 QTest::newRow(dataTag: "userstate") << QTextDocumentFragment(&doc)
1738 << QString("EMPTYBLOCK") +
1739 QString("<p OPENDEFAULTBLOCKSTYLE -qt-user-state:42;\">Foo</p>");
1740 }
1741
1742 {
1743 CREATE_DOC_AND_CURSOR();
1744
1745 QTextBlockFormat blockFmt;
1746 blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
1747
1748 cursor.insertBlock(format: blockFmt);
1749 cursor.insertText(text: "Foo");
1750
1751 blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore | QTextFormat::PageBreak_AlwaysAfter);
1752
1753 cursor.insertBlock(format: blockFmt);
1754 cursor.insertText(text: "Bar");
1755
1756 QTextTableFormat tableFmt;
1757 tableFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter);
1758
1759 cursor.insertTable(rows: 1, cols: 1, format: tableFmt);
1760
1761 QTest::newRow(dataTag: "pagebreak") << QTextDocumentFragment(&doc)
1762 << QString("EMPTYBLOCK") +
1763 QString("<p OPENDEFAULTBLOCKSTYLE page-break-before:always;\">Foo</p>"
1764 "\n<p OPENDEFAULTBLOCKSTYLE page-break-before:always; page-break-after:always;\">Bar</p>"
1765 "\n<table border=\"1\" style=\" page-break-after:always;\" cellspacing=\"2\">\n<tr>\n<td></td></tr></table>");
1766 }
1767
1768 {
1769 CREATE_DOC_AND_CURSOR();
1770
1771 QTextListFormat listFmt;
1772 listFmt.setStyle(QTextListFormat::ListDisc);
1773
1774 cursor.insertList(format: listFmt);
1775 cursor.insertText(text: "Blah");
1776
1777 QTest::newRow(dataTag: "list-ul-margin") << QTextDocumentFragment(&doc)
1778 << QString("EMPTYBLOCK") +
1779 QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li DEFAULTBLOCKSTYLE>Blah</li></ul>");
1780 }
1781 {
1782 CREATE_DOC_AND_CURSOR();
1783 const QString listHtml = "<ul><li>item-1</li><li>item-2<ul><li>item-2.1</li><li>item-2.2"
1784 "<ul><li>item-2.2.1</li></ul></li><li>item-2.3<ul><li>item-2.3.1"
1785 "</li></ul></li></ul></li><li>item-3</li></ul>";
1786 cursor.insertHtml(html: listHtml);
1787
1788 QTest::newRow(dataTag: "nested-lists-one") << QTextDocumentFragment(&doc)
1789 << QString("<ul DEFAULTULSTYLE 1;\"><li style=\" margin-top:12px; margin-bottom:0px; "
1790 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
1791 "item-1</li>\n<li DEFAULTBLOCKSTYLE>item-2\n<ul DEFAULTULSTYLE 2;\"><li "
1792 "DEFAULTBLOCKSTYLE>item-2.1</li>\n<li DEFAULTBLOCKSTYLE>item-2.2\n<ul "
1793 "DEFAULTULSTYLE 3;\"><li DEFAULTBLOCKSTYLE>item-2.2.1</li></ul></li>\n"
1794 "<li DEFAULTBLOCKSTYLE>item-2.3\n<ul DEFAULTULSTYLE 3;\"><li DEFAULTBLOCKSTYLE>"
1795 "item-2.3.1</li></ul></li></ul></li>\n<li DEFAULTLASTLISTYLE>item-3</li></ul>");
1796 }
1797 {
1798 CREATE_DOC_AND_CURSOR();
1799 const QString listHtml = "<ul><li>item-1</li><li>item-2<ul><li>item-2.1</li></ul></li></ul>";
1800 cursor.insertHtml(html: listHtml);
1801
1802 QTest::newRow(dataTag: "nested-lists-two") << QTextDocumentFragment(&doc)
1803 << QString("<ul DEFAULTULSTYLE 1;\"><li style=\" margin-top:12px; margin-bottom:0px; "
1804 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
1805 "item-1</li>\n<li DEFAULTLASTLISTYLE>item-2\n<ul DEFAULTULSTYLE 2;\"><li "
1806 "DEFAULTBLOCKSTYLE>item-2.1</li></ul></li></ul>");
1807 }
1808 {
1809 CREATE_DOC_AND_CURSOR();
1810 const QString listHtml = "<ul><li>item-1</li><li>item-2<ul><li>item-2.1</li><li>item-2.2"
1811 "</li></ul></li></ul>";
1812 cursor.insertHtml(html: listHtml);
1813
1814 QTest::newRow(dataTag: "nested-lists-three") << QTextDocumentFragment(&doc)
1815 << QString("<ul DEFAULTULSTYLE 1;\"><li style=\" margin-top:12px; margin-bottom:0px; "
1816 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
1817 "item-1</li>\n<li DEFAULTLASTLISTYLE>item-2\n<ul DEFAULTULSTYLE 2;\"><li "
1818 "DEFAULTBLOCKSTYLE>item-2.1</li>\n<li DEFAULTBLOCKSTYLE>item-2.2</li></ul>"
1819 "</li></ul>");
1820 }
1821 {
1822 CREATE_DOC_AND_CURSOR();
1823 const QString listHtml = "<ul><li>item-1.1</li><li>item-1.2<li></ul>"
1824 "<ul><li>item-2.1</li></ul>";
1825 cursor.insertHtml(html: listHtml);
1826
1827 QTest::newRow(dataTag: "not-nested-list") << QTextDocumentFragment(&doc)
1828 << QString("<ul DEFAULTULSTYLE 1;\"><li style=\" margin-top:12px; margin-bottom:0px; "
1829 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">"
1830 "item-1.1</li>\n<li DEFAULTBLOCKSTYLE>item-1.2</li></ul>\n<ul DEFAULTULSTYLE 1;\">"
1831 "<li style=\" margin-top:12px; margin-bottom:12px; margin-left:0px; "
1832 "margin-right:0px; -qt-block-indent:0; text-indent:0px;\">item-2.1</li></ul>");
1833 }
1834}
1835
1836void tst_QTextDocument::toHtml()
1837{
1838 QFETCH(QTextDocumentFragment, input);
1839 QFETCH(QString, expectedOutput);
1840
1841 cursor.insertFragment(fragment: input);
1842
1843 expectedOutput.prepend(s: htmlHead);
1844
1845 expectedOutput.replace(before: "OPENDEFAULTBLOCKSTYLE", after: "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;");
1846 expectedOutput.replace(before: "DEFAULTBLOCKSTYLE", after: "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"");
1847 expectedOutput.replace(before: "EMPTYBLOCK", after: "<p style=\"-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><br /></p>\n");
1848 expectedOutput.replace(before: "DEFAULTULSTYLE", after: "style=\"margin-top: 0px; margin-bottom: 0px; "
1849 "margin-left: 0px; margin-right: 0px; -qt-list-indent:");
1850 expectedOutput.replace(before: "DEFAULTLASTLISTYLE", after: "style=\" margin-top:0px; margin-bottom:12px; "
1851 "margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"");
1852
1853 if (expectedOutput.endsWith(c: QLatin1Char('\n')))
1854 expectedOutput.chop(n: 1);
1855 expectedOutput.append(s: htmlTail);
1856
1857 QString output = doc->toHtml();
1858
1859 writeActualAndExpected(testTag: QTest::currentDataTag(), actual: output, expected: expectedOutput);
1860
1861 QCOMPARE(output, expectedOutput);
1862
1863 QDomDocument document;
1864#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1865 QEXPECT_FAIL("charfmt-for-list-item",
1866 "The attribute \"style\" is redefined in the generated HTML, which is not valid "
1867 "according to XML standard. The new QDomDocument implementation follows the XML "
1868 "standard.", Continue);
1869#endif
1870 QVERIFY2(document.setContent(output), "Output was not valid XML");
1871}
1872
1873void tst_QTextDocument::toHtml2()
1874{
1875 QTextDocument doc;
1876 doc.setHtml("<p>text <img src=\"\"> text</p>"); // 4 spaces before the second 'text'
1877 QTextBlock block = doc.firstBlock();
1878 QTextBlock::Iterator iter = block.begin();
1879 QTextFragment f = iter.fragment();
1880 QVERIFY(f.isValid());
1881 QCOMPARE(f.position(), 0);
1882 QCOMPARE(f.length(), 5);
1883 //qDebug() << block.text().mid(f.position(), f.length());
1884
1885 iter++;
1886 f = iter.fragment();
1887 QVERIFY(f.isValid());
1888 QCOMPARE(f.position(), 5);
1889 QCOMPARE(f.length(), 1);
1890 //qDebug() << block.text().mid(f.position(), f.length());
1891
1892 iter++;
1893 f = iter.fragment();
1894 //qDebug() << block.text().mid(f.position(), f.length());
1895 QVERIFY(f.isValid());
1896 QCOMPARE(f.position(), 6);
1897 QCOMPARE(f.length(), 5); // 1 space should be preserved.
1898 QCOMPARE(block.text().mid(f.position(), f.length()), QString(" text"));
1899
1900 doc.setHtml("<table><tr><td> foo</td></tr></table> text"); // 4 spaces before the second 'text'
1901 block = doc.firstBlock().next();
1902 //qDebug() << block.text();
1903 QCOMPARE(block.text(), QString("foo"));
1904
1905 block = block.next();
1906 //qDebug() << block.text();
1907 QCOMPARE(block.text(), QString("text"));
1908}
1909
1910void tst_QTextDocument::setFragmentMarkersInHtmlExport()
1911{
1912 {
1913 CREATE_DOC_AND_CURSOR();
1914
1915 cursor.insertText(text: "Leadin");
1916 const int startPos = cursor.position();
1917
1918 cursor.insertText(text: "Test");
1919 QTextCharFormat fmt;
1920 fmt.setForeground(QColor("#00ff00"));
1921 cursor.insertText(text: "Blah", format: fmt);
1922
1923 const int endPos = cursor.position();
1924 cursor.insertText(text: "Leadout", format: QTextCharFormat());
1925
1926 cursor.setPosition(pos: startPos);
1927 cursor.setPosition(pos: endPos, mode: QTextCursor::KeepAnchor);
1928 QTextDocumentFragment fragment(cursor);
1929
1930 QString expected = htmlHead;
1931 expected.replace(rx: QRegExp("<body.*>"), after: QString("<body>"));
1932 expected += QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><!--StartFragment-->Test<span style=\" color:#00ff00;\">Blah</span><!--EndFragment--></p>") + htmlTail;
1933 QCOMPARE(fragment.toHtml(), expected);
1934 }
1935 {
1936 CREATE_DOC_AND_CURSOR();
1937
1938 cursor.insertText(text: "Leadin");
1939 const int startPos = cursor.position();
1940
1941 cursor.insertText(text: "Test");
1942
1943 const int endPos = cursor.position();
1944 cursor.insertText(text: "Leadout", format: QTextCharFormat());
1945
1946 cursor.setPosition(pos: startPos);
1947 cursor.setPosition(pos: endPos, mode: QTextCursor::KeepAnchor);
1948 QTextDocumentFragment fragment(cursor);
1949
1950 QString expected = htmlHead;
1951 expected.replace(rx: QRegExp("<body.*>"), after: QString("<body>"));
1952 expected += QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"><!--StartFragment-->Test<!--EndFragment--></p>") + htmlTail;
1953 QCOMPARE(fragment.toHtml(), expected);
1954 }
1955}
1956
1957void tst_QTextDocument::toHtmlBodyBgColor()
1958{
1959 CREATE_DOC_AND_CURSOR();
1960
1961 cursor.insertText(text: "Blah");
1962
1963 QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
1964 fmt.setBackground(QColor("#0000ff"));
1965 doc.rootFrame()->setFrameFormat(fmt);
1966
1967 QString expectedHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
1968 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
1969 "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
1970 "p, li { white-space: pre-wrap; }\n"
1971 "</style></head>"
1972 "<body style=\" font-family:'%1'; font-size:%2; font-weight:%3; font-style:%4;\""
1973 " bgcolor=\"#0000ff\">\n"
1974 "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
1975 "</body></html>");
1976
1977 expectedHtml = expectedHtml
1978 .arg(a: defaultFont.family())
1979 .arg(a: cssFontSizeString(font: defaultFont))
1980 .arg(a: defaultFont.weight() * 8)
1981 .arg(a: (defaultFont.italic() ? "italic" : "normal"));
1982
1983 QCOMPARE(doc.toHtml(), expectedHtml);
1984}
1985
1986void tst_QTextDocument::toHtmlBodyBgColorRgba()
1987{
1988 CREATE_DOC_AND_CURSOR();
1989
1990 cursor.insertText(text: "Blah");
1991
1992 QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
1993 fmt.setBackground(QColor(255, 0, 0, 51));
1994 doc.rootFrame()->setFrameFormat(fmt);
1995
1996 QString expectedHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
1997 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
1998 "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
1999 "p, li { white-space: pre-wrap; }\n"
2000 "</style></head>"
2001 "<body style=\" font-family:'%1'; font-size:%2; font-weight:%3; font-style:%4;\""
2002 " bgcolor=\"rgba(255,0,0,0.2)\">\n"
2003 "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
2004 "</body></html>");
2005
2006 expectedHtml = expectedHtml.arg(a: defaultFont.family())
2007 .arg(a: cssFontSizeString(font: defaultFont))
2008 .arg(a: defaultFont.weight() * 8)
2009 .arg(a: (defaultFont.italic() ? "italic" : "normal"));
2010
2011 QCOMPARE(doc.toHtml(), expectedHtml);
2012}
2013
2014void tst_QTextDocument::toHtmlBodyBgColorTransparent()
2015{
2016 CREATE_DOC_AND_CURSOR();
2017
2018 cursor.insertText(text: "Blah");
2019
2020 QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
2021 fmt.setBackground(QColor(255, 0, 0, 0));
2022 doc.rootFrame()->setFrameFormat(fmt);
2023
2024 QString expectedHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
2025 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
2026 "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
2027 "p, li { white-space: pre-wrap; }\n"
2028 "</style></head>"
2029 "<body style=\" font-family:'%1'; font-size:%2; font-weight:%3; font-style:%4;\""
2030 " bgcolor=\"transparent\">\n"
2031 "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
2032 "</body></html>");
2033
2034 expectedHtml = expectedHtml
2035 .arg(a: defaultFont.family())
2036 .arg(a: cssFontSizeString(font: defaultFont))
2037 .arg(a: defaultFont.weight() * 8)
2038 .arg(a: (defaultFont.italic() ? "italic" : "normal"));
2039
2040 QCOMPARE(doc.toHtml(), expectedHtml);
2041}
2042
2043void tst_QTextDocument::toHtmlRootFrameProperties()
2044{
2045 CREATE_DOC_AND_CURSOR();
2046
2047 QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
2048 fmt.setTopMargin(10);
2049 fmt.setLeftMargin(10);
2050 fmt.setBorder(2);
2051 doc.rootFrame()->setFrameFormat(fmt);
2052
2053 cursor.insertText(text: "Blah");
2054
2055 QString expectedOutput("<table border=\"2\" style=\"-qt-table-type: root; margin-top:10px; "
2056 "margin-bottom:4px; margin-left:10px; margin-right:4px;\">\n"
2057 "<tr>\n<td style=\"border: none;\">\n"
2058 "<p DEFAULTBLOCKSTYLE>Blah</p></td></tr></table>");
2059
2060 expectedOutput.prepend(s: htmlHead);
2061 expectedOutput.replace(before: "DEFAULTBLOCKSTYLE", after: "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"");
2062 expectedOutput.append(s: htmlTail);
2063
2064 writeActualAndExpected(testTag: QTest::currentTestFunction(), actual: doc.toHtml(), expected: expectedOutput);
2065
2066 QCOMPARE(doc.toHtml(), expectedOutput);
2067}
2068
2069void tst_QTextDocument::toHtmlLineHeightProperties()
2070{
2071 CREATE_DOC_AND_CURSOR();
2072
2073 QTextBlock block = doc.firstBlock();
2074 QTextBlockFormat blockFormat = block.blockFormat();
2075 blockFormat.setLineHeight(height: 200, heightType: QTextBlockFormat::ProportionalHeight);
2076 cursor.setBlockFormat(blockFormat);
2077
2078 cursor.insertText(text: "Blah");
2079 QString expectedOutput("<p DEFAULTBLOCKSTYLE line-height:200%;\">Blah</p>");
2080
2081 expectedOutput.prepend(s: htmlHead);
2082 expectedOutput.replace(before: "DEFAULTBLOCKSTYLE", after: "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;");
2083 expectedOutput.append(s: htmlTail);
2084
2085 QCOMPARE(doc.toHtml(), expectedOutput);
2086}
2087
2088void tst_QTextDocument::toHtmlDefaultFontSpacingProperties()
2089{
2090 CREATE_DOC_AND_CURSOR();
2091
2092 cursor.insertText(text: "Blah");
2093
2094 QFont fnt = doc.defaultFont();
2095 fnt.setLetterSpacing(type: QFont::AbsoluteSpacing, spacing: 13);
2096 fnt.setWordSpacing(15);
2097 doc.setDefaultFont(fnt);
2098
2099 QString expectedOutput = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
2100 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
2101 "<html><head><meta name=\"qrichtext\" content=\"1\" />"
2102 "<style type=\"text/css\">\n"
2103 "p, li { white-space: pre-wrap; }\n"
2104 "</style></head>"
2105 "<body style=\" font-family:'%1'; font-size:%2; "
2106 "font-weight:%3; font-style:%4; letter-spacing:13px; "
2107 "word-spacing:15px;\">\n"
2108 "<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
2109 "</body></html>");
2110 expectedOutput = expectedOutput.arg(a: defaultFont.family())
2111 .arg(a: cssFontSizeString(font: defaultFont))
2112 .arg(a: defaultFont.weight() * 8)
2113 .arg(a: (defaultFont.italic() ? "italic" : "normal"));
2114 QCOMPARE(doc.toHtml(), expectedOutput);
2115}
2116
2117void tst_QTextDocument::capitalizationHtmlInExport()
2118{
2119 doc->setPlainText("Test");
2120
2121 QRegExp re(".*span style=\"(.*)\">Test.*");
2122 QVERIFY(re.exactMatch(doc->toHtml()) == false); // no span
2123
2124 QTextCursor cursor(doc);
2125 cursor.setPosition(pos: 4, mode: QTextCursor::KeepAnchor);
2126 QTextCharFormat cf;
2127 cf.setFontCapitalization(QFont::SmallCaps);
2128 cursor.mergeCharFormat(modifier: cf);
2129
2130 const QString smallcaps = doc->toHtml();
2131 QVERIFY(re.exactMatch(doc->toHtml()));
2132 QCOMPARE(re.captureCount(), 1);
2133 QCOMPARE(re.cap(1).trimmed(), QString("font-variant:small-caps;"));
2134
2135 cf.setFontCapitalization(QFont::AllUppercase);
2136 cursor.mergeCharFormat(modifier: cf);
2137 const QString uppercase = doc->toHtml();
2138 QVERIFY(re.exactMatch(doc->toHtml()));
2139 QCOMPARE(re.captureCount(), 1);
2140 QCOMPARE(re.cap(1).trimmed(), QString("text-transform:uppercase;"));
2141
2142 cf.setFontCapitalization(QFont::AllLowercase);
2143 cursor.mergeCharFormat(modifier: cf);
2144 const QString lowercase = doc->toHtml();
2145 QVERIFY(re.exactMatch(doc->toHtml()));
2146 QCOMPARE(re.captureCount(), 1);
2147 QCOMPARE(re.cap(1).trimmed(), QString("text-transform:lowercase;"));
2148
2149 doc->setHtml(smallcaps);
2150 cursor.setPosition(pos: 1);
2151 QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::SmallCaps);
2152 doc->setHtml(uppercase);
2153 QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::AllUppercase);
2154 doc->setHtml(lowercase);
2155 QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::AllLowercase);
2156}
2157
2158void tst_QTextDocument::wordspacingHtmlExport()
2159{
2160 doc->setPlainText("Test");
2161
2162 QRegExp re(".*span style=\"(.*)\">Test.*");
2163 QVERIFY(re.exactMatch(doc->toHtml()) == false); // no span
2164
2165 QTextCursor cursor(doc);
2166 cursor.setPosition(pos: 4, mode: QTextCursor::KeepAnchor);
2167 QTextCharFormat cf;
2168 cf.setFontWordSpacing(4);
2169 cursor.mergeCharFormat(modifier: cf);
2170
2171 QVERIFY(re.exactMatch(doc->toHtml()));
2172 QCOMPARE(re.captureCount(), 1);
2173 QCOMPARE(re.cap(1).trimmed(), QString("word-spacing:4px;"));
2174
2175 cf.setFontWordSpacing(-8.5);
2176 cursor.mergeCharFormat(modifier: cf);
2177
2178 QVERIFY(re.exactMatch(doc->toHtml()));
2179 QCOMPARE(re.captureCount(), 1);
2180 QCOMPARE(re.cap(1).trimmed(), QString("word-spacing:-8.5px;"));
2181}
2182
2183class CursorPosSignalSpy : public QObject
2184{
2185 Q_OBJECT
2186public:
2187 CursorPosSignalSpy(QTextDocument *doc)
2188 {
2189 calls = 0;
2190 connect(sender: doc, SIGNAL(cursorPositionChanged(QTextCursor)),
2191 receiver: this, SLOT(cursorPositionChanged(QTextCursor)));
2192 }
2193
2194 int calls;
2195
2196private slots:
2197 void cursorPositionChanged(const QTextCursor &)
2198 {
2199 ++calls;
2200 }
2201};
2202
2203void tst_QTextDocument::cursorPositionChanged()
2204{
2205 CursorPosSignalSpy spy(doc);
2206
2207 cursor.insertText(text: "Test");
2208 QCOMPARE(spy.calls, 1);
2209
2210 spy.calls = 0;
2211 QTextCursor unrelatedCursor(doc);
2212 unrelatedCursor.insertText(text: "Blah");
2213 QCOMPARE(spy.calls, 2);
2214
2215 spy.calls = 0;
2216 cursor.insertText(text: "Blah");
2217 QCOMPARE(spy.calls, 1);
2218
2219 spy.calls = 0;
2220 cursor.movePosition(op: QTextCursor::PreviousCharacter);
2221 QCOMPARE(spy.calls, 0);
2222}
2223
2224void tst_QTextDocument::cursorPositionChangedOnSetText()
2225{
2226 CursorPosSignalSpy spy(doc);
2227
2228 // doc has one QTextCursor stored in the
2229 // cursor member variable, thus the signal
2230 // gets emitted once.
2231
2232 doc->setPlainText("Foo\nBar\nBaz\nBlub\nBlah");
2233
2234 QCOMPARE(spy.calls, 1);
2235
2236 spy.calls = 0;
2237 doc->setHtml("<p>Foo<p>Bar<p>Baz<p>Blah");
2238
2239 QCOMPARE(spy.calls, 1);
2240}
2241
2242void tst_QTextDocument::textFrameIterator()
2243{
2244 cursor.insertTable(rows: 1, cols: 1);
2245
2246 int blockCount = 0;
2247 int frameCount = 0;
2248
2249 for (QTextFrame::Iterator frameIt = doc->rootFrame()->begin();
2250 !frameIt.atEnd(); ++frameIt) {
2251 if (frameIt.currentFrame())
2252 ++frameCount;
2253 else if (frameIt.currentBlock().isValid())
2254 ++blockCount;
2255
2256 }
2257
2258 QEXPECT_FAIL("", "This is currently worked around in the html export but needs fixing!", Continue);
2259 QCOMPARE(blockCount, 0);
2260 QCOMPARE(frameCount, 1);
2261}
2262
2263void tst_QTextDocument::codecForHtml()
2264{
2265 const QByteArray header("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;charset=utf-16\">");
2266 QTextCodec *c = Qt::codecForHtml(ba: header);
2267 QVERIFY(c);
2268 QCOMPARE(c->name(), QByteArray("UTF-16"));
2269}
2270
2271class TestSyntaxHighlighter : public QObject
2272{
2273 Q_OBJECT
2274public:
2275 inline TestSyntaxHighlighter(QTextDocument *doc) : QObject(doc), ok(false) {}
2276
2277 bool ok;
2278
2279private slots:
2280 inline void markBlockDirty(int from, int charsRemoved, int charsAdded)
2281 {
2282 Q_UNUSED(charsRemoved);
2283 Q_UNUSED(charsAdded);
2284 QTextDocument *doc = static_cast<QTextDocument *>(parent());
2285 QTextBlock block = doc->findBlock(pos: from);
2286
2287 QTestDocumentLayout *lout = qobject_cast<QTestDocumentLayout *>(object: doc->documentLayout());
2288 lout->called = false;
2289
2290 doc->markContentsDirty(from: block.position(), length: block.length());
2291
2292 ok = (lout->called == false);
2293 }
2294
2295 inline void modifyBlockAgain(int from, int charsRemoved, int charsAdded)
2296 {
2297 Q_UNUSED(charsRemoved);
2298 Q_UNUSED(charsAdded);
2299 QTextDocument *doc = static_cast<QTextDocument *>(parent());
2300 QTextBlock block = doc->findBlock(pos: from);
2301 QTextCursor cursor(block);
2302
2303 QTestDocumentLayout *lout = qobject_cast<QTestDocumentLayout *>(object: doc->documentLayout());
2304 lout->called = false;
2305
2306 cursor.insertText(text: "Foo");
2307
2308 ok = (lout->called == true);
2309 }
2310};
2311
2312void tst_QTextDocument::markContentsDirty()
2313{
2314 QTestDocumentLayout *lout = new QTestDocumentLayout(doc);
2315 doc->setDocumentLayout(lout);
2316 TestSyntaxHighlighter *highlighter = new TestSyntaxHighlighter(doc);
2317 connect(sender: doc, SIGNAL(contentsChange(int,int,int)),
2318 receiver: highlighter, SLOT(markBlockDirty(int,int,int)));
2319
2320 highlighter->ok = false;
2321 cursor.insertText(text: "Some dummy text blah blah");
2322 QVERIFY(highlighter->ok);
2323
2324 disconnect(sender: doc, SIGNAL(contentsChange(int,int,int)),
2325 receiver: highlighter, SLOT(markBlockDirty(int,int,int)));
2326 connect(sender: doc, SIGNAL(contentsChange(int,int,int)),
2327 receiver: highlighter, SLOT(modifyBlockAgain(int,int,int)));
2328 highlighter->ok = false;
2329 cursor.insertText(text: "FooBar");
2330 QVERIFY(highlighter->ok);
2331
2332 lout->called = false;
2333
2334 doc->markContentsDirty(from: 1, length: 4);
2335
2336 QVERIFY(lout->called);
2337}
2338
2339void tst_QTextDocument::clonePreservesMetaInformation()
2340{
2341 const QString title("Foobar");
2342 const QString url("about:blank");
2343 doc->setHtml("<html><head><title>" + title + "</title></head><body>Hrm</body></html>");
2344 doc->setMetaInformation(info: QTextDocument::DocumentUrl, url);
2345 QCOMPARE(doc->metaInformation(QTextDocument::DocumentTitle), title);
2346 QCOMPARE(doc->metaInformation(QTextDocument::DocumentUrl), url);
2347
2348 QTextDocument *clone = doc->clone();
2349 QCOMPARE(clone->metaInformation(QTextDocument::DocumentTitle), title);
2350 QCOMPARE(clone->metaInformation(QTextDocument::DocumentUrl), url);
2351 delete clone;
2352}
2353
2354void tst_QTextDocument::clonePreservesPageSize()
2355{
2356 QSizeF sz(100., 100.);
2357 doc->setPageSize(sz);
2358 QTextDocument *clone = doc->clone();
2359 QCOMPARE(clone->pageSize(), sz);
2360 delete clone;
2361}
2362
2363void tst_QTextDocument::clonePreservesPageBreakPolicies()
2364{
2365 QTextTableFormat tableFmt;
2366 tableFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter);
2367
2368 QTextBlockFormat blockFmt;
2369 blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
2370
2371 QTextCursor cursor(doc);
2372
2373 cursor.setBlockFormat(blockFmt);
2374 cursor.insertText(text: "foo");
2375 cursor.insertTable(rows: 2, cols: 2, format: tableFmt);
2376
2377 QTextDocument *clone = doc->clone();
2378 QCOMPARE(clone->begin().blockFormat().pageBreakPolicy(), QTextFormat::PageBreak_AlwaysBefore);
2379 QVERIFY(!clone->rootFrame()->childFrames().isEmpty());
2380 QCOMPARE(clone->rootFrame()->childFrames().first()->frameFormat().pageBreakPolicy(), QTextFormat::PageBreak_AlwaysAfter);
2381 delete clone;
2382}
2383
2384void tst_QTextDocument::clonePreservesDefaultFont()
2385{
2386 QFont f = doc->defaultFont();
2387 QVERIFY(f.pointSize() != 100);
2388 f.setPointSize(100);
2389 doc->setDefaultFont(f);
2390 QTextDocument *clone = doc->clone();
2391 QCOMPARE(clone->defaultFont(), f);
2392 delete clone;
2393}
2394
2395void tst_QTextDocument::clonePreservesResources()
2396{
2397 QUrl testUrl(":/foobar");
2398 QVariant testResource("hello world");
2399
2400 doc->addResource(type: QTextDocument::ImageResource, name: testUrl, resource: testResource);
2401 QTextDocument *clone = doc->clone();
2402 QVERIFY(clone->resource(QTextDocument::ImageResource, testUrl) == testResource);
2403 delete clone;
2404}
2405
2406void tst_QTextDocument::clonePreservesUserStates()
2407{
2408 QTextCursor cursor(doc);
2409 cursor.insertText(text: "bla bla bla");
2410 cursor.block().setUserState(1);
2411 cursor.insertBlock();
2412 cursor.insertText(text: "foo bar");
2413 cursor.block().setUserState(2);
2414 cursor.insertBlock();
2415 cursor.insertText(text: "no user state");
2416
2417 QTextDocument *clone = doc->clone();
2418 QTextBlock b1 = doc->begin(), b2 = clone->begin();
2419 while (b1 != doc->end()) {
2420 b1 = b1.next();
2421 b2 = b2.next();
2422 QCOMPARE(b1.userState(), b2.userState());
2423 }
2424 QCOMPARE(b2, clone->end());
2425 delete clone;
2426}
2427
2428void tst_QTextDocument::clonePreservesRootFrameFormat()
2429{
2430 doc->setPlainText("Hello");
2431 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
2432 fmt.setMargin(200);
2433 doc->rootFrame()->setFrameFormat(fmt);
2434 QCOMPARE(doc->rootFrame()->frameFormat().margin(), qreal(200));
2435 QTextDocument *copy = doc->clone();
2436 QCOMPARE(copy->rootFrame()->frameFormat().margin(), qreal(200));
2437 delete copy;
2438}
2439
2440void tst_QTextDocument::clonePreservesIndentWidth()
2441{
2442 doc->setIndentWidth(42);
2443 QTextDocument *clone = doc->clone();
2444 QCOMPARE(clone->indentWidth(), qreal(42));
2445 delete clone;
2446}
2447
2448void tst_QTextDocument::clonePreservesFormatsWhenEmpty()
2449{
2450 QTextDocument document;
2451 QTextCursor cursor(&document);
2452
2453 // Change a few char format attributes
2454 QTextCharFormat charFormat;
2455 charFormat.setFontPointSize(charFormat.fontPointSize() + 1);
2456 charFormat.setFontWeight(charFormat.fontWeight() + 1);
2457 cursor.setBlockCharFormat(charFormat);
2458
2459 // Change a few block format attributes
2460 QTextBlockFormat blockFormat;
2461 blockFormat.setAlignment(Qt::AlignRight); // The default is Qt::AlignLeft
2462 blockFormat.setIndent(blockFormat.indent() + 1);
2463 cursor.setBlockFormat(blockFormat);
2464
2465 auto clone = document.clone();
2466 QTextCursor cloneCursor(clone);
2467
2468 QCOMPARE(cloneCursor.blockCharFormat().fontPointSize(), charFormat.fontPointSize());
2469 QCOMPARE(cloneCursor.blockCharFormat().fontWeight(), charFormat.fontWeight());
2470 QCOMPARE(cloneCursor.blockFormat().alignment(), blockFormat.alignment());
2471 QCOMPARE(cloneCursor.blockFormat().indent(), blockFormat.indent());
2472}
2473
2474void tst_QTextDocument::blockCount()
2475{
2476 QCOMPARE(doc->blockCount(), 1);
2477 cursor.insertBlock();
2478 QCOMPARE(doc->blockCount(), 2);
2479 cursor.insertBlock();
2480 QCOMPARE(doc->blockCount(), 3);
2481 cursor.insertText(text: "blah blah");
2482 QCOMPARE(doc->blockCount(), 3);
2483 doc->undo();
2484 doc->undo();
2485 QCOMPARE(doc->blockCount(), 2);
2486 doc->undo();
2487 QCOMPARE(doc->blockCount(), 1);
2488}
2489
2490void tst_QTextDocument::resolvedFontInEmptyFormat()
2491{
2492 QFont font;
2493 font.setPointSize(42);
2494 doc->setDefaultFont(font);
2495 QTextCharFormat fmt = doc->begin().charFormat();
2496 QVERIFY(fmt.properties().isEmpty());
2497 QCOMPARE(fmt.font(), font);
2498}
2499
2500void tst_QTextDocument::defaultRootFrameMargin()
2501{
2502 QCOMPARE(doc->rootFrame()->frameFormat().margin(), 4.0);
2503}
2504
2505class TestDocument : public QTextDocument
2506{
2507public:
2508 inline TestDocument(const QUrl &testUrl, const QString &testString)
2509 : url(testUrl), string(testString), resourceLoaded(false) {}
2510
2511 bool hasResourceCached();
2512
2513protected:
2514 virtual QVariant loadResource(int type, const QUrl &name);
2515
2516private:
2517 QUrl url;
2518 QString string;
2519 bool resourceLoaded;
2520};
2521
2522bool TestDocument::hasResourceCached()
2523{
2524 resourceLoaded = false;
2525 resource(type: QTextDocument::ImageResource, name: url);
2526 return !resourceLoaded;
2527}
2528
2529QVariant TestDocument::loadResource(int type, const QUrl &name)
2530{
2531 if (type == QTextDocument::ImageResource
2532 && name == url) {
2533 resourceLoaded = true;
2534 return string;
2535 }
2536 return QTextDocument::loadResource(type, name);
2537}
2538
2539void tst_QTextDocument::clearResources()
2540{
2541 // regular resource for QTextDocument
2542 QUrl testUrl(":/foobar");
2543 QVariant testResource("hello world");
2544
2545 // implicitly cached resource, initially loaded through TestDocument::loadResource()
2546 QUrl cacheUrl(":/blub");
2547 QString cacheResource("mah");
2548
2549 TestDocument doc(cacheUrl, cacheResource);
2550 doc.addResource(type: QTextDocument::ImageResource, name: testUrl, resource: testResource);
2551
2552 QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
2553
2554 doc.setPlainText("Hah");
2555 QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
2556
2557 doc.setHtml("<b>Mooo</b><img src=\":/blub\"/>");
2558 QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
2559 QVERIFY(doc.resource(QTextDocument::ImageResource, cacheUrl) == cacheResource);
2560
2561 doc.clear();
2562 QVERIFY(!doc.resource(QTextDocument::ImageResource, testUrl).isValid());
2563 QVERIFY(!doc.hasResourceCached());
2564 doc.clear();
2565
2566 doc.setHtml("<b>Mooo</b><img src=\":/blub\"/>");
2567 QVERIFY(doc.resource(QTextDocument::ImageResource, cacheUrl) == cacheResource);
2568
2569 doc.setPlainText("Foob");
2570 QVERIFY(!doc.hasResourceCached());
2571}
2572
2573void tst_QTextDocument::setPlainText()
2574{
2575 doc->setPlainText("Hello World");
2576 QString s("");
2577 doc->setPlainText(s);
2578 QCOMPARE(doc->toPlainText(), s);
2579}
2580
2581void tst_QTextDocument::toPlainText_data()
2582{
2583 QTest::addColumn<QString>(name: "html");
2584 QTest::addColumn<QString>(name: "expectedPlainText");
2585
2586 QTest::newRow(dataTag: "nbsp") << "Hello&nbsp;World" << "Hello World";
2587 QTest::newRow(dataTag: "empty_div") << "<div></div>hello" << "hello";
2588 QTest::newRow(dataTag: "br_and_p") << "<p>first<br></p><p>second<br></p>" << "first\n\nsecond\n";
2589 QTest::newRow(dataTag: "div") << "first<div>second<br>third</div>fourth" << "first\nsecond\nthird\nfourth"; // <div> and </div> become newlines...
2590 QTest::newRow(dataTag: "br_text_end_of_div") << "<div><div>first<br>moretext</div>second<br></div>" << "first\nmoretext\nsecond\n"; // ... when there is text before <div>
2591 QTest::newRow(dataTag: "br_end_of_div_like_gmail") << "<div><div><div>first<br></div>second<br></div>third<br></div>" << "first\nsecond\nthird\n"; // ... and when there is text before </div>
2592 QTest::newRow(dataTag: "p_and_div") << "<div><div>first<p>second</p></div>third</div>" << "first\nsecond\nthird";
2593}
2594
2595void tst_QTextDocument::toPlainText()
2596{
2597 QFETCH(QString, html);
2598 QFETCH(QString, expectedPlainText);
2599
2600 doc->setHtml(html);
2601 QCOMPARE(doc->toPlainText(), expectedPlainText);
2602}
2603
2604void tst_QTextDocument::toRawText()
2605{
2606 doc->setHtml("&nbsp;");
2607
2608 QString rawText = doc->toRawText();
2609 QCOMPARE(rawText.size(), 1);
2610 QCOMPARE(rawText.at(0).unicode(), ushort(QChar::Nbsp));
2611}
2612
2613
2614void tst_QTextDocument::deleteTextObjectsOnClear()
2615{
2616 QPointer<QTextTable> table = cursor.insertTable(rows: 2, cols: 2);
2617 QVERIFY(!table.isNull());
2618 doc->clear();
2619 QVERIFY(table.isNull());
2620}
2621
2622void tst_QTextDocument::defaultStyleSheet()
2623{
2624 const QColor green("green");
2625 const QString sheet("p { background-color: green; }");
2626 QVERIFY(doc->defaultStyleSheet().isEmpty());
2627 doc->setDefaultStyleSheet(sheet);
2628 QCOMPARE(doc->defaultStyleSheet(), sheet);
2629
2630 cursor.insertHtml(html: "<p>test");
2631 QTextBlockFormat fmt = doc->begin().blockFormat();
2632 QCOMPARE(fmt.background().color(), green);
2633
2634 doc->clear();
2635 cursor.insertHtml(html: "<p>test");
2636 fmt = doc->begin().blockFormat();
2637 QCOMPARE(fmt.background().color(), green);
2638
2639 QTextDocument *clone = doc->clone();
2640 QCOMPARE(clone->defaultStyleSheet(), sheet);
2641 cursor = QTextCursor(clone);
2642 cursor.insertHtml(html: "<p>test");
2643 fmt = clone->begin().blockFormat();
2644 QCOMPARE(fmt.background().color(), green);
2645 delete clone;
2646
2647 cursor = QTextCursor(doc);
2648 cursor.insertHtml(html: "<p>test");
2649 fmt = doc->begin().blockFormat();
2650 QCOMPARE(fmt.background().color(), green);
2651
2652 doc->clear();
2653 cursor.insertHtml(html: "<style>p { background-color: red; }</style><p>test");
2654 fmt = doc->begin().blockFormat();
2655 QCOMPARE(fmt.background().color(), QColor(Qt::red));
2656
2657 doc->clear();
2658 doc->setDefaultStyleSheet("invalid style sheet....");
2659 cursor.insertHtml(html: "<p>test");
2660 fmt = doc->begin().blockFormat();
2661 QVERIFY(fmt.background().color() != QColor("green"));
2662}
2663
2664void tst_QTextDocument::maximumBlockCount()
2665{
2666 QCOMPARE(doc->maximumBlockCount(), 0);
2667 QVERIFY(doc->isUndoRedoEnabled());
2668
2669 cursor.insertBlock();
2670 cursor.insertText(text: "Blah");
2671 cursor.insertBlock();
2672 cursor.insertText(text: "Foo");
2673 QCOMPARE(doc->blockCount(), 3);
2674 QCOMPARE(doc->toPlainText(), QString("\nBlah\nFoo"));
2675
2676 doc->setMaximumBlockCount(1);
2677 QVERIFY(!doc->isUndoRedoEnabled());
2678
2679 QCOMPARE(doc->blockCount(), 1);
2680 QCOMPARE(doc->toPlainText(), QString("Foo"));
2681
2682 cursor.insertBlock();
2683 cursor.insertText(text: "Hello");
2684 doc->setMaximumBlockCount(1);
2685 QCOMPARE(doc->blockCount(), 1);
2686 QCOMPARE(doc->toPlainText(), QString("Hello"));
2687
2688 doc->setMaximumBlockCount(100);
2689 for (int i = 0; i < 1000; ++i) {
2690 cursor.insertBlock();
2691 cursor.insertText(text: "Blah)");
2692 QVERIFY(doc->blockCount() <= 100);
2693 }
2694
2695 cursor.movePosition(op: QTextCursor::End);
2696 QCOMPARE(cursor.blockNumber(), 99);
2697 QTextCharFormat fmt;
2698 fmt.setFontItalic(true);
2699 cursor.setBlockCharFormat(fmt);
2700 cursor.movePosition(op: QTextCursor::Start);
2701 QVERIFY(!cursor.blockCharFormat().fontItalic());
2702
2703 doc->setMaximumBlockCount(1);
2704 QVERIFY(cursor.blockCharFormat().fontItalic());
2705
2706 cursor.insertTable(rows: 2, cols: 2);
2707 QCOMPARE(doc->blockCount(), 6);
2708 cursor.insertBlock();
2709 QCOMPARE(doc->blockCount(), 1);
2710}
2711
2712void tst_QTextDocument::adjustSize()
2713{
2714 // avoid ugly tooltips like in task 125583
2715 QString text("Test Text");
2716 doc->setPlainText(text);
2717 doc->rootFrame()->setFrameFormat(QTextFrameFormat());
2718 doc->adjustSize();
2719 QCOMPARE(doc->size().width(), doc->idealWidth());
2720}
2721
2722void tst_QTextDocument::initialUserData()
2723{
2724 doc->setPlainText("Hello");
2725 QTextBlock block = doc->begin();
2726 block.setUserData(new QTextBlockUserData);
2727 QVERIFY(block.userData());
2728 doc->documentLayout();
2729 QVERIFY(block.userData());
2730 doc->setDocumentLayout(new QTestDocumentLayout(doc));
2731 QVERIFY(!block.userData());
2732}
2733
2734void tst_QTextDocument::html_defaultFont()
2735{
2736 QFont f;
2737 f.setItalic(true);
2738 f.setWeight(QFont::Bold);
2739 doc->setDefaultFont(f);
2740 doc->setPlainText("Test");
2741
2742 QString bodyPart = QString::fromLatin1(str: "<body style=\" font-family:'%1'; font-size:%2; font-weight:%3; font-style:italic;\">")
2743 .arg(a: f.family())
2744 .arg(a: cssFontSizeString(font: f))
2745 .arg(a: f.weight() * 8);
2746
2747 QString html = doc->toHtml();
2748 if (!html.contains(s: bodyPart)) {
2749 qDebug() << "html:" << html;
2750 qDebug() << "expected body:" << bodyPart;
2751 QVERIFY(html.contains(bodyPart));
2752 }
2753
2754 if (html.contains(s: "span"))
2755 qDebug() << "html:" << html;
2756 QVERIFY(!html.contains("<span style"));
2757}
2758
2759void tst_QTextDocument::blockCountChanged()
2760{
2761 QSignalSpy spy(doc, SIGNAL(blockCountChanged(int)));
2762
2763 doc->setPlainText("Foo");
2764
2765 QCOMPARE(doc->blockCount(), 1);
2766 QCOMPARE(spy.count(), 0);
2767
2768 spy.clear();
2769
2770 doc->setPlainText("Foo\nBar");
2771 QCOMPARE(doc->blockCount(), 2);
2772 QCOMPARE(spy.count(), 1);
2773 QCOMPARE(spy.at(0).value(0).toInt(), 2);
2774
2775 spy.clear();
2776
2777 cursor.movePosition(op: QTextCursor::End);
2778 cursor.insertText(text: "Blahblah");
2779
2780 QCOMPARE(spy.count(), 0);
2781
2782 cursor.insertBlock();
2783 QCOMPARE(spy.count(), 1);
2784 QCOMPARE(spy.at(0).value(0).toInt(), 3);
2785
2786 spy.clear();
2787 doc->undo();
2788
2789 QCOMPARE(spy.count(), 1);
2790 QCOMPARE(spy.at(0).value(0).toInt(), 2);
2791}
2792
2793void tst_QTextDocument::nonZeroDocumentLengthOnClear()
2794{
2795 QTestDocumentLayout *lout = new QTestDocumentLayout(doc);
2796 doc->setDocumentLayout(lout);
2797
2798 doc->clear();
2799 QVERIFY(lout->called);
2800 QVERIFY(!lout->lastDocumentLengths.contains(0));
2801}
2802
2803void tst_QTextDocument::setTextPreservesUndoRedoEnabled()
2804{
2805 QVERIFY(doc->isUndoRedoEnabled());
2806
2807 doc->setPlainText("Test");
2808
2809 QVERIFY(doc->isUndoRedoEnabled());
2810
2811 doc->setUndoRedoEnabled(false);
2812 QVERIFY(!doc->isUndoRedoEnabled());
2813 doc->setPlainText("Test2");
2814 QVERIFY(!doc->isUndoRedoEnabled());
2815
2816 doc->setHtml("<p>hello");
2817 QVERIFY(!doc->isUndoRedoEnabled());
2818}
2819
2820void tst_QTextDocument::firstLast()
2821{
2822 QCOMPARE(doc->blockCount(), 1);
2823 QCOMPARE(doc->firstBlock(), doc->lastBlock());
2824
2825 doc->setPlainText("Hello\nTest\nWorld");
2826
2827 QCOMPARE(doc->blockCount(), 3);
2828 QVERIFY(doc->firstBlock() != doc->lastBlock());
2829
2830 QCOMPARE(doc->firstBlock().text(), QString("Hello"));
2831 QCOMPARE(doc->lastBlock().text(), QString("World"));
2832
2833 // manual forward loop
2834 QTextBlock block = doc->firstBlock();
2835
2836 QVERIFY(block.isValid());
2837 QCOMPARE(block.text(), QString("Hello"));
2838
2839 block = block.next();
2840
2841 QVERIFY(block.isValid());
2842 QCOMPARE(block.text(), QString("Test"));
2843
2844 block = block.next();
2845
2846 QVERIFY(block.isValid());
2847 QCOMPARE(block.text(), QString("World"));
2848
2849 block = block.next();
2850 QVERIFY(!block.isValid());
2851
2852 // manual backward loop
2853 block = doc->lastBlock();
2854
2855 QVERIFY(block.isValid());
2856 QCOMPARE(block.text(), QString("World"));
2857
2858 block = block.previous();
2859
2860 QVERIFY(block.isValid());
2861 QCOMPARE(block.text(), QString("Test"));
2862
2863 block = block.previous();
2864
2865 QVERIFY(block.isValid());
2866 QCOMPARE(block.text(), QString("Hello"));
2867
2868 block = block.previous();
2869 QVERIFY(!block.isValid());
2870}
2871
2872const QString backgroundImage_html("<body><table><tr><td background=\"foo.png\">Blah</td></tr></table></body>");
2873
2874void tst_QTextDocument::backgroundImage_checkExpectedHtml(const QTextDocument &doc)
2875{
2876 QString expectedHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
2877 "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
2878 "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
2879 "p, li { white-space: pre-wrap; }\n"
2880 "</style></head>"
2881 "<body style=\" font-family:'%1'; font-size:%2; font-weight:%3; font-style:%4;\">\n"
2882 "<table border=\"0\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;\" cellspacing=\"2\" cellpadding=\"0\">"
2883 "\n<tr>\n<td background=\"foo.png\">"
2884 "\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
2885 "</td></tr></table></body></html>");
2886
2887 expectedHtml = expectedHtml
2888 .arg(a: defaultFont.family())
2889 .arg(a: cssFontSizeString(font: defaultFont))
2890 .arg(a: defaultFont.weight() * 8)
2891 .arg(a: (defaultFont.italic() ? "italic" : "normal"));
2892
2893 writeActualAndExpected(testTag: QTest::currentTestFunction(), actual: doc.toHtml(), expected: expectedHtml);
2894
2895 QCOMPARE(doc.toHtml(), expectedHtml);
2896}
2897
2898void tst_QTextDocument::buildRegExpData()
2899{
2900 QTest::addColumn<QString>(name: "haystack");
2901 QTest::addColumn<QString>(name: "needle");
2902 QTest::addColumn<int>(name: "flags");
2903 QTest::addColumn<int>(name: "from");
2904 QTest::addColumn<int>(name: "anchor");
2905 QTest::addColumn<int>(name: "position");
2906
2907 // match integers 0 to 99
2908 QTest::newRow(dataTag: "1") << "23" << "^\\d\\d?$" << int(QTextDocument::FindCaseSensitively) << 0 << 0 << 2;
2909 // match ampersands but not &amp;
2910 QTest::newRow(dataTag: "2") << "His &amp; hers & theirs" << "&(?!amp;)"<< int(QTextDocument::FindCaseSensitively) << 0 << 15 << 16;
2911 //backward search
2912 QTest::newRow(dataTag: "3") << QString::fromLatin1(str: "HelloBlahWorld Blah Hah")
2913 << "h" << int(QTextDocument::FindBackward) << 18 << 8 << 9;
2914}
2915
2916void tst_QTextDocument::backgroundImage_toHtml()
2917{
2918 CREATE_DOC_AND_CURSOR();
2919
2920 doc.setHtml(backgroundImage_html);
2921 backgroundImage_checkExpectedHtml(doc);
2922}
2923
2924void tst_QTextDocument::backgroundImage_toHtml2()
2925{
2926 CREATE_DOC_AND_CURSOR();
2927
2928 cursor.insertHtml(html: backgroundImage_html);
2929 backgroundImage_checkExpectedHtml(doc);
2930}
2931
2932void tst_QTextDocument::backgroundImage_clone()
2933{
2934 CREATE_DOC_AND_CURSOR();
2935
2936 doc.setHtml(backgroundImage_html);
2937 QTextDocument *clone = doc.clone();
2938 backgroundImage_checkExpectedHtml(doc: *clone);
2939 delete clone;
2940}
2941
2942void tst_QTextDocument::backgroundImage_copy()
2943{
2944 CREATE_DOC_AND_CURSOR();
2945
2946 doc.setHtml(backgroundImage_html);
2947 QTextDocumentFragment fragment(&doc);
2948
2949 {
2950 CREATE_DOC_AND_CURSOR();
2951
2952 cursor.insertFragment(fragment);
2953 backgroundImage_checkExpectedHtml(doc);
2954 }
2955}
2956
2957void tst_QTextDocument::documentCleanup()
2958{
2959 QTextDocument doc;
2960 QTextCursor cursor(&doc);
2961 cursor.insertText(text: "d\nfoo\nbar\n");
2962 doc.documentLayout(); // forces relayout
2963
2964 // remove char 1
2965 cursor.setPosition(pos: 0);
2966 QSizeF size = doc.documentLayout()->documentSize();
2967 cursor.deleteChar();
2968 // the size should be unchanged.
2969 QCOMPARE(doc.documentLayout()->documentSize(), size);
2970}
2971
2972void tst_QTextDocument::characterAt()
2973{
2974 QTextDocument doc;
2975 QTextCursor cursor(&doc);
2976 QString text("12345\n67890");
2977 cursor.insertText(text);
2978 int length = doc.characterCount();
2979 QCOMPARE(length, text.length() + 1);
2980 QCOMPARE(doc.characterAt(length-1), QChar(QChar::ParagraphSeparator));
2981 QCOMPARE(doc.characterAt(-1), QChar());
2982 QCOMPARE(doc.characterAt(length), QChar());
2983 QCOMPARE(doc.characterAt(length + 1), QChar());
2984 for (int i = 0; i < text.length(); ++i) {
2985 QChar c = text.at(i);
2986 if (c == QLatin1Char('\n'))
2987 c = QChar(QChar::ParagraphSeparator);
2988 QCOMPARE(doc.characterAt(i), c);
2989 }
2990}
2991
2992void tst_QTextDocument::revisions()
2993{
2994 QTextDocument doc;
2995 QTextCursor cursor(&doc);
2996 QString text("Hello World");
2997 QCOMPARE(doc.firstBlock().revision(), 0);
2998 cursor.insertText(text);
2999 QCOMPARE(doc.firstBlock().revision(), 1);
3000 cursor.setPosition(pos: 6);
3001 cursor.insertBlock();
3002 QCOMPARE(cursor.block().previous().revision(), 2);
3003 QCOMPARE(cursor.block().revision(), 2);
3004 cursor.insertText(text: "candle");
3005 QCOMPARE(cursor.block().revision(), 3);
3006 cursor.movePosition(op: QTextCursor::EndOfBlock);
3007 cursor.insertBlock(); // we are at the block end
3008 QCOMPARE(cursor.block().previous().revision(), 3);
3009 QCOMPARE(cursor.block().revision(), 4);
3010 cursor.insertText(text: "lightbulb");
3011 QCOMPARE(cursor.block().revision(), 5);
3012 cursor.movePosition(op: QTextCursor::StartOfBlock);
3013 cursor.insertBlock(); // we are the block start
3014 QCOMPARE(cursor.block().previous().revision(), 6);
3015 QCOMPARE(cursor.block().revision(), 5);
3016}
3017
3018void tst_QTextDocument::revisionWithUndoCompressionAndUndo()
3019{
3020 QTextDocument doc;
3021 QTextCursor cursor(&doc);
3022 cursor.insertText(text: "This is the beginning of it all.");
3023 QCOMPARE(doc.firstBlock().revision(), 1);
3024 QCOMPARE(doc.revision(), 1);
3025 cursor.insertBlock();
3026 QCOMPARE(doc.revision(), 2);
3027 cursor.insertText(text: "this");
3028 QCOMPARE(doc.revision(), 3);
3029 cursor.insertText(text: "is");
3030 QCOMPARE(doc.revision(), 4);
3031 cursor.insertText(text: "compressed");
3032 QCOMPARE(doc.revision(), 5);
3033 doc.undo();
3034 QCOMPARE(doc.revision(), 6);
3035 QCOMPARE(doc.toPlainText(), QString("This is the beginning of it all.\n")) ;
3036 cursor.setPosition(pos: 0);
3037 QCOMPARE(doc.firstBlock().revision(), 1);
3038 cursor.insertText(text: "Very beginnig");
3039 QCOMPARE(doc.firstBlock().revision(), 7);
3040 doc.undo();
3041 QCOMPARE(doc.revision(), 8);
3042 QCOMPARE(doc.firstBlock().revision(), 1);
3043
3044 cursor.beginEditBlock();
3045 cursor.insertText(text: "Hello");
3046 cursor.insertBlock();
3047 cursor.insertText(text: "world");
3048 cursor.endEditBlock();
3049 QCOMPARE(doc.revision(), 9);
3050 doc.undo();
3051 QCOMPARE(doc.revision(), 10);
3052
3053
3054}
3055
3056void tst_QTextDocument::testUndoCommandAdded()
3057{
3058 QVERIFY(doc);
3059 QSignalSpy spy(doc, SIGNAL(undoCommandAdded()));
3060 QVERIFY(spy.isValid());
3061 QVERIFY(spy.isEmpty());
3062
3063 cursor.insertText(text: "a");
3064 QCOMPARE(spy.count(), 1);
3065 cursor.insertText(text: "b"); // should be merged
3066 QCOMPARE(spy.count(), 1);
3067 cursor.insertText(text: "c"); // should be merged
3068 QCOMPARE(spy.count(), 1);
3069 QCOMPARE(doc->toPlainText(), QString("abc"));
3070 doc->undo();
3071 QCOMPARE(doc->toPlainText(), QString(""));
3072
3073 doc->clear();
3074 spy.clear();
3075 cursor.insertText(text: "aaa");
3076 QCOMPARE(spy.count(), 1);
3077
3078 spy.clear();
3079 cursor.insertText(text: "aaaa\nbcd");
3080 QCOMPARE(spy.count(), 1);
3081
3082 spy.clear();
3083 cursor.beginEditBlock();
3084 cursor.insertText(text: "aa");
3085 cursor.insertText(text: "bbb\n");
3086 cursor.setCharFormat(QTextCharFormat());
3087 cursor.insertText(text: "\nccc");
3088 QVERIFY(spy.isEmpty());
3089 cursor.endEditBlock();
3090 QCOMPARE(spy.count(), 1);
3091
3092 spy.clear();
3093 cursor.insertBlock();
3094 QCOMPARE(spy.count(), 1);
3095
3096 spy.clear();
3097 cursor.setPosition(pos: 5);
3098 QVERIFY(spy.isEmpty());
3099 cursor.setCharFormat(QTextCharFormat());
3100 QVERIFY(spy.isEmpty());
3101 cursor.setPosition(pos: 10, mode: QTextCursor::KeepAnchor);
3102 QVERIFY(spy.isEmpty());
3103 QTextCharFormat cf;
3104 cf.setFontItalic(true);
3105 cursor.mergeCharFormat(modifier: cf);
3106 QCOMPARE(spy.count(), 1);
3107
3108 spy.clear();
3109 doc->undo();
3110 QCOMPARE(spy.count(), 0);
3111 doc->undo();
3112 QCOMPARE(spy.count(), 0);
3113 spy.clear();
3114 doc->redo();
3115 QCOMPARE(spy.count(), 0);
3116 doc->redo();
3117 QCOMPARE(spy.count(), 0);
3118}
3119
3120void tst_QTextDocument::testUndoBlocks()
3121{
3122 QVERIFY(doc);
3123 cursor.insertText(text: "Hello World");
3124 cursor.insertText(text: "period");
3125 doc->undo();
3126 QCOMPARE(doc->toPlainText(), QString(""));
3127 cursor.insertText(text: "Hello World");
3128 cursor.insertText(text: "One\nTwo\nThree");
3129 QCOMPARE(doc->toPlainText(), QString("Hello WorldOne\nTwo\nThree"));
3130 doc->undo();
3131 QCOMPARE(doc->toPlainText(), QString("Hello World"));
3132 cursor.insertText(text: "One\nTwo\nThree");
3133 cursor.insertText(text: "Trailing text");
3134 doc->undo();
3135 QCOMPARE(doc->toPlainText(), QString("Hello WorldOne\nTwo\nThree"));
3136 doc->undo();
3137 QCOMPARE(doc->toPlainText(), QString("Hello World"));
3138 doc->undo();
3139 QCOMPARE(doc->toPlainText(), QString(""));
3140
3141 cursor.insertText(text: "town");
3142 cursor.beginEditBlock(); // Edit block 1 - Deletion/Insertion
3143 cursor.setPosition(pos: 0, mode: QTextCursor::KeepAnchor);
3144 cursor.insertText(text: "r");
3145 cursor.endEditBlock();
3146 cursor.insertText(text: "est"); // Merged into edit block 1
3147 QCOMPARE(doc->toPlainText(), QString("rest"));
3148 doc->undo();
3149 QCOMPARE(doc->toPlainText(), QString("town"));
3150 doc->undo();
3151 QCOMPARE(doc->toPlainText(), QString(""));
3152
3153 // This case would not happen in practice. If the user typed out this text, it would all be part of one
3154 // edit block. This would cause the undo to clear all text. But for the purpose of testing the beginEditBlock
3155 // and endEditBlock calls with respect to qtextdocument this is tested.
3156 cursor.insertText(text: "quod");
3157 cursor.beginEditBlock(); // Edit block 1 - Insertion
3158 cursor.insertText(text: " erat");
3159 cursor.endEditBlock();
3160 cursor.insertText(text: " demonstrandum"); // Merged into edit block 1
3161 QCOMPARE(doc->toPlainText(), QString("quod erat demonstrandum"));
3162 doc->undo();
3163 QCOMPARE(doc->toPlainText(), QString("quod"));
3164 doc->undo();
3165 QCOMPARE(doc->toPlainText(), QString(""));
3166}
3167
3168class Receiver : public QObject
3169{
3170 Q_OBJECT
3171 public:
3172 QString first;
3173 public slots:
3174 void cursorPositionChanged() {
3175 if (first.isEmpty())
3176 first = QLatin1String("cursorPositionChanged");
3177 }
3178
3179 void contentsChange() {
3180 if (first.isEmpty())
3181 first = QLatin1String("contentsChanged");
3182 }
3183};
3184
3185void tst_QTextDocument::receiveCursorPositionChangedAfterContentsChange()
3186{
3187 QVERIFY(doc);
3188 doc->setDocumentLayout(new MyAbstractTextDocumentLayout(doc));
3189 Receiver rec;
3190 connect(sender: doc, SIGNAL(cursorPositionChanged(QTextCursor)),
3191 receiver: &rec, SLOT(cursorPositionChanged()));
3192 connect(sender: doc, SIGNAL(contentsChange(int,int,int)),
3193 receiver: &rec, SLOT(contentsChange()));
3194 cursor.insertText(text: "Hello World");
3195 QCOMPARE(rec.first, QString("contentsChanged"));
3196}
3197
3198void tst_QTextDocument::QTBUG25778_pixelSizeFromHtml()
3199{
3200 QTextDocument document1;
3201 QTextDocument document2;
3202
3203 document1.setHtml("<span style=\"font-size: 24px\">Foobar</span>");
3204 document2.setHtml(document1.toHtml());
3205
3206 QTextCursor cursor(&document2);
3207 QCOMPARE(cursor.charFormat().font().pixelSize(), 24);
3208}
3209
3210void tst_QTextDocument::copiedFontSize()
3211{
3212 QTextDocument documentInput;
3213 QTextDocument documentOutput;
3214
3215 QFont fontInput;
3216 fontInput.setPixelSize(24);
3217
3218 QTextCursor cursorInput(&documentInput);
3219 QTextCharFormat formatInput = cursorInput.charFormat();
3220 formatInput.setFont(fontInput);
3221 cursorInput.insertText(text: "Should be the same font", format: formatInput);
3222 cursorInput.select(selection: QTextCursor::Document);
3223
3224 QTextDocumentFragment fragmentInput(cursorInput);
3225 QString html = fragmentInput.toHtml();
3226
3227 QTextCursor cursorOutput(&documentOutput);
3228 QTextDocumentFragment fragmentOutput = QTextDocumentFragment::fromHtml(html);
3229 cursorOutput.insertFragment(fragment: fragmentOutput);
3230
3231 QCOMPARE(cursorOutput.charFormat().font().pixelSize(), 24);
3232}
3233
3234void tst_QTextDocument::htmlExportImportBlockCount()
3235{
3236 QTextDocument document;
3237 {
3238 QTextCursor cursor(&document);
3239 cursor.insertText(text: "Foo");
3240 cursor.insertBlock();
3241 cursor.insertBlock();
3242 cursor.insertBlock();
3243 cursor.insertBlock();
3244 cursor.insertText(text: "Bar");
3245 }
3246
3247 QCOMPARE(document.blockCount(), 5);
3248 QString html = document.toHtml();
3249
3250 document.clear();
3251 document.setHtml(html);
3252
3253 QCOMPARE(document.blockCount(), 5);
3254}
3255
3256void tst_QTextDocument::QTBUG27354_spaceAndSoftSpace()
3257{
3258 QTextDocument document;
3259 {
3260 QTextCursor cursor(&document);
3261 QTextBlockFormat blockFormat;
3262 blockFormat.setAlignment(Qt::AlignJustify);
3263 cursor.mergeBlockFormat(modifier: blockFormat);
3264 cursor.insertText(text: "ac");
3265 cursor.insertBlock();
3266 cursor.insertText(text: " ");
3267 cursor.insertText(text: QChar(0x2028));
3268 }
3269
3270 // Trigger justification of text
3271 QImage image(1000, 1000, QImage::Format_ARGB32);
3272 image.fill(pixel: 0);
3273 {
3274 QPainter p(&image);
3275 document.drawContents(painter: &p, rect: image.rect());
3276 }
3277 {
3278 // If no p tag is specified it should not be inheriting it
3279 QTextDocument td;
3280 td.setHtml("<html><head><style type=\"text/css\">p { line-height: 200% }</style></head><body>Foo<ul><li>First</li></ul></body></html>");
3281 QTextBlock block = td.begin();
3282 while (block.isValid()) {
3283 QTextBlockFormat fmt = block.blockFormat();
3284 QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::SingleHeight));
3285 QCOMPARE(fmt.lineHeight(), qreal(0));
3286 block = block.next();
3287 }
3288 }
3289 {
3290 QTextDocument td;
3291 td.setHtml("<html><head></head><body><p>Foo</p><ul><li>First</li></ul></body></html>");
3292 QList<double> originalMargins;
3293 QTextBlock block = td.begin();
3294 while (block.isValid()) {
3295 originalMargins << block.blockFormat().topMargin();
3296 block = block.next();
3297 }
3298 originalMargins[0] = 85;
3299 td.setHtml("<html><head><style type=\"text/css\">body { margin-top: 85px; }</style></head><body><p>Foo</p><ul><li>First</li></ul></body></html>");
3300 block = td.begin();
3301 int count = 0;
3302 while (block.isValid()) {
3303 QTextBlockFormat fmt = block.blockFormat();
3304 QCOMPARE(fmt.topMargin(), originalMargins.at(count++));
3305 block = block.next();
3306 }
3307 }
3308}
3309
3310class BaseDocument : public QTextDocument
3311{
3312public:
3313 QUrl loadedResource() const { return resourceUrl; }
3314
3315 QVariant loadResource(int type, const QUrl &name)
3316 {
3317 resourceUrl = name;
3318 return QTextDocument::loadResource(type, name);
3319 }
3320
3321private:
3322 QUrl resourceUrl;
3323};
3324
3325void tst_QTextDocument::baseUrl_data()
3326{
3327 QTest::addColumn<QUrl>(name: "base");
3328 QTest::addColumn<QUrl>(name: "resource");
3329 QTest::addColumn<QUrl>(name: "loaded");
3330
3331 QTest::newRow(dataTag: "1") << QUrl() << QUrl("images/logo.png") << QUrl("images/logo.png");
3332 QTest::newRow(dataTag: "2") << QUrl("file:///path/to/content") << QUrl("images/logo.png") << QUrl("file:///path/to/images/logo.png");
3333 QTest::newRow(dataTag: "3") << QUrl("file:///path/to/content/") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
3334 QTest::newRow(dataTag: "4") << QUrl("file:///path/to/content/images") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
3335 QTest::newRow(dataTag: "5") << QUrl("file:///path/to/content/images/") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/images/logo.png");
3336 QTest::newRow(dataTag: "6") << QUrl("file:///path/to/content/images") << QUrl("../images/logo.png") << QUrl("file:///path/to/images/logo.png");
3337 QTest::newRow(dataTag: "7") << QUrl("file:///path/to/content/images/") << QUrl("../images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
3338 QTest::newRow(dataTag: "8") << QUrl("file:///path/to/content/index.html") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
3339}
3340
3341void tst_QTextDocument::baseUrl()
3342{
3343 QFETCH(QUrl, base);
3344 QFETCH(QUrl, resource);
3345 QFETCH(QUrl, loaded);
3346
3347 BaseDocument document;
3348 QVERIFY(!document.baseUrl().isValid());
3349 document.setBaseUrl(base);
3350 QCOMPARE(document.baseUrl(), base);
3351
3352 document.setHtml(QLatin1String("<img src='") + resource.toString() + QLatin1String("'/>"));
3353 document.resource(type: QTextDocument::ImageResource, name: resource);
3354 QCOMPARE(document.loadedResource(), loaded);
3355}
3356
3357void tst_QTextDocument::QTBUG28998_linkColor()
3358{
3359 QPalette pal;
3360 pal.setColor(acr: QPalette::Link, acolor: QColor("tomato"));
3361 QGuiApplication::setPalette(pal);
3362
3363 QTextDocument doc;
3364 doc.setHtml("<a href=\"http://www.qt-project.org\">Qt</a>");
3365
3366 QCOMPARE(doc.blockCount(), 1);
3367 QTextBlock block = doc.firstBlock();
3368 QVERIFY(block.isValid());
3369
3370 QTextFragment fragment = block.begin().fragment();
3371 QVERIFY(fragment.isValid());
3372
3373 QTextCharFormat format = fragment.charFormat();
3374 QVERIFY(format.isValid());
3375 QVERIFY(format.isAnchor());
3376 QCOMPARE(format.anchorHref(), QStringLiteral("http://www.qt-project.org"));
3377
3378 QCOMPARE(format.foreground(), pal.link());
3379}
3380
3381class ContentsChangeHandler : public QObject
3382{
3383 Q_OBJECT
3384public:
3385 ContentsChangeHandler(QTextDocument *doc)
3386 : verticalMovementX(-1)
3387 , doc(doc)
3388 {
3389 connect(sender: doc, SIGNAL(contentsChange(int,int,int)),
3390 receiver: this, SLOT(saveModifiedText(int, int, int)));
3391 }
3392
3393private slots:
3394 void saveModifiedText(int from, int /*charsRemoved*/, int charsAdded)
3395 {
3396 QTextCursor tmp(doc);
3397 tmp.setPosition(pos: from);
3398 tmp.setPosition(pos: from + charsAdded, mode: QTextCursor::KeepAnchor);
3399 text = tmp.selectedText();
3400 verticalMovementX = tmp.verticalMovementX();
3401 }
3402
3403public:
3404 QString text;
3405 int verticalMovementX;
3406private:
3407 QTextDocument *doc;
3408};
3409
3410void tst_QTextDocument::textCursorUsageWithinContentsChange()
3411{
3412 // force creation of layout
3413 doc->documentLayout();
3414
3415 QTextCursor cursor(doc);
3416 cursor.insertText(text: "initial text");
3417
3418 ContentsChangeHandler handler(doc);
3419
3420 cursor.insertText(text: "new text");
3421
3422 QCOMPARE(handler.text, QString("new text"));
3423 QCOMPARE(handler.verticalMovementX, -1);
3424}
3425
3426void tst_QTextDocument::cssInheritance()
3427{
3428 {
3429 QTextDocument td;
3430 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 200% }</style></head><body>"
3431 "<p>Foo</p><p>Bar</p><p>Baz</p></body></html>");
3432 QTextBlock block = td.begin();
3433 while (block.isValid()) {
3434 QTextBlockFormat fmt = block.blockFormat();
3435 QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::ProportionalHeight));
3436 QCOMPARE(fmt.lineHeight(), qreal(200));
3437 block = block.next();
3438 }
3439 }
3440 {
3441 QTextDocument td;
3442 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 200% } p { line-height: 300% }</style></head><body>"
3443 "<p style=\"line-height: 40px\">Foo</p><p>Bar</p><p>Baz</p></body></html>");
3444 QTextBlock block = td.begin();
3445 QTextBlockFormat fmt = block.blockFormat();
3446 QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::MinimumHeight));
3447 QCOMPARE(fmt.lineHeight(), qreal(40));
3448 block = block.next();
3449 fmt = block.blockFormat();
3450 QCOMPARE(fmt.lineHeightType(), int(QTextBlockFormat::ProportionalHeight));
3451 QCOMPARE(fmt.lineHeight(), qreal(300));
3452 }
3453 {
3454 QTextDocument td;
3455 td.setHtml("<html><head><style type=\"text/css\">body { font-weight: bold; background-color: #ff0000 }</style></head><body>"
3456 "<p>Foo</p><p>Bar</p><p>Baz</p></body></html>");
3457 QTextBlock block = td.begin();
3458 while (block.isValid()) {
3459 QCOMPARE(block.blockFormat().background(), QBrush());
3460 QVERIFY(block.charFormat().font().bold());
3461 block = block.next();
3462 }
3463 }
3464 {
3465 QTextDocument td;
3466 td.setHtml("<html><head><style type=\"text/css\">body { font-style: italic; font-weight: normal; }</style></head><body>"
3467 "<table><tr><th>Foo</th></tr><tr><td>Bar</td></tr></table></body></html>");
3468 QTextBlock block = td.begin();
3469 // First is the table
3470 QTextCharFormat fmt = block.charFormat();
3471 QVERIFY(!fmt.font().bold());
3472 QVERIFY(fmt.font().italic());
3473 // Then the th
3474 block = block.next();
3475 fmt = block.charFormat();
3476 QVERIFY(fmt.font().bold());
3477 QVERIFY(fmt.font().italic());
3478 // Then the td
3479 block = block.next();
3480 fmt = block.charFormat();
3481 QVERIFY(!fmt.font().bold());
3482 QVERIFY(fmt.font().italic());
3483 }
3484 {
3485 QTextDocument td;
3486 td.setHtml("<html><head><style type=\"text/css\">b { font-style: italic; font-weight: normal; }</style></head><body>"
3487 "<p>This should be <b>bold</b></p></body></html>");
3488 QTextBlock block = td.begin();
3489 // First is the p
3490 QTextCharFormat fmt = block.charFormat();
3491 QVERIFY(!fmt.font().bold());
3492 QTextBlock::iterator it = block.begin();
3493 // The non bold text is first
3494 QTextFragment currentFragment = it.fragment();
3495 QVERIFY(currentFragment.isValid());
3496 fmt = currentFragment.charFormat();
3497 QVERIFY(!fmt.font().bold());
3498 ++it;
3499 QVERIFY(!it.atEnd());
3500 // Now check the "bold" text
3501 currentFragment = it.fragment();
3502 QVERIFY(currentFragment.isValid());
3503 fmt = currentFragment.charFormat();
3504 QVERIFY(!fmt.font().bold());
3505 QVERIFY(fmt.font().italic());
3506 }
3507 {
3508 QTextDocument td;
3509 td.setHtml("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"test.css\"></head><body>"
3510 "<p>This should be <b>bold</b></p></body></html>");
3511 QTextBlock block = td.begin();
3512 // First is the p
3513 QTextCharFormat fmt = block.charFormat();
3514 QVERIFY(!fmt.font().bold());
3515 QTextBlock::iterator it = block.begin();
3516 // The non bold text is first
3517 QTextFragment currentFragment = it.fragment();
3518 QVERIFY(currentFragment.isValid());
3519 fmt = currentFragment.charFormat();
3520 QVERIFY(!fmt.font().bold());
3521 ++it;
3522 QVERIFY(!it.atEnd());
3523 // Now check the bold text
3524 currentFragment = it.fragment();
3525 QVERIFY(currentFragment.isValid());
3526 fmt = currentFragment.charFormat();
3527 QVERIFY(fmt.font().bold());
3528 }
3529}
3530
3531void tst_QTextDocument::lineHeightType()
3532{
3533 {
3534 QTextDocument td;
3535 td.setHtml("<html><body>Foobar</body></html>");
3536 QTextBlock block = td.begin();
3537 QTextBlockFormat format = block.blockFormat();
3538 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::SingleHeight));
3539 QCOMPARE(format.lineHeight(), 0.0);
3540 }
3541
3542 {
3543 QTextDocument td;
3544 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 40px; }</style></head><body>Foobar</body></html>");
3545 QTextBlock block = td.begin();
3546 QTextBlockFormat format = block.blockFormat();
3547 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::MinimumHeight));
3548 QCOMPARE(format.lineHeight(), 40.0);
3549 }
3550
3551 {
3552 QTextDocument td;
3553 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 200%; }</style></head><body>Foobar</body></html>");
3554 QTextBlock block = td.begin();
3555 QTextBlockFormat format = block.blockFormat();
3556 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
3557 QCOMPARE(format.lineHeight(), 200.0);
3558 }
3559
3560 {
3561 QTextDocument td;
3562 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 200%; -qt-line-height-type: single; }</style></head><body>Foobar</body></html>");
3563 QTextBlock block = td.begin();
3564 QTextBlockFormat format = block.blockFormat();
3565 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::SingleHeight));
3566 QCOMPARE(format.lineHeight(), 200.0);
3567 }
3568
3569 {
3570 QTextDocument td;
3571 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 40px; -qt-line-height-type: proportional; }</style></head><body>Foobar</body></html>");
3572 QTextBlock block = td.begin();
3573 QTextBlockFormat format = block.blockFormat();
3574 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
3575 QCOMPARE(format.lineHeight(), 40.0);
3576 }
3577
3578 {
3579 QTextDocument td;
3580 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 10; -qt-line-height-type: fixed; }</style></head><body>Foobar</body></html>");
3581 QTextBlock block = td.begin();
3582 QTextBlockFormat format = block.blockFormat();
3583 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight));
3584 QCOMPARE(format.lineHeight(), 10.0);
3585 }
3586
3587 {
3588 QTextDocument td;
3589 td.setHtml("<html><head><style type=\"text/css\">body { -qt-line-height-type: fixed; line-height: 10; -qt-line-height-type: fixed; }</style></head><body>Foobar</body></html>");
3590 QTextBlock block = td.begin();
3591 QTextBlockFormat format = block.blockFormat();
3592 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight));
3593 QCOMPARE(format.lineHeight(), 10.0);
3594 }
3595
3596 {
3597 QTextDocument td;
3598 td.setHtml("<html><head><style type=\"text/css\">body { -qt-line-height-type: proportional; line-height: 3; }</style></head><body>Foobar</body></html>");
3599 QTextBlock block = td.begin();
3600 QTextBlockFormat format = block.blockFormat();
3601 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
3602 QCOMPARE(format.lineHeight(), 3.0);
3603 }
3604
3605 {
3606 QTextDocument td;
3607 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 2.5; -qt-line-height-type: proportional; }</style></head><body>Foobar</body></html>");
3608 QTextBlock block = td.begin();
3609 QTextBlockFormat format = block.blockFormat();
3610 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
3611 QCOMPARE(format.lineHeight(), 2.5);
3612 }
3613
3614 {
3615 QTextDocument td;
3616 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 33; -qt-line-height-type: minimum; }</style></head><body>Foobar</body></html>");
3617 QTextBlock block = td.begin();
3618 QTextBlockFormat format = block.blockFormat();
3619 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::MinimumHeight));
3620 QCOMPARE(format.lineHeight(), 33.0);
3621 }
3622
3623 {
3624 QTextDocument td;
3625 td.setHtml("<html><head><style type=\"text/css\">body { -qt-line-height-type: fixed; line-height: 200%; }</style></head><body>Foobar</body></html>");
3626 QTextBlock block = td.begin();
3627 QTextBlockFormat format = block.blockFormat();
3628 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight));
3629 QCOMPARE(format.lineHeight(), 200.0);
3630 }
3631
3632 {
3633 QTextDocument td;
3634 td.setHtml("<html><head><style type=\"text/css\">body { -qt-line-height-type: fixed; line-height: 200px; }</style></head><body>Foobar</body></html>");
3635 QTextBlock block = td.begin();
3636 QTextBlockFormat format = block.blockFormat();
3637 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::FixedHeight));
3638 QCOMPARE(format.lineHeight(), 200.0);
3639 }
3640}
3641
3642void tst_QTextDocument::cssLineHeightMultiplier()
3643{
3644 {
3645 QTextDocument td;
3646 td.setHtml("<html><head><style type=\"text/css\">body { line-height: 10; }</style></head><body>Foobar</body></html>");
3647 QTextBlock block = td.begin();
3648 QTextBlockFormat format = block.blockFormat();
3649 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
3650 QCOMPARE(format.lineHeight(), 1000.0);
3651 }
3652
3653 {
3654 QTextDocument td;
3655 td.setHtml("<html><head><style type=\"text/css\">body {line-height: 1.38; }</style></head><body>Foobar</body></html>");
3656 QTextBlock block = td.begin();
3657 QTextBlockFormat format = block.blockFormat();
3658 QCOMPARE(int(format.lineHeightType()), int(QTextBlockFormat::ProportionalHeight));
3659 QCOMPARE(format.lineHeight(), 138.0);
3660 }
3661}
3662
3663void tst_QTextDocument::fontTagFace()
3664{
3665 {
3666 QTextDocument td;
3667 td.setHtml("<html><body><font face='Times'>Foobar</font></body></html>");
3668 QTextFragment fragment = td.begin().begin().fragment();
3669 QTextCharFormat format = fragment.charFormat();
3670 QCOMPARE(format.fontFamily(), QLatin1String("Times"));
3671 }
3672
3673 {
3674 QTextDocument td;
3675 td.setHtml("<html><body><font face='Times, serif'>Foobar</font></body></html>");
3676 QTextFragment fragment = td.begin().begin().fragment();
3677 QTextCharFormat format = fragment.charFormat();
3678 QCOMPARE(format.fontFamily(), QLatin1String("Times"));
3679 QStringList expectedFamilies = { QLatin1String("Times"), QLatin1String("serif") };
3680 QCOMPARE(format.fontFamilies().toStringList(), expectedFamilies);
3681 }
3682}
3683
3684void tst_QTextDocument::mergeFontFamilies()
3685{
3686 QTextDocument td;
3687 td.setHtml(QLatin1String(
3688 "<html><body>"
3689 "<span style=\" font-family:'MS Shell Dlg 2';\">Hello world</span>"
3690 "</body></html>"));
3691
3692 QTextCharFormat newFormat;
3693 newFormat.setFontFamily(QLatin1String("Jokerman"));
3694
3695 QTextCursor cursor = QTextCursor(&td);
3696 cursor.setPosition(pos: 0);
3697 cursor.setPosition(pos: QByteArray("Hello World").length(), mode: QTextCursor::KeepAnchor);
3698 cursor.mergeCharFormat(modifier: newFormat);
3699
3700 QVERIFY(td.toHtml().contains(QLatin1String("font-family:'MS Shell Dlg 2','Jokerman';")));
3701
3702 QTextCharFormat newFormatFamilies;
3703 newFormatFamilies.setFontFamilies({ QLatin1String("Arial"), QLatin1String("Helvetica") });
3704 cursor.mergeCharFormat(modifier: newFormatFamilies);
3705
3706 QVERIFY(td.toHtml().contains(QLatin1String("font-family:'Arial','Helvetica','Jokerman'")));
3707
3708 newFormatFamilies.setFontFamilies({ QLatin1String("Arial"), QLatin1String("Jokerman"), QLatin1String("Helvetica") });
3709 cursor.mergeCharFormat(modifier: newFormatFamilies);
3710
3711 QVERIFY(td.toHtml().contains(QLatin1String("font-family:'Arial','Jokerman','Helvetica'")));
3712}
3713
3714void tst_QTextDocument::clearUndoRedoStacks()
3715{
3716 QTextDocument doc;
3717 QTextCursor c(&doc);
3718 c.insertText(QStringLiteral("lorem ipsum"));
3719 QVERIFY(doc.isUndoAvailable());
3720 doc.clearUndoRedoStacks(historyToClear: QTextDocument::UndoStack); // Don't crash
3721 QVERIFY(!doc.isUndoAvailable());
3722}
3723
3724
3725void tst_QTextDocument::contentsChangeIndices_data()
3726{
3727 QTest::addColumn<QString>(name: "html");
3728 // adding list entries change the entire block, so change position is
3729 // not the same as the cursor position if this value is >= 0
3730 QTest::addColumn<int>(name: "expectedBegin");
3731
3732 QTest::addRow(format: "text") << "Test" << -1;
3733 QTest::addRow(format: "unnumbered list") << "<ul><li>Test</li></ul>" << 0;
3734 QTest::addRow(format: "numbered list") << "<ol><li>Test</li></ol>" << 0;
3735 QTest::addRow(format: "table") << "<table><tr><td>Test</td></tr></table>" << -1;
3736}
3737
3738void tst_QTextDocument::contentsChangeIndices()
3739{
3740 QFETCH(QString, html);
3741 QFETCH(int, expectedBegin);
3742
3743 QTextDocument doc;
3744 QTestDocumentLayout *layout = new QTestDocumentLayout(&doc);
3745 doc.setDocumentLayout(layout);
3746 doc.setHtml(QString("<html><body>%1</body></html>").arg(a: html));
3747
3748 int documentLength = 0;
3749 int cursorLength = 0;
3750 int changeBegin = 0;
3751 int changeRemoved = 0;
3752 int changeAdded = 0;
3753 connect(sender: &doc, signal: &QTextDocument::contentsChange, context: this, slot: [&](int pos, int removed, int added){
3754 documentLength = doc.characterCount();
3755
3756 QTextCursor cursor(&doc);
3757 cursor.movePosition(op: QTextCursor::End);
3758 // includes end-of-paragraph character
3759 cursorLength = cursor.position() + 1;
3760
3761 changeBegin = pos;
3762 changeRemoved = removed;
3763 changeAdded = added;
3764 });
3765
3766 QTextCursor cursor(&doc);
3767 cursor.movePosition(op: QTextCursor::End);
3768 if (expectedBegin < 0)
3769 expectedBegin = cursor.position();
3770 cursor.insertBlock();
3771
3772 const int changeEnd = changeBegin + changeAdded;
3773
3774 QVERIFY(documentLength > 0);
3775 QCOMPARE(documentLength, cursorLength);
3776 QVERIFY(documentLength >= changeEnd);
3777 QCOMPARE(changeBegin, expectedBegin);
3778 QCOMPARE(changeAdded - changeRemoved, 1);
3779}
3780
3781void tst_QTextDocument::insertHtmlWithComments_data()
3782{
3783 QTest::addColumn<QString>(name: "html");
3784 QTest::addColumn<QStringList>(name: "expectedBlocks");
3785
3786 QTest::newRow(dataTag: "commentless") << "<p>first</p><p>second</p><p>third</p>"
3787 << QStringList { "first", "second", "third" };
3788 QTest::newRow(dataTag: "normal") << "<p>first</p><!--<p>second</p>--><p>third</p>"
3789 << QStringList { "first", "third" };
3790 QTest::newRow(dataTag: "nonClosing") << "<p>first</p><!--<p>second</p><p>third</p>"
3791 << QStringList { "first" };
3792 QTest::newRow(dataTag: "immediatelyClosing") << "<p>first</p><!----><p>second</p><p>third</p>"
3793 << QStringList { "first", "second", "third" };
3794 QTest::newRow(dataTag: "fake") << "<p>first</p><!-<p>second</p><p>third</p>"
3795 << QStringList { "first", "second", "third" };
3796 QTest::newRow(dataTag: "endingNonExistant") << "<p>first</p>--><p>second</p><p>third</p>"
3797 << QStringList { "first", "-->", "second", "third" };
3798}
3799
3800void tst_QTextDocument::insertHtmlWithComments()
3801{
3802 QFETCH(QString, html);
3803 QFETCH(QStringList, expectedBlocks);
3804
3805 QTextDocument doc;
3806 doc.setHtml(html);
3807
3808 QCOMPARE(doc.blockCount(), expectedBlocks.count());
3809
3810 QStringList blockContent;
3811 auto currentBlock = doc.begin();
3812 while (currentBlock != doc.end()) {
3813 blockContent.append(t: currentBlock.text());
3814 currentBlock = currentBlock.next();
3815 }
3816
3817 QCOMPARE(blockContent, expectedBlocks);
3818}
3819
3820QTEST_MAIN(tst_QTextDocument)
3821#include "tst_qtextdocument.moc"
3822

source code of qtbase/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp