qt5base-lts/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp
Simon Hausmann 12eb3b51c4 Fix incorrect repaints with plain text edit
The plain text edit's smart repaint logic in
QPlainTextDocumentLayout::documentChanged assumes that during a change inside
just one block, the block is in the state before the edit and a call to
layoutBlock() is going to bring it up-to-date. Then only a comparison of the
bounding rect - before and after - is going to allow for smart repaints.

The assumption of the layout being in the same state as before the edit got
broken by commit cc57a2e90f, which introduced
code to use a QTextCursor within a slot connected to QTextDocument's
contentsChange signal. The usage of the QTextCursor there ends up updating the
layout of the block ahead of time, breaking the assumption and therefore the
optimization, in the sense that during changes in the preedit that cause a
change of height / line count, the old bounding rect in
QPlainTextDocumentLayout::documentChanged and the new bounding rect will be the
same. This causes a repaint of only the edited block, missing repaints of the
following blocks, even though the line count effectively changed.

So what's causing QTextCursor to mess with the layout is the attempt of
updating the vertical movement x property. This patch inhibits the update,
marking it as dirty for initialization later. This means that slots connected
to this low-level signal cannot rely on the cursor's visual x position, but
that doesn't seem useful anyway and isn't required for commit
cc57a2e90f.

Task-number: QTBUG-38536
Change-Id: I5fae12d646a4b2d2cc22b9f2d021e5dc8cfdda94
Reviewed-by: Paul Olav Tvete <paul.tvete@digia.com>
Reviewed-by: Eskil Abrahamsen Blomfeldt <eskil.abrahamsen-blomfeldt@digia.com>
Reviewed-by: Konstantin Ritt <ritt.ks@gmail.com>
2014-04-30 18:23:23 +02:00

3073 lines
99 KiB
C++

/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia. For licensing terms and
** conditions see http://qt.digia.com/licensing. For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights. These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>
#include <qtextdocument.h>
#include <qdebug.h>
#include <qtextcursor.h>
#include <qtextdocumentfragment.h>
#include <qtextformat.h>
#include <qtextobject.h>
#include <qtexttable.h>
#include <qabstracttextdocumentlayout.h>
#include <qtextlist.h>
#include <qtextcodec.h>
#include <qguiapplication.h>
#include <qurl.h>
#include <qpainter.h>
#include <qfontmetrics.h>
#include <qimage.h>
#include <qtextlayout.h>
#include <QDomDocument>
#include "common.h"
QT_FORWARD_DECLARE_CLASS(QTextDocument)
class tst_QTextDocument : public QObject
{
Q_OBJECT
public:
tst_QTextDocument();
virtual ~tst_QTextDocument();
public slots:
void init();
void cleanup();
void cleanupTestCase();
private slots:
void getSetCheck();
void isEmpty();
void find_data();
void find();
void find2();
void findWithRegExp_data();
void findWithRegExp();
void findMultiple();
void basicIsModifiedChecks();
void moreIsModified();
void isModified2();
void isModified3();
void isModified4();
void noundo_basicIsModifiedChecks();
void noundo_moreIsModified();
void noundo_isModified2();
void noundo_isModified3();
void mightBeRichText();
void mightBeRichText_data();
void task240325();
void stylesheetFont_data();
void stylesheetFont();
void toHtml_data();
void toHtml();
void toHtml2();
void setFragmentMarkersInHtmlExport();
void toHtmlBodyBgColor();
void toHtmlBodyBgColorRgba();
void toHtmlBodyBgColorTransparent();
void toHtmlRootFrameProperties();
void toHtmlLineHeightProperties();
void capitalizationHtmlInExport();
void wordspacingHtmlExport();
void cursorPositionChanged();
void cursorPositionChangedOnSetText();
void textFrameIterator();
void codecForHtml();
void markContentsDirty();
void clonePreservesMetaInformation();
void clonePreservesPageSize();
void clonePreservesPageBreakPolicies();
void clonePreservesDefaultFont();
void clonePreservesRootFrameFormat();
void clonePreservesResources();
void clonePreservesUserStates();
void clonePreservesIndentWidth();
void blockCount();
void defaultStyleSheet();
void resolvedFontInEmptyFormat();
void defaultRootFrameMargin();
void clearResources();
void setPlainText();
void toPlainText();
void deleteTextObjectsOnClear();
void maximumBlockCount();
void adjustSize();
void initialUserData();
void html_defaultFont();
void blockCountChanged();
void nonZeroDocumentLengthOnClear();
void setTextPreservesUndoRedoEnabled();
void firstLast();
void backgroundImage_toHtml();
void backgroundImage_toHtml2();
void backgroundImage_clone();
void backgroundImage_copy();
void documentCleanup();
void characterAt();
void revisions();
void revisionWithUndoCompressionAndUndo();
void testUndoCommandAdded();
void testUndoBlocks();
void receiveCursorPositionChangedAfterContentsChange();
void copiedFontSize();
void QTBUG25778_pixelSizeFromHtml();
void htmlExportImportBlockCount();
void QTBUG27354_spaceAndSoftSpace();
void baseUrl_data();
void baseUrl();
void QTBUG28998_linkColor();
void textCursorUsageWithinContentsChange();
private:
void backgroundImage_checkExpectedHtml(const QTextDocument &doc);
QTextDocument *doc;
QTextCursor cursor;
QFont defaultFont;
QString htmlHead;
QString htmlTail;
};
class MyAbstractTextDocumentLayout : public QAbstractTextDocumentLayout
{
public:
MyAbstractTextDocumentLayout(QTextDocument *doc) : QAbstractTextDocumentLayout(doc) {}
void draw(QPainter *, const PaintContext &) {}
int hitTest(const QPointF &, Qt::HitTestAccuracy) const { return 0; }
int pageCount() const { return 0; }
QSizeF documentSize() const { return QSizeF(); }
QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); }
QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); }
void documentChanged(int, int, int) {}
};
// Testing get/set functions
void tst_QTextDocument::getSetCheck()
{
QTextDocument obj1;
// QAbstractTextDocumentLayout * QTextDocument::documentLayout()
// void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *)
QPointer<MyAbstractTextDocumentLayout> var1 = new MyAbstractTextDocumentLayout(0);
obj1.setDocumentLayout(var1);
QCOMPARE(static_cast<QAbstractTextDocumentLayout *>(var1), obj1.documentLayout());
obj1.setDocumentLayout((QAbstractTextDocumentLayout *)0);
QVERIFY(var1.isNull());
QVERIFY(obj1.documentLayout());
// bool QTextDocument::useDesignMetrics()
// void QTextDocument::setUseDesignMetrics(bool)
obj1.setUseDesignMetrics(false);
QCOMPARE(false, obj1.useDesignMetrics());
obj1.setUseDesignMetrics(true);
QCOMPARE(true, obj1.useDesignMetrics());
}
tst_QTextDocument::tst_QTextDocument()
{
QImage img(16, 16, QImage::Format_ARGB32_Premultiplied);
img.save("foo.png");
}
tst_QTextDocument::~tst_QTextDocument()
{
}
void tst_QTextDocument::init()
{
doc = new QTextDocument;
cursor = QTextCursor(doc);
defaultFont = QFont();
htmlHead = QString("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
"\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head>"
"<body style=\" font-family:'%1'; font-size:%2pt; font-weight:%3; font-style:%4;\">\n");
htmlHead = htmlHead.arg(defaultFont.family()).arg(defaultFont.pointSizeF()).arg(defaultFont.weight() * 8).arg((defaultFont.italic() ? "italic" : "normal"));
htmlTail = QString("</body></html>");
}
void tst_QTextDocument::cleanup()
{
cursor = QTextCursor();
delete doc;
doc = 0;
}
void tst_QTextDocument::cleanupTestCase()
{
QFile::remove(QLatin1String("foo.png"));
}
void tst_QTextDocument::isEmpty()
{
QVERIFY(doc->isEmpty());
}
void tst_QTextDocument::find_data()
{
QTest::addColumn<QString>("haystack");
QTest::addColumn<QString>("needle");
QTest::addColumn<int>("flags");
QTest::addColumn<int>("from");
QTest::addColumn<int>("anchor");
QTest::addColumn<int>("position");
QTest::newRow("1") << "Hello World" << "World" << int(QTextDocument::FindCaseSensitively) << 0 << 6 << 11;
QTest::newRow("2") << QString::fromLatin1("Hello") + QString(QChar::ParagraphSeparator) + QString::fromLatin1("World")
<< "World" << int(QTextDocument::FindCaseSensitively) << 1 << 6 << 11;
QTest::newRow("3") << QString::fromLatin1("Hello") + QString(QChar::ParagraphSeparator) + QString::fromLatin1("World")
<< "Hello" << int(QTextDocument::FindCaseSensitively | QTextDocument::FindBackward) << 10 << 0 << 5;
QTest::newRow("4wholewords") << QString::fromLatin1("Hello Blah World")
<< "Blah" << int(QTextDocument::FindWholeWords) << 0 << 6 << 10;
QTest::newRow("5wholewords") << QString::fromLatin1("HelloBlahWorld")
<< "Blah" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1;
QTest::newRow("6wholewords") << QString::fromLatin1("HelloBlahWorld Blah Hah")
<< "Blah" << int(QTextDocument::FindWholeWords) << 0 << 15 << 19;
QTest::newRow("7wholewords") << QString::fromLatin1("HelloBlahWorld Blah Hah")
<< "Blah" << int(QTextDocument::FindWholeWords | QTextDocument::FindBackward) << 23 << 15 << 19;
QTest::newRow("8wholewords") << QString::fromLatin1("Hello: World\n")
<< "orld" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1;
QTest::newRow("across-paragraphs") << QString::fromLatin1("First Parag\nSecond Parag with a lot more text")
<< "Parag" << int(QTextDocument::FindBackward)
<< 15 << 6 << 11;
QTest::newRow("nbsp") << "Hello" + QString(QChar(QChar::Nbsp)) +"World" << " " << int(QTextDocument::FindCaseSensitively) << 0 << 5 << 6;
}
void tst_QTextDocument::find()
{
QFETCH(QString, haystack);
QFETCH(QString, needle);
QFETCH(int, flags);
QFETCH(int, from);
QFETCH(int, anchor);
QFETCH(int, position);
cursor.insertText(haystack);
cursor = doc->find(needle, from, QTextDocument::FindFlags(flags));
if (anchor != -1) {
QCOMPARE(cursor.anchor(), anchor);
QCOMPARE(cursor.position(), position);
} else {
QVERIFY(cursor.isNull());
}
//search using a regular expression
QRegExp expr(needle);
expr.setPatternSyntax(QRegExp::FixedString);
QTextDocument::FindFlags flg(flags);
expr.setCaseSensitivity((flg & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
cursor = doc->find(expr, from, flg);
if (anchor != -1) {
QCOMPARE(cursor.anchor(), anchor);
QCOMPARE(cursor.position(), position);
} else {
QVERIFY(cursor.isNull());
}
}
void tst_QTextDocument::findWithRegExp_data()
{
QTest::addColumn<QString>("haystack");
QTest::addColumn<QString>("needle");
QTest::addColumn<int>("flags");
QTest::addColumn<int>("from");
QTest::addColumn<int>("anchor");
QTest::addColumn<int>("position");
// match integers 0 to 99
QTest::newRow("1") << "23" << "^\\d\\d?$" << int(QTextDocument::FindCaseSensitively) << 0 << 0 << 2;
// match ampersands but not &amp;
QTest::newRow("2") << "His &amp; hers & theirs" << "&(?!amp;)"<< int(QTextDocument::FindCaseSensitively) << 0 << 15 << 16;
//backward search
QTest::newRow("3") << QString::fromLatin1("HelloBlahWorld Blah Hah")
<< "h" << int(QTextDocument::FindBackward) << 18 << 8 << 9;
}
void tst_QTextDocument::findWithRegExp()
{
QFETCH(QString, haystack);
QFETCH(QString, needle);
QFETCH(int, flags);
QFETCH(int, from);
QFETCH(int, anchor);
QFETCH(int, position);
cursor.insertText(haystack);
//search using a regular expression
QRegExp expr(needle);
QTextDocument::FindFlags flg(flags);
expr.setCaseSensitivity((flg & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive);
cursor = doc->find(expr, from, flg);
if (anchor != -1) {
QCOMPARE(cursor.anchor(), anchor);
QCOMPARE(cursor.position(), position);
} else {
QVERIFY(cursor.isNull());
}
}
void tst_QTextDocument::find2()
{
doc->setPlainText("aaa");
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
QTextCursor hit = doc->find("a", cursor);
QCOMPARE(hit.position(), 2);
QCOMPARE(hit.anchor(), 1);
}
void tst_QTextDocument::findMultiple()
{
const QString text("foo bar baz foo bar baz");
doc->setPlainText(text);
cursor.movePosition(QTextCursor::Start);
cursor = doc->find("bar", cursor);
QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor = doc->find("bar", cursor);
QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor.movePosition(QTextCursor::End);
cursor = doc->find("bar", cursor, QTextDocument::FindBackward);
QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor = doc->find("bar", cursor, QTextDocument::FindBackward);
QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
QRegExp expr("bar");
expr.setPatternSyntax(QRegExp::FixedString);
cursor.movePosition(QTextCursor::End);
cursor = doc->find(expr, cursor, QTextDocument::FindBackward);
QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor = doc->find(expr, cursor, QTextDocument::FindBackward);
QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor.movePosition(QTextCursor::Start);
cursor = doc->find(expr, cursor);
QCOMPARE(cursor.selectionStart(), text.indexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
cursor = doc->find(expr, cursor);
QCOMPARE(cursor.selectionStart(), text.lastIndexOf("bar"));
QCOMPARE(cursor.selectionEnd(), cursor.selectionStart() + 3);
}
void tst_QTextDocument::basicIsModifiedChecks()
{
QSignalSpy spy(doc, SIGNAL(modificationChanged(bool)));
QVERIFY(!doc->isModified());
cursor.insertText("Hello World");
QVERIFY(doc->isModified());
QCOMPARE(spy.count(), 1);
QVERIFY(spy.takeFirst().at(0).toBool());
doc->undo();
QVERIFY(!doc->isModified());
QCOMPARE(spy.count(), 1);
QVERIFY(!spy.takeFirst().at(0).toBool());
doc->redo();
QVERIFY(doc->isModified());
QCOMPARE(spy.count(), 1);
QVERIFY(spy.takeFirst().at(0).toBool());
}
void tst_QTextDocument::moreIsModified()
{
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
doc->undo();
QVERIFY(!doc->isModified());
}
void tst_QTextDocument::isModified2()
{
// reported on qt4-preview-feedback
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->setModified(false);
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
}
void tst_QTextDocument::isModified3()
{
QVERIFY(!doc->isModified());
doc->setUndoRedoEnabled(false);
doc->setUndoRedoEnabled(true);
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(!doc->isModified());
}
void tst_QTextDocument::isModified4()
{
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
cursor.insertText("World");
doc->setModified(false);
QVERIFY(!doc->isModified());
cursor.insertText("Again");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(!doc->isModified());
doc->undo();
QVERIFY(doc->isModified());
doc->redo();
QVERIFY(!doc->isModified());
doc->redo();
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(!doc->isModified());
doc->undo();
QVERIFY(doc->isModified());
//task 197769
cursor.insertText("Hello");
QVERIFY(doc->isModified());
}
void tst_QTextDocument::noundo_basicIsModifiedChecks()
{
doc->setUndoRedoEnabled(false);
QSignalSpy spy(doc, SIGNAL(modificationChanged(bool)));
QVERIFY(!doc->isModified());
cursor.insertText("Hello World");
QVERIFY(doc->isModified());
QCOMPARE(spy.count(), 1);
QVERIFY(spy.takeFirst().at(0).toBool());
doc->undo();
QVERIFY(doc->isModified());
QCOMPARE(spy.count(), 0);
doc->redo();
QVERIFY(doc->isModified());
QCOMPARE(spy.count(), 0);
}
void tst_QTextDocument::task240325()
{
doc->setHtml("<html><img width=\"100\" height=\"100\" align=\"right\"/>Foobar Foobar Foobar Foobar</html>");
QImage img(1000, 7000, QImage::Format_ARGB32_Premultiplied);
QPainter p(&img);
QFontMetrics fm(p.font());
// Set page size to contain image and one "Foobar"
doc->setPageSize(QSize(100 + fm.width("Foobar")*2, 1000));
// Force layout
doc->drawContents(&p);
QCOMPARE(doc->blockCount(), 1);
for (QTextBlock block = doc->begin() ; block!=doc->end() ; block = block.next()) {
QTextLayout *layout = block.layout();
QCOMPARE(layout->lineCount(), 4);
for (int lineIdx=0;lineIdx<layout->lineCount();++lineIdx) {
QTextLine line = layout->lineAt(lineIdx);
QString text = block.text().mid(line.textStart(), line.textLength()).trimmed();
// Remove start token
if (lineIdx == 0)
text = text.mid(1);
QCOMPARE(text, QString::fromLatin1("Foobar"));
}
}
}
void tst_QTextDocument::stylesheetFont_data()
{
QTest::addColumn<QString>("stylesheet");
QTest::addColumn<QFont>("font");
{
QFont font;
font.setBold(true);
font.setPixelSize(64);
QTest::newRow("Regular font specification")
<< "font-size: 64px; font-weight: bold;"
<< font;
}
{
QFont font;
font.setBold(true);
font.setPixelSize(64);
QTest::newRow("Shorthand font specification")
<< "font: normal bold 64px Arial;"
<< font;
}
}
void tst_QTextDocument::stylesheetFont()
{
QFETCH(QString, stylesheet);
QFETCH(QFont, font);
QString html = QString::fromLatin1("<html>"
"<body>"
"<div style=\"%1\" >"
"Foobar"
"</div>"
"</body>"
"</html>").arg(stylesheet);
qDebug() << html;
doc->setHtml(html);
QCOMPARE(doc->blockCount(), 1);
// First and only block
QTextBlock block = doc->firstBlock();
QString text = block.text();
QCOMPARE(text, QString::fromLatin1("Foobar"));
QFont actualFont = block.charFormat().font();
QCOMPARE(actualFont.bold(), font.bold());
QCOMPARE(actualFont.pixelSize(), font.pixelSize());
}
void tst_QTextDocument::noundo_moreIsModified()
{
doc->setUndoRedoEnabled(false);
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(doc->isModified());
cursor.insertText("Hello");
doc->undo();
QVERIFY(doc->isModified());
}
void tst_QTextDocument::noundo_isModified2()
{
// reported on qt4-preview-feedback
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->setModified(false);
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
}
void tst_QTextDocument::noundo_isModified3()
{
doc->setUndoRedoEnabled(false);
QVERIFY(!doc->isModified());
cursor.insertText("Hello");
QVERIFY(doc->isModified());
doc->undo();
QVERIFY(doc->isModified());
}
void tst_QTextDocument::mightBeRichText_data()
{
const char qtDocuHeader[] = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
"<!DOCTYPE html\n"
" PUBLIC ""-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">";
QVERIFY(Qt::mightBeRichText(QString::fromLatin1(qtDocuHeader)));
QTest::addColumn<QString>("input");
QTest::addColumn<bool>("result");
QTest::newRow("documentation-header") << QString("<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n"
"<!DOCTYPE html\n"
" PUBLIC ""-//W3C//DTD XHTML 1.0 Strict//EN\" \"DTD/xhtml1-strict.dtd\">\n"
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">")
<< true;
QTest::newRow("br-nospace") << QString("Test <br/> new line") << true;
QTest::newRow("br-space") << QString("Test <br /> new line") << true;
QTest::newRow("br-invalidspace") << QString("Test <br/ > new line") << false;
QTest::newRow("invalid closing tag") << QString("Test <br/ line") << false;
}
void tst_QTextDocument::mightBeRichText()
{
QFETCH(QString, input);
QFETCH(bool, result);
QVERIFY(result == Qt::mightBeRichText(input));
}
Q_DECLARE_METATYPE(QTextDocumentFragment)
#define CREATE_DOC_AND_CURSOR() \
QTextDocument doc; \
doc.setDefaultFont(defaultFont); \
QTextCursor cursor(&doc);
void tst_QTextDocument::toHtml_data()
{
QTest::addColumn<QTextDocumentFragment>("input");
QTest::addColumn<QString>("expectedOutput");
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QTest::newRow("simple") << QTextDocumentFragment(&doc) << QString("<p DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("&<>");
QTest::newRow("entities") << QTextDocumentFragment(&doc) << QString("<p DEFAULTBLOCKSTYLE>&amp;&lt;&gt;</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontFamily("Times");
cursor.insertText("Blah", fmt);
QTest::newRow("font-family") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:'Times';\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontFamily("Foo's Family");
cursor.insertText("Blah", fmt);
QTest::newRow("font-family-with-quotes1") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:&quot;Foo's Family&quot;;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontFamily("Foo\"s Family");
cursor.insertText("Blah", fmt);
QTest::newRow("font-family-with-quotes2") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-family:'Foo&quot;s Family';\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setNonBreakableLines(true);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("pre") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<pre DEFAULTBLOCKSTYLE>Blah</pre>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontPointSize(40);
cursor.insertText("Blah", fmt);
QTest::newRow("font-size") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-size:40pt;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setProperty(QTextFormat::FontSizeIncrement, 2);
cursor.insertText("Blah", fmt);
QTest::newRow("logical-font-size") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-size:x-large;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Foo");
QTextCharFormat fmt;
fmt.setFontPointSize(40);
cursor.insertBlock(QTextBlockFormat(), fmt);
fmt.clearProperty(QTextFormat::FontPointSize);
cursor.insertText("Blub", fmt);
QTest::newRow("no-font-size") << QTextDocumentFragment(&doc)
<< 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>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setLayoutDirection(Qt::RightToLeft);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("rtl") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p dir='rtl' DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignJustify);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("blockalign") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p align=\"justify\" DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignCenter);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("blockalign2") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p align=\"center\" DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setAlignment(Qt::AlignRight | Qt::AlignAbsolute);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("blockalign3") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p align=\"right\" DEFAULTBLOCKSTYLE>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setBackground(QColor("#0000ff"));
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("bgcolor") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE background-color:#0000ff;\">Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setBackground(QColor(255, 0, 0, 51));
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("bgcolor-rgba") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE background-color:rgba(255,0,0,0.2);\">Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setBackground(QColor(255, 0, 0, 0));
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("bgcolor-transparent") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE background-color:transparent;\">Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontWeight(40);
cursor.insertText("Blah", fmt);
QTest::newRow("font-weight") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-weight:320;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontItalic(true);
cursor.insertText("Blah", fmt);
QTest::newRow("font-italic") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" font-style:italic;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setFontUnderline(true);
fmt.setFontOverline(false);
cursor.insertText("Blah", fmt);
QTest::newRow("text-decoration-1") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" text-decoration: underline;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
cursor.insertText("Blah", fmt);
QTest::newRow("color") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" color:#00ff00;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor(0, 255, 0, 51));
cursor.insertText("Blah", fmt);
QTest::newRow("color-rgba") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" color:rgba(0,255,0,0.2);\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor(0, 255, 0, 0));
cursor.insertText("Blah", fmt);
QTest::newRow("color-transparent") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" color:transparent;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setBackground(QColor("#00ff00"));
cursor.insertText("Blah", fmt);
QTest::newRow("span-bgcolor") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setBackground(QColor(0, 255, 0, 51));
cursor.insertText("Blah", fmt);
QTest::newRow("span-bgcolor-rgba") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:rgba(0,255,0,0.2);\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setBackground(QColor(0, 255, 0, 0));
cursor.insertText("Blah", fmt);
QTest::newRow("span-bgcolor-transparent") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:transparent;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setVerticalAlignment(QTextCharFormat::AlignSubScript);
cursor.insertText("Blah", fmt);
QTest::newRow("valign-sub") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" vertical-align:sub;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setVerticalAlignment(QTextCharFormat::AlignSuperScript);
cursor.insertText("Blah", fmt);
QTest::newRow("valign-super") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" vertical-align:super;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setAnchor(true);
fmt.setAnchorName("blub");
cursor.insertText("Blah", fmt);
QTest::newRow("named anchor") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><a name=\"blub\"></a>Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setAnchor(true);
fmt.setAnchorHref("http://www.kde.org/");
cursor.insertText("Blah", fmt);
QTest::newRow("href anchor") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/\">Blah</a></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setAnchor(true);
fmt.setAnchorHref("http://www.kde.org/?a=1&b=2");
cursor.insertText("Blah", fmt);
QTest::newRow("href anchor with &") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/?a=1&amp;b=2\">Blah</a></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setAnchor(true);
fmt.setAnchorHref("http://www.kde.org/?a='&b=\"");
cursor.insertText("Blah", fmt);
QTest::newRow("href anchor with ' and \"") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><a href=\"http://www.kde.org/?a='&amp;b=&quot;\">Blah</a></p>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertTable(2, 2);
QTest::newRow("simpletable") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(1, 4);
table->mergeCells(0, 0, 1, 2);
table->mergeCells(0, 2, 1, 2);
QTest::newRow("tablespans") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td colspan=\"2\"></td>\n<td colspan=\"2\"></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
fmt.setBorder(1);
fmt.setCellSpacing(3);
fmt.setCellPadding(3);
fmt.setBackground(QColor("#ff00ff"));
fmt.setWidth(QTextLength(QTextLength::PercentageLength, 50));
fmt.setAlignment(Qt::AlignHCenter);
fmt.setPosition(QTextFrameFormat::FloatRight);
cursor.insertTable(2, 2, fmt);
QTest::newRow("tableattrs") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" style=\" float: right;\" align=\"center\" width=\"50%\" cellspacing=\"3\" cellpadding=\"3\" bgcolor=\"#ff00ff\">"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
fmt.setBorder(1);
fmt.setCellSpacing(3);
fmt.setCellPadding(3);
fmt.setBackground(QColor("#ff00ff"));
fmt.setWidth(QTextLength(QTextLength::PercentageLength, 50));
fmt.setAlignment(Qt::AlignHCenter);
fmt.setPosition(QTextFrameFormat::FloatRight);
fmt.setLeftMargin(25);
fmt.setBottomMargin(35);
cursor.insertTable(2, 2, fmt);
QTest::newRow("tableattrs2") << QTextDocumentFragment(&doc)
<< 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\">"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
fmt.setHeaderRowCount(2);
cursor.insertTable(4, 2, fmt);
QTest::newRow("tableheader") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"<thead>\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr></thead>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(2, 2);
QTextTable *subTable = table->cellAt(0, 1).firstCursorPosition().insertTable(1, 1);
subTable->cellAt(0, 0).firstCursorPosition().insertText("Hey");
QTest::newRow("nestedtable") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\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>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
QVector<QTextLength> widths;
widths.append(QTextLength());
widths.append(QTextLength(QTextLength::PercentageLength, 30));
widths.append(QTextLength(QTextLength::FixedLength, 40));
fmt.setColumnWidthConstraints(widths);
cursor.insertTable(1, 3, fmt);
QTest::newRow("colwidths") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td></td>\n<td width=\"30%\"></td>\n<td width=\"40\"></td></tr>"
"</table>");
}
// ### rowspan/colspan tests, once texttable api for that is back again
//
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(1, 1);
QTextCursor cellCurs = table->cellAt(0, 0).firstCursorPosition();
QTextCharFormat fmt;
fmt.setBackground(QColor("#ffffff"));
cellCurs.mergeBlockCharFormat(fmt);
QTest::newRow("cellproperties") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td bgcolor=\"#ffffff\"></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
// ### fixme: use programmatic api as soon as we can create floats through it
const char html[] = "<html><body>Blah<img src=\"image.png\" width=\"10\" height=\"20\" style=\"float: right;\" />Blubb</body></html>";
QTest::newRow("image") << QTextDocumentFragment::fromHtml(QString::fromLatin1(html))
<< 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>");
}
{
CREATE_DOC_AND_CURSOR();
QTextImageFormat fmt;
fmt.setName("foo");
fmt.setVerticalAlignment(QTextCharFormat::AlignMiddle);
cursor.insertImage(fmt);
QTest::newRow("image-align-middle") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" style=\"vertical-align: middle;\" /></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextImageFormat fmt;
fmt.setName("foo");
fmt.setVerticalAlignment(QTextCharFormat::AlignTop);
cursor.insertImage(fmt);
QTest::newRow("image-align-top") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" style=\"vertical-align: top;\" /></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextImageFormat fmt;
fmt.setName("foo");
cursor.insertImage(fmt);
cursor.insertImage(fmt);
QTest::newRow("2images") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><img src=\"foo\" /><img src=\"foo\" /></p>");
}
{
CREATE_DOC_AND_CURSOR();
QString txt = QLatin1String("Blah");
txt += QChar::LineSeparator;
txt += QLatin1String("Bar");
cursor.insertText(txt);
QTest::newRow("linebreaks") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE>Blah<br />Bar</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setTopMargin(10);
fmt.setBottomMargin(20);
fmt.setLeftMargin(30);
fmt.setRightMargin(40);
cursor.insertBlock(fmt);
cursor.insertText("Blah");
QTest::newRow("blockmargins") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p style=\" margin-top:10px; margin-bottom:20px; margin-left:30px; margin-right:40px; -qt-block-indent:0; text-indent:0px;\">Blah</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextList *list = cursor.insertList(QTextListFormat::ListDisc);
cursor.insertText("Blubb");
cursor.insertBlock();
cursor.insertText("Blah");
QCOMPARE(list->count(), 2);
QTest::newRow("lists") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
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>");
}
{
CREATE_DOC_AND_CURSOR();
QTextList *list = cursor.insertList(QTextListFormat::ListDisc);
cursor.insertText("Blubb");
cursor.insertBlock();
QTextCharFormat blockCharFmt;
blockCharFmt.setForeground(QColor("#0000ff"));
cursor.mergeBlockCharFormat(blockCharFmt);
QTextCharFormat fmt;
fmt.setForeground(QColor("#ff0000"));
cursor.insertText("Blah", fmt);
QCOMPARE(list->count(), 2);
QTest::newRow("charfmt-for-list-item") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
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>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setIndent(3);
fmt.setTextIndent(30);
cursor.insertBlock(fmt);
cursor.insertText("Test");
QTest::newRow("block-indent") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:3; text-indent:30px;\">Test</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextListFormat fmt;
fmt.setStyle(QTextListFormat::ListDisc);
fmt.setIndent(4);
cursor.insertList(fmt);
cursor.insertText("Blah");
QTest::newRow("list-indent") << QTextDocumentFragment(&doc)
<<
QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 4;\"><li DEFAULTBLOCKSTYLE>Blah</li></ul>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertBlock();
QTest::newRow("emptyblock") << QTextDocumentFragment(&doc)
// after insertBlock() we /do/ have two blocks in the document, so also expect
// these in the html output
<< QString("EMPTYBLOCK") + QString("EMPTYBLOCK");
}
{
CREATE_DOC_AND_CURSOR();
// if you press enter twice in an empty textedit and then insert 'Test'
// you actually get three visible paragraphs, two empty leading ones and
// a third with the actual text. the corresponding html representation
// therefore should also contain three paragraphs.
cursor.insertBlock();
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
fmt.setProperty(QTextFormat::FontSizeIncrement, 1);
cursor.mergeBlockCharFormat(fmt);
fmt.setProperty(QTextFormat::FontSizeIncrement, 2);
cursor.insertText("Test", fmt);
QTest::newRow("blockcharfmt") << QTextDocumentFragment(&doc)
<< 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>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
cursor.setBlockCharFormat(fmt);
fmt.setForeground(QColor("#0000ff"));
cursor.insertText("Test", fmt);
QTest::newRow("blockcharfmt2") << QTextDocumentFragment(&doc)
<< 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>");
}
{
QTest::newRow("horizontal-ruler") << QTextDocumentFragment::fromHtml("<hr />")
<<
QString("EMPTYBLOCK") +
QString("<hr />");
}
{
QTest::newRow("horizontal-ruler-with-width") << QTextDocumentFragment::fromHtml("<hr width=\"50%\"/>")
<<
QString("EMPTYBLOCK") +
QString("<hr width=\"50%\"/>");
}
{
CREATE_DOC_AND_CURSOR();
QTextFrame *mainFrame = cursor.currentFrame();
QTextFrameFormat ffmt;
ffmt.setBorder(1);
ffmt.setPosition(QTextFrameFormat::FloatRight);
ffmt.setMargin(2);
ffmt.setWidth(100);
ffmt.setHeight(50);
ffmt.setBackground(QColor("#00ff00"));
cursor.insertFrame(ffmt);
cursor.insertText("Hello World");
cursor = mainFrame->lastCursorPosition();
QTest::newRow("frame") << QTextDocumentFragment(&doc)
<< 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>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
// fmt.setBackground(QColor("#0000ff"));
cursor.setBlockCharFormat(fmt);
fmt.setForeground(QBrush());
// fmt.setBackground(QBrush());
cursor.insertText("Test", fmt);
// 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>");
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;\">Test</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(2, 2);
table->mergeCells(0, 0, 1, 2);
QTextTableFormat fmt = table->format();
QVector<QTextLength> widths;
widths.append(QTextLength(QTextLength::FixedLength, 20));
widths.append(QTextLength(QTextLength::FixedLength, 40));
fmt.setColumnWidthConstraints(widths);
table->setFormat(fmt);
QTest::newRow("mergedtablecolwidths") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td colspan=\"2\"></td></tr>"
"\n<tr>\n<td width=\"20\"></td>\n<td width=\"40\"></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextCharFormat fmt;
cursor.insertText("Blah\nGreen yellow green");
cursor.setPosition(0);
cursor.setPosition(23, QTextCursor::KeepAnchor);
fmt.setBackground(Qt::green);
cursor.mergeCharFormat(fmt);
cursor.clearSelection();
cursor.setPosition(11);
cursor.setPosition(17, QTextCursor::KeepAnchor);
fmt.setBackground(Qt::yellow);
cursor.mergeCharFormat(fmt);
cursor.clearSelection();
QTest::newRow("multiparagraph-bgcolor") << QTextDocumentFragment(&doc)
<< QString("<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Blah</span></p>\n"
"<p DEFAULTBLOCKSTYLE><span style=\" background-color:#00ff00;\">Green </span>"
"<span style=\" background-color:#ffff00;\">yellow</span>"
"<span style=\" background-color:#00ff00;\"> green</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat fmt;
fmt.setBackground(QColor("#0000ff"));
cursor.insertBlock(fmt);
QTextCharFormat charfmt;
charfmt.setBackground(QColor("#0000ff"));
cursor.insertText("Blah", charfmt);
QTest::newRow("nospan-bgcolor") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE background-color:#0000ff;\"><span style=\" background-color:#0000ff;\">Blah</span></p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(2, 2);
QTextCharFormat fmt = table->cellAt(0, 0).format();
fmt.setVerticalAlignment(QTextCharFormat::AlignMiddle);
table->cellAt(0, 0).setFormat(fmt);
fmt = table->cellAt(0, 1).format();
fmt.setVerticalAlignment(QTextCharFormat::AlignTop);
table->cellAt(0, 1).setFormat(fmt);
fmt = table->cellAt(1, 0).format();
fmt.setVerticalAlignment(QTextCharFormat::AlignBottom);
table->cellAt(1, 0).setFormat(fmt);
table->cellAt(0, 0).firstCursorPosition().insertText("Blah");
QTest::newRow("table-vertical-alignment") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td style=\" vertical-align:middle;\">\n"
"<p DEFAULTBLOCKSTYLE>Blah</p></td>"
"\n<td style=\" vertical-align:top;\"></td></tr>"
"\n<tr>\n<td style=\" vertical-align:bottom;\"></td>"
"\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTable *table = cursor.insertTable(2, 2);
QTextTableCellFormat fmt = table->cellAt(0, 0).format().toTableCellFormat();
fmt.setLeftPadding(1);
table->cellAt(0, 0).setFormat(fmt);
fmt = table->cellAt(0, 1).format().toTableCellFormat();
fmt.setRightPadding(1);
table->cellAt(0, 1).setFormat(fmt);
fmt = table->cellAt(1, 0).format().toTableCellFormat();
fmt.setTopPadding(1);
table->cellAt(1, 0).setFormat(fmt);
fmt = table->cellAt(1, 1).format().toTableCellFormat();
fmt.setBottomPadding(1);
table->cellAt(1, 1).setFormat(fmt);
table->cellAt(0, 0).firstCursorPosition().insertText("Blah");
QTest::newRow("table-cell-paddings") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" cellspacing=\"2\">"
"\n<tr>\n<td style=\" padding-left:1;\">\n"
"<p DEFAULTBLOCKSTYLE>Blah</p></td>"
"\n<td style=\" padding-right:1;\"></td></tr>"
"\n<tr>\n<td style=\" padding-top:1;\"></td>"
"\n<td style=\" padding-bottom:1;\"></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextTableFormat fmt;
fmt.setBorderBrush(QColor("#0000ff"));
fmt.setBorderStyle(QTextFrameFormat::BorderStyle_Solid);
cursor.insertTable(2, 2, fmt);
QTest::newRow("tableborder") << QTextDocumentFragment(&doc)
<< QString("<table border=\"1\" style=\" border-color:#0000ff; border-style:solid;\" cellspacing=\"2\">"
"\n<tr>\n<td></td>\n<td></td></tr>"
"\n<tr>\n<td></td>\n<td></td></tr>"
"</table>");
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertBlock();
cursor.insertText("Foo");
cursor.block().setUserState(42);
QTest::newRow("userstate") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE -qt-user-state:42;\">Foo</p>");
}
{
CREATE_DOC_AND_CURSOR();
QTextBlockFormat blockFmt;
blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
cursor.insertBlock(blockFmt);
cursor.insertText("Foo");
blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore | QTextFormat::PageBreak_AlwaysAfter);
cursor.insertBlock(blockFmt);
cursor.insertText("Bar");
QTextTableFormat tableFmt;
tableFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter);
cursor.insertTable(1, 1, tableFmt);
QTest::newRow("pagebreak") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<p OPENDEFAULTBLOCKSTYLE page-break-before:always;\">Foo</p>"
"\n<p OPENDEFAULTBLOCKSTYLE page-break-before:always; page-break-after:always;\">Bar</p>"
"\n<table border=\"1\" style=\" page-break-after:always;\" cellspacing=\"2\">\n<tr>\n<td></td></tr></table>");
}
{
CREATE_DOC_AND_CURSOR();
QTextListFormat listFmt;
listFmt.setStyle(QTextListFormat::ListDisc);
cursor.insertList(listFmt);
cursor.insertText("Blah");
QTest::newRow("list-ul-margin") << QTextDocumentFragment(&doc)
<< QString("EMPTYBLOCK") +
QString("<ul style=\"margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;\"><li DEFAULTBLOCKSTYLE>Blah</li></ul>");
}
}
void tst_QTextDocument::toHtml()
{
QFETCH(QTextDocumentFragment, input);
QFETCH(QString, expectedOutput);
cursor.insertFragment(input);
expectedOutput.prepend(htmlHead);
expectedOutput.replace("OPENDEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;");
expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"");
expectedOutput.replace("EMPTYBLOCK", "<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");
if (expectedOutput.endsWith(QLatin1Char('\n')))
expectedOutput.chop(1);
expectedOutput.append(htmlTail);
QString output = doc->toHtml();
QCOMPARE(output, expectedOutput);
QDomDocument document;
QVERIFY2(document.setContent(output), "Output was not valid XML");
}
void tst_QTextDocument::toHtml2()
{
QTextDocument doc;
doc.setHtml("<p>text <img src=\"\"> text</p>"); // 4 spaces before the second 'text'
QTextBlock block = doc.firstBlock();
QTextBlock::Iterator iter = block.begin();
QTextFragment f = iter.fragment();
QVERIFY(f.isValid());
QCOMPARE(f.position(), 0);
QCOMPARE(f.length(), 5);
//qDebug() << block.text().mid(f.position(), f.length());
iter++;
f = iter.fragment();
QVERIFY(f.isValid());
QCOMPARE(f.position(), 5);
QCOMPARE(f.length(), 1);
//qDebug() << block.text().mid(f.position(), f.length());
iter++;
f = iter.fragment();
//qDebug() << block.text().mid(f.position(), f.length());
QVERIFY(f.isValid());
QCOMPARE(f.position(), 6);
QCOMPARE(f.length(), 5); // 1 space should be preserved.
QCOMPARE(block.text().mid(f.position(), f.length()), QString(" text"));
doc.setHtml("<table><tr><td> foo</td></tr></table> text"); // 4 spaces before the second 'text'
block = doc.firstBlock().next();
//qDebug() << block.text();
QCOMPARE(block.text(), QString("foo"));
block = block.next();
//qDebug() << block.text();
QCOMPARE(block.text(), QString("text"));
}
void tst_QTextDocument::setFragmentMarkersInHtmlExport()
{
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Leadin");
const int startPos = cursor.position();
cursor.insertText("Test");
QTextCharFormat fmt;
fmt.setForeground(QColor("#00ff00"));
cursor.insertText("Blah", fmt);
const int endPos = cursor.position();
cursor.insertText("Leadout", QTextCharFormat());
cursor.setPosition(startPos);
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
QTextDocumentFragment fragment(cursor);
QString expected = htmlHead;
expected.replace(QRegExp("<body.*>"), QString("<body>"));
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;
QCOMPARE(fragment.toHtml(), expected);
}
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Leadin");
const int startPos = cursor.position();
cursor.insertText("Test");
const int endPos = cursor.position();
cursor.insertText("Leadout", QTextCharFormat());
cursor.setPosition(startPos);
cursor.setPosition(endPos, QTextCursor::KeepAnchor);
QTextDocumentFragment fragment(cursor);
QString expected = htmlHead;
expected.replace(QRegExp("<body.*>"), QString("<body>"));
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;
QCOMPARE(fragment.toHtml(), expected);
}
}
void tst_QTextDocument::toHtmlBodyBgColor()
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
fmt.setBackground(QColor("#0000ff"));
doc.rootFrame()->setFrameFormat(fmt);
QString expectedHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
"\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head>"
"<body style=\" font-family:'%1'; font-size:%2pt; font-weight:%3; font-style:%4;\""
" bgcolor=\"#0000ff\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
"</body></html>");
expectedHtml = expectedHtml.arg(defaultFont.family()).arg(defaultFont.pointSizeF()).arg(defaultFont.weight() * 8).arg((defaultFont.italic() ? "italic" : "normal"));
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::toHtmlBodyBgColorRgba()
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
fmt.setBackground(QColor(255, 0, 0, 51));
doc.rootFrame()->setFrameFormat(fmt);
QString expectedHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
"\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head>"
"<body style=\" font-family:'%1'; font-size:%2pt; font-weight:%3; font-style:%4;\""
" bgcolor=\"rgba(255,0,0,0.2)\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
"</body></html>");
expectedHtml = expectedHtml.arg(defaultFont.family()).arg(defaultFont.pointSizeF()).arg(defaultFont.weight() * 8).arg((defaultFont.italic() ? "italic" : "normal"));
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::toHtmlBodyBgColorTransparent()
{
CREATE_DOC_AND_CURSOR();
cursor.insertText("Blah");
QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
fmt.setBackground(QColor(255, 0, 0, 0));
doc.rootFrame()->setFrameFormat(fmt);
QString expectedHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
"\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head>"
"<body style=\" font-family:'%1'; font-size:%2pt; font-weight:%3; font-style:%4;\""
" bgcolor=\"transparent\">\n"
"<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
"</body></html>");
expectedHtml = expectedHtml.arg(defaultFont.family()).arg(defaultFont.pointSizeF()).arg(defaultFont.weight() * 8).arg((defaultFont.italic() ? "italic" : "normal"));
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::toHtmlRootFrameProperties()
{
CREATE_DOC_AND_CURSOR();
QTextFrameFormat fmt = doc.rootFrame()->frameFormat();
fmt.setTopMargin(10);
fmt.setLeftMargin(10);
fmt.setBorder(2);
doc.rootFrame()->setFrameFormat(fmt);
cursor.insertText("Blah");
QString expectedOutput("<table border=\"2\" style=\"-qt-table-type: root; margin-top:10px; "
"margin-bottom:4px; margin-left:10px; margin-right:4px;\">\n"
"<tr>\n<td style=\"border: none;\">\n"
"<p DEFAULTBLOCKSTYLE>Blah</p></td></tr></table>");
expectedOutput.prepend(htmlHead);
expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"");
expectedOutput.append(htmlTail);
QCOMPARE(doc.toHtml(), expectedOutput);
}
void tst_QTextDocument::toHtmlLineHeightProperties()
{
CREATE_DOC_AND_CURSOR();
QTextBlock block = doc.firstBlock();
QTextBlockFormat blockFormat = block.blockFormat();
blockFormat.setLineHeight(200, QTextBlockFormat::ProportionalHeight);
cursor.setBlockFormat(blockFormat);
cursor.insertText("Blah");
QString expectedOutput("<p DEFAULTBLOCKSTYLE line-height:200%;\">Blah</p>");
expectedOutput.prepend(htmlHead);
expectedOutput.replace("DEFAULTBLOCKSTYLE", "style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;");
expectedOutput.append(htmlTail);
QCOMPARE(doc.toHtml(), expectedOutput);
}
void tst_QTextDocument::capitalizationHtmlInExport()
{
doc->setPlainText("Test");
QRegExp re(".*span style=\"(.*)\">Test.*");
QVERIFY(re.exactMatch(doc->toHtml()) == false); // no span
QTextCursor cursor(doc);
cursor.setPosition(4, QTextCursor::KeepAnchor);
QTextCharFormat cf;
cf.setFontCapitalization(QFont::SmallCaps);
cursor.mergeCharFormat(cf);
const QString smallcaps = doc->toHtml();
QVERIFY(re.exactMatch(doc->toHtml()));
QCOMPARE(re.captureCount(), 1);
QCOMPARE(re.cap(1).trimmed(), QString("font-variant:small-caps;"));
cf.setFontCapitalization(QFont::AllUppercase);
cursor.mergeCharFormat(cf);
const QString uppercase = doc->toHtml();
QVERIFY(re.exactMatch(doc->toHtml()));
QCOMPARE(re.captureCount(), 1);
QCOMPARE(re.cap(1).trimmed(), QString("text-transform:uppercase;"));
cf.setFontCapitalization(QFont::AllLowercase);
cursor.mergeCharFormat(cf);
const QString lowercase = doc->toHtml();
QVERIFY(re.exactMatch(doc->toHtml()));
QCOMPARE(re.captureCount(), 1);
QCOMPARE(re.cap(1).trimmed(), QString("text-transform:lowercase;"));
doc->setHtml(smallcaps);
cursor.setPosition(1);
QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::SmallCaps);
doc->setHtml(uppercase);
QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::AllUppercase);
doc->setHtml(lowercase);
QCOMPARE(cursor.charFormat().fontCapitalization(), QFont::AllLowercase);
}
void tst_QTextDocument::wordspacingHtmlExport()
{
doc->setPlainText("Test");
QRegExp re(".*span style=\"(.*)\">Test.*");
QVERIFY(re.exactMatch(doc->toHtml()) == false); // no span
QTextCursor cursor(doc);
cursor.setPosition(4, QTextCursor::KeepAnchor);
QTextCharFormat cf;
cf.setFontWordSpacing(4);
cursor.mergeCharFormat(cf);
QVERIFY(re.exactMatch(doc->toHtml()));
QCOMPARE(re.captureCount(), 1);
QCOMPARE(re.cap(1).trimmed(), QString("word-spacing:4px;"));
cf.setFontWordSpacing(-8.5);
cursor.mergeCharFormat(cf);
QVERIFY(re.exactMatch(doc->toHtml()));
QCOMPARE(re.captureCount(), 1);
QCOMPARE(re.cap(1).trimmed(), QString("word-spacing:-8.5px;"));
}
class CursorPosSignalSpy : public QObject
{
Q_OBJECT
public:
CursorPosSignalSpy(QTextDocument *doc)
{
calls = 0;
connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)),
this, SLOT(cursorPositionChanged(QTextCursor)));
}
int calls;
private slots:
void cursorPositionChanged(const QTextCursor &)
{
++calls;
}
};
void tst_QTextDocument::cursorPositionChanged()
{
CursorPosSignalSpy spy(doc);
cursor.insertText("Test");
QCOMPARE(spy.calls, 1);
spy.calls = 0;
QTextCursor unrelatedCursor(doc);
unrelatedCursor.insertText("Blah");
QCOMPARE(spy.calls, 2);
spy.calls = 0;
cursor.insertText("Blah");
QCOMPARE(spy.calls, 1);
spy.calls = 0;
cursor.movePosition(QTextCursor::PreviousCharacter);
QCOMPARE(spy.calls, 0);
}
void tst_QTextDocument::cursorPositionChangedOnSetText()
{
CursorPosSignalSpy spy(doc);
// doc has one QTextCursor stored in the
// cursor member variable, thus the signal
// gets emitted once.
doc->setPlainText("Foo\nBar\nBaz\nBlub\nBlah");
QCOMPARE(spy.calls, 1);
spy.calls = 0;
doc->setHtml("<p>Foo<p>Bar<p>Baz<p>Blah");
QCOMPARE(spy.calls, 1);
}
void tst_QTextDocument::textFrameIterator()
{
cursor.insertTable(1, 1);
int blockCount = 0;
int frameCount = 0;
for (QTextFrame::Iterator frameIt = doc->rootFrame()->begin();
!frameIt.atEnd(); ++frameIt) {
if (frameIt.currentFrame())
++frameCount;
else if (frameIt.currentBlock().isValid())
++blockCount;
}
QEXPECT_FAIL("", "This is currently worked around in the html export but needs fixing!", Continue);
QCOMPARE(blockCount, 0);
QCOMPARE(frameCount, 1);
}
void tst_QTextDocument::codecForHtml()
{
const QByteArray header("<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html;charset=utf-16\">");
QTextCodec *c = Qt::codecForHtml(header);
QVERIFY(c);
QCOMPARE(c->name(), QByteArray("UTF-16"));
}
class TestSyntaxHighlighter : public QObject
{
Q_OBJECT
public:
inline TestSyntaxHighlighter(QTextDocument *doc) : QObject(doc), ok(false) {}
bool ok;
private slots:
inline void markBlockDirty(int from, int charsRemoved, int charsAdded)
{
Q_UNUSED(charsRemoved);
Q_UNUSED(charsAdded);
QTextDocument *doc = static_cast<QTextDocument *>(parent());
QTextBlock block = doc->findBlock(from);
QTestDocumentLayout *lout = qobject_cast<QTestDocumentLayout *>(doc->documentLayout());
lout->called = false;
doc->markContentsDirty(block.position(), block.length());
ok = (lout->called == false);
}
inline void modifyBlockAgain(int from, int charsRemoved, int charsAdded)
{
Q_UNUSED(charsRemoved);
Q_UNUSED(charsAdded);
QTextDocument *doc = static_cast<QTextDocument *>(parent());
QTextBlock block = doc->findBlock(from);
QTextCursor cursor(block);
QTestDocumentLayout *lout = qobject_cast<QTestDocumentLayout *>(doc->documentLayout());
lout->called = false;
cursor.insertText("Foo");
ok = (lout->called == true);
}
};
void tst_QTextDocument::markContentsDirty()
{
QTestDocumentLayout *lout = new QTestDocumentLayout(doc);
doc->setDocumentLayout(lout);
TestSyntaxHighlighter *highlighter = new TestSyntaxHighlighter(doc);
connect(doc, SIGNAL(contentsChange(int,int,int)),
highlighter, SLOT(markBlockDirty(int,int,int)));
highlighter->ok = false;
cursor.insertText("Some dummy text blah blah");
QVERIFY(highlighter->ok);
disconnect(doc, SIGNAL(contentsChange(int,int,int)),
highlighter, SLOT(markBlockDirty(int,int,int)));
connect(doc, SIGNAL(contentsChange(int,int,int)),
highlighter, SLOT(modifyBlockAgain(int,int,int)));
highlighter->ok = false;
cursor.insertText("FooBar");
QVERIFY(highlighter->ok);
lout->called = false;
doc->markContentsDirty(1, 4);
QVERIFY(lout->called);
}
void tst_QTextDocument::clonePreservesMetaInformation()
{
const QString title("Foobar");
const QString url("about:blank");
doc->setHtml("<html><head><title>" + title + "</title></head><body>Hrm</body></html>");
doc->setMetaInformation(QTextDocument::DocumentUrl, url);
QCOMPARE(doc->metaInformation(QTextDocument::DocumentTitle), title);
QCOMPARE(doc->metaInformation(QTextDocument::DocumentUrl), url);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->metaInformation(QTextDocument::DocumentTitle), title);
QCOMPARE(clone->metaInformation(QTextDocument::DocumentUrl), url);
delete clone;
}
void tst_QTextDocument::clonePreservesPageSize()
{
QSizeF sz(100., 100.);
doc->setPageSize(sz);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->pageSize(), sz);
delete clone;
}
void tst_QTextDocument::clonePreservesPageBreakPolicies()
{
QTextTableFormat tableFmt;
tableFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysAfter);
QTextBlockFormat blockFmt;
blockFmt.setPageBreakPolicy(QTextFormat::PageBreak_AlwaysBefore);
QTextCursor cursor(doc);
cursor.setBlockFormat(blockFmt);
cursor.insertText("foo");
cursor.insertTable(2, 2, tableFmt);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->begin().blockFormat().pageBreakPolicy(), QTextFormat::PageBreak_AlwaysBefore);
QVERIFY(!clone->rootFrame()->childFrames().isEmpty());
QCOMPARE(clone->rootFrame()->childFrames().first()->frameFormat().pageBreakPolicy(), QTextFormat::PageBreak_AlwaysAfter);
delete clone;
}
void tst_QTextDocument::clonePreservesDefaultFont()
{
QFont f = doc->defaultFont();
QVERIFY(f.pointSize() != 100);
f.setPointSize(100);
doc->setDefaultFont(f);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->defaultFont(), f);
delete clone;
}
void tst_QTextDocument::clonePreservesResources()
{
QUrl testUrl(":/foobar");
QVariant testResource("hello world");
doc->addResource(QTextDocument::ImageResource, testUrl, testResource);
QTextDocument *clone = doc->clone();
QVERIFY(clone->resource(QTextDocument::ImageResource, testUrl) == testResource);
delete clone;
}
void tst_QTextDocument::clonePreservesUserStates()
{
QTextCursor cursor(doc);
cursor.insertText("bla bla bla");
cursor.block().setUserState(1);
cursor.insertBlock();
cursor.insertText("foo bar");
cursor.block().setUserState(2);
cursor.insertBlock();
cursor.insertText("no user state");
QTextDocument *clone = doc->clone();
QTextBlock b1 = doc->begin(), b2 = clone->begin();
while (b1 != doc->end()) {
b1 = b1.next();
b2 = b2.next();
QCOMPARE(b1.userState(), b2.userState());
}
QVERIFY(b2 == clone->end());
delete clone;
}
void tst_QTextDocument::clonePreservesRootFrameFormat()
{
doc->setPlainText("Hello");
QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
fmt.setMargin(200);
doc->rootFrame()->setFrameFormat(fmt);
QCOMPARE(doc->rootFrame()->frameFormat().margin(), qreal(200));
QTextDocument *copy = doc->clone();
QCOMPARE(copy->rootFrame()->frameFormat().margin(), qreal(200));
delete copy;
}
void tst_QTextDocument::clonePreservesIndentWidth()
{
doc->setIndentWidth(42);
QTextDocument *clone = doc->clone();
QCOMPARE(clone->indentWidth(), qreal(42));
delete clone;
}
void tst_QTextDocument::blockCount()
{
QCOMPARE(doc->blockCount(), 1);
cursor.insertBlock();
QCOMPARE(doc->blockCount(), 2);
cursor.insertBlock();
QCOMPARE(doc->blockCount(), 3);
cursor.insertText("blah blah");
QCOMPARE(doc->blockCount(), 3);
doc->undo();
doc->undo();
QCOMPARE(doc->blockCount(), 2);
doc->undo();
QCOMPARE(doc->blockCount(), 1);
}
void tst_QTextDocument::resolvedFontInEmptyFormat()
{
QFont font;
font.setPointSize(42);
doc->setDefaultFont(font);
QTextCharFormat fmt = doc->begin().charFormat();
QVERIFY(fmt.properties().isEmpty());
QVERIFY(fmt.font() == font);
}
void tst_QTextDocument::defaultRootFrameMargin()
{
QCOMPARE(doc->rootFrame()->frameFormat().margin(), 4.0);
}
class TestDocument : public QTextDocument
{
public:
inline TestDocument(const QUrl &testUrl, const QString &testString)
: url(testUrl), string(testString), resourceLoaded(false) {}
bool hasResourceCached();
protected:
virtual QVariant loadResource(int type, const QUrl &name);
private:
QUrl url;
QString string;
bool resourceLoaded;
};
bool TestDocument::hasResourceCached()
{
resourceLoaded = false;
resource(QTextDocument::ImageResource, url);
return !resourceLoaded;
}
QVariant TestDocument::loadResource(int type, const QUrl &name)
{
if (type == QTextDocument::ImageResource
&& name == url) {
resourceLoaded = true;
return string;
}
return QTextDocument::loadResource(type, name);
}
void tst_QTextDocument::clearResources()
{
// regular resource for QTextDocument
QUrl testUrl(":/foobar");
QVariant testResource("hello world");
// implicitly cached resource, initially loaded through TestDocument::loadResource()
QUrl cacheUrl(":/blub");
QString cacheResource("mah");
TestDocument doc(cacheUrl, cacheResource);
doc.addResource(QTextDocument::ImageResource, testUrl, testResource);
QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
doc.setPlainText("Hah");
QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
doc.setHtml("<b>Mooo</b><img src=\":/blub\"/>");
QVERIFY(doc.resource(QTextDocument::ImageResource, testUrl) == testResource);
QVERIFY(doc.resource(QTextDocument::ImageResource, cacheUrl) == cacheResource);
doc.clear();
QVERIFY(!doc.resource(QTextDocument::ImageResource, testUrl).isValid());
QVERIFY(!doc.hasResourceCached());
doc.clear();
doc.setHtml("<b>Mooo</b><img src=\":/blub\"/>");
QVERIFY(doc.resource(QTextDocument::ImageResource, cacheUrl) == cacheResource);
doc.setPlainText("Foob");
QVERIFY(!doc.hasResourceCached());
}
void tst_QTextDocument::setPlainText()
{
doc->setPlainText("Hello World");
QString s("");
doc->setPlainText(s);
QCOMPARE(doc->toPlainText(), s);
}
void tst_QTextDocument::toPlainText()
{
doc->setHtml("Hello&nbsp;World");
QCOMPARE(doc->toPlainText(), QLatin1String("Hello World"));
}
void tst_QTextDocument::deleteTextObjectsOnClear()
{
QPointer<QTextTable> table = cursor.insertTable(2, 2);
QVERIFY(!table.isNull());
doc->clear();
QVERIFY(table.isNull());
}
void tst_QTextDocument::defaultStyleSheet()
{
const QString sheet("p { background-color: green; }");
QVERIFY(doc->defaultStyleSheet().isEmpty());
doc->setDefaultStyleSheet(sheet);
QCOMPARE(doc->defaultStyleSheet(), sheet);
cursor.insertHtml("<p>test");
QTextBlockFormat fmt = doc->begin().blockFormat();
QVERIFY(fmt.background().color() == QColor("green"));
doc->clear();
cursor.insertHtml("<p>test");
fmt = doc->begin().blockFormat();
QVERIFY(fmt.background().color() == QColor("green"));
QTextDocument *clone = doc->clone();
QCOMPARE(clone->defaultStyleSheet(), sheet);
cursor = QTextCursor(clone);
cursor.insertHtml("<p>test");
fmt = clone->begin().blockFormat();
QVERIFY(fmt.background().color() == QColor("green"));
delete clone;
cursor = QTextCursor(doc);
cursor.insertHtml("<p>test");
fmt = doc->begin().blockFormat();
QVERIFY(fmt.background().color() == QColor("green"));
doc->clear();
cursor.insertHtml("<style>p { background-color: red; }</style><p>test");
fmt = doc->begin().blockFormat();
QVERIFY(fmt.background().color() == QColor("red"));
doc->clear();
doc->setDefaultStyleSheet("invalid style sheet....");
cursor.insertHtml("<p>test");
fmt = doc->begin().blockFormat();
QVERIFY(fmt.background().color() != QColor("green"));
}
void tst_QTextDocument::maximumBlockCount()
{
QCOMPARE(doc->maximumBlockCount(), 0);
QVERIFY(doc->isUndoRedoEnabled());
cursor.insertBlock();
cursor.insertText("Blah");
cursor.insertBlock();
cursor.insertText("Foo");
QCOMPARE(doc->blockCount(), 3);
QCOMPARE(doc->toPlainText(), QString("\nBlah\nFoo"));
doc->setMaximumBlockCount(1);
QVERIFY(!doc->isUndoRedoEnabled());
QCOMPARE(doc->blockCount(), 1);
QCOMPARE(doc->toPlainText(), QString("Foo"));
cursor.insertBlock();
cursor.insertText("Hello");
doc->setMaximumBlockCount(1);
QCOMPARE(doc->blockCount(), 1);
QCOMPARE(doc->toPlainText(), QString("Hello"));
doc->setMaximumBlockCount(100);
for (int i = 0; i < 1000; ++i) {
cursor.insertBlock();
cursor.insertText("Blah)");
QVERIFY(doc->blockCount() <= 100);
}
cursor.movePosition(QTextCursor::End);
QCOMPARE(cursor.blockNumber(), 99);
QTextCharFormat fmt;
fmt.setFontItalic(true);
cursor.setBlockCharFormat(fmt);
cursor.movePosition(QTextCursor::Start);
QVERIFY(!cursor.blockCharFormat().fontItalic());
doc->setMaximumBlockCount(1);
QVERIFY(cursor.blockCharFormat().fontItalic());
cursor.insertTable(2, 2);
QCOMPARE(doc->blockCount(), 6);
cursor.insertBlock();
QCOMPARE(doc->blockCount(), 1);
}
void tst_QTextDocument::adjustSize()
{
// avoid ugly tooltips like in task 125583
QString text("Test Text");
doc->setPlainText(text);
doc->rootFrame()->setFrameFormat(QTextFrameFormat());
doc->adjustSize();
QCOMPARE(doc->size().width(), doc->idealWidth());
}
void tst_QTextDocument::initialUserData()
{
doc->setPlainText("Hello");
QTextBlock block = doc->begin();
block.setUserData(new QTextBlockUserData);
QVERIFY(block.userData());
doc->documentLayout();
QVERIFY(block.userData());
doc->setDocumentLayout(new QTestDocumentLayout(doc));
QVERIFY(!block.userData());
}
void tst_QTextDocument::html_defaultFont()
{
QFont f;
f.setItalic(true);
f.setWeight(QFont::Bold);
doc->setDefaultFont(f);
doc->setPlainText("Test");
QString bodyPart = QString::fromLatin1("<body style=\" font-family:'%1'; font-size:%2pt; font-weight:%3; font-style:italic;\">")
.arg(f.family()).arg(f.pointSizeF()).arg(f.weight() * 8);
QString html = doc->toHtml();
if (!html.contains(bodyPart)) {
qDebug() << "html:" << html;
qDebug() << "expected body:" << bodyPart;
QVERIFY(html.contains(bodyPart));
}
if (html.contains("span"))
qDebug() << "html:" << html;
QVERIFY(!html.contains("<span style"));
}
void tst_QTextDocument::blockCountChanged()
{
QSignalSpy spy(doc, SIGNAL(blockCountChanged(int)));
doc->setPlainText("Foo");
QCOMPARE(doc->blockCount(), 1);
QCOMPARE(spy.count(), 0);
spy.clear();
doc->setPlainText("Foo\nBar");
QCOMPARE(doc->blockCount(), 2);
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.at(0).value(0).toInt(), 2);
spy.clear();
cursor.movePosition(QTextCursor::End);
cursor.insertText("Blahblah");
QCOMPARE(spy.count(), 0);
cursor.insertBlock();
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.at(0).value(0).toInt(), 3);
spy.clear();
doc->undo();
QCOMPARE(spy.count(), 1);
QCOMPARE(spy.at(0).value(0).toInt(), 2);
}
void tst_QTextDocument::nonZeroDocumentLengthOnClear()
{
QTestDocumentLayout *lout = new QTestDocumentLayout(doc);
doc->setDocumentLayout(lout);
doc->clear();
QVERIFY(lout->called);
QVERIFY(!lout->lastDocumentLengths.contains(0));
}
void tst_QTextDocument::setTextPreservesUndoRedoEnabled()
{
QVERIFY(doc->isUndoRedoEnabled());
doc->setPlainText("Test");
QVERIFY(doc->isUndoRedoEnabled());
doc->setUndoRedoEnabled(false);
QVERIFY(!doc->isUndoRedoEnabled());
doc->setPlainText("Test2");
QVERIFY(!doc->isUndoRedoEnabled());
doc->setHtml("<p>hello");
QVERIFY(!doc->isUndoRedoEnabled());
}
void tst_QTextDocument::firstLast()
{
QCOMPARE(doc->blockCount(), 1);
QVERIFY(doc->firstBlock() == doc->lastBlock());
doc->setPlainText("Hello\nTest\nWorld");
QCOMPARE(doc->blockCount(), 3);
QVERIFY(doc->firstBlock() != doc->lastBlock());
QCOMPARE(doc->firstBlock().text(), QString("Hello"));
QCOMPARE(doc->lastBlock().text(), QString("World"));
// manual forward loop
QTextBlock block = doc->firstBlock();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("Hello"));
block = block.next();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("Test"));
block = block.next();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("World"));
block = block.next();
QVERIFY(!block.isValid());
// manual backward loop
block = doc->lastBlock();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("World"));
block = block.previous();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("Test"));
block = block.previous();
QVERIFY(block.isValid());
QCOMPARE(block.text(), QString("Hello"));
block = block.previous();
QVERIFY(!block.isValid());
}
const QString backgroundImage_html("<body><table><tr><td background=\"foo.png\">Blah</td></tr></table></body>");
void tst_QTextDocument::backgroundImage_checkExpectedHtml(const QTextDocument &doc)
{
QString expectedHtml("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" "
"\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
"p, li { white-space: pre-wrap; }\n"
"</style></head>"
"<body style=\" font-family:'%1'; font-size:%2pt; font-weight:%3; font-style:%4;\">\n"
"<table border=\"0\" style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px;\" cellspacing=\"2\" cellpadding=\"0\">"
"\n<tr>\n<td background=\"foo.png\">"
"\n<p style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\">Blah</p>"
"</td></tr></table></body></html>");
expectedHtml = expectedHtml.arg(defaultFont.family()).arg(defaultFont.pointSizeF()).arg(defaultFont.weight() * 8).arg((defaultFont.italic() ? "italic" : "normal"));
QCOMPARE(doc.toHtml(), expectedHtml);
}
void tst_QTextDocument::backgroundImage_toHtml()
{
CREATE_DOC_AND_CURSOR();
doc.setHtml(backgroundImage_html);
backgroundImage_checkExpectedHtml(doc);
}
void tst_QTextDocument::backgroundImage_toHtml2()
{
CREATE_DOC_AND_CURSOR();
cursor.insertHtml(backgroundImage_html);
backgroundImage_checkExpectedHtml(doc);
}
void tst_QTextDocument::backgroundImage_clone()
{
CREATE_DOC_AND_CURSOR();
doc.setHtml(backgroundImage_html);
QTextDocument *clone = doc.clone();
backgroundImage_checkExpectedHtml(*clone);
delete clone;
}
void tst_QTextDocument::backgroundImage_copy()
{
CREATE_DOC_AND_CURSOR();
doc.setHtml(backgroundImage_html);
QTextDocumentFragment fragment(&doc);
{
CREATE_DOC_AND_CURSOR();
cursor.insertFragment(fragment);
backgroundImage_checkExpectedHtml(doc);
}
}
void tst_QTextDocument::documentCleanup()
{
QTextDocument doc;
QTextCursor cursor(&doc);
cursor.insertText("d\nfoo\nbar\n");
doc.documentLayout(); // forces relayout
// remove char 1
cursor.setPosition(0);
QSizeF size = doc.documentLayout()->documentSize();
cursor.deleteChar();
// the size should be unchanged.
QCOMPARE(doc.documentLayout()->documentSize(), size);
}
void tst_QTextDocument::characterAt()
{
QTextDocument doc;
QTextCursor cursor(&doc);
QString text("12345\n67890");
cursor.insertText(text);
int length = doc.characterCount();
QCOMPARE(length, text.length() + 1);
QCOMPARE(doc.characterAt(length-1), QChar(QChar::ParagraphSeparator));
QCOMPARE(doc.characterAt(-1), QChar());
QCOMPARE(doc.characterAt(length), QChar());
QCOMPARE(doc.characterAt(length + 1), QChar());
for (int i = 0; i < text.length(); ++i) {
QChar c = text.at(i);
if (c == QLatin1Char('\n'))
c = QChar(QChar::ParagraphSeparator);
QCOMPARE(doc.characterAt(i), c);
}
}
void tst_QTextDocument::revisions()
{
QTextDocument doc;
QTextCursor cursor(&doc);
QString text("Hello World");
QCOMPARE(doc.firstBlock().revision(), 0);
cursor.insertText(text);
QCOMPARE(doc.firstBlock().revision(), 1);
cursor.setPosition(6);
cursor.insertBlock();
QCOMPARE(cursor.block().previous().revision(), 2);
QCOMPARE(cursor.block().revision(), 2);
cursor.insertText("candle");
QCOMPARE(cursor.block().revision(), 3);
cursor.movePosition(QTextCursor::EndOfBlock);
cursor.insertBlock(); // we are at the block end
QCOMPARE(cursor.block().previous().revision(), 3);
QCOMPARE(cursor.block().revision(), 4);
cursor.insertText("lightbulb");
QCOMPARE(cursor.block().revision(), 5);
cursor.movePosition(QTextCursor::StartOfBlock);
cursor.insertBlock(); // we are the block start
QCOMPARE(cursor.block().previous().revision(), 6);
QCOMPARE(cursor.block().revision(), 5);
}
void tst_QTextDocument::revisionWithUndoCompressionAndUndo()
{
QTextDocument doc;
QTextCursor cursor(&doc);
cursor.insertText("This is the beginning of it all.");
QCOMPARE(doc.firstBlock().revision(), 1);
QCOMPARE(doc.revision(), 1);
cursor.insertBlock();
QCOMPARE(doc.revision(), 2);
cursor.insertText("this");
QCOMPARE(doc.revision(), 3);
cursor.insertText("is");
QCOMPARE(doc.revision(), 4);
cursor.insertText("compressed");
QCOMPARE(doc.revision(), 5);
doc.undo();
QCOMPARE(doc.revision(), 6);
QCOMPARE(doc.toPlainText(), QString("This is the beginning of it all.\n")) ;
cursor.setPosition(0);
QCOMPARE(doc.firstBlock().revision(), 1);
cursor.insertText("Very beginnig");
QCOMPARE(doc.firstBlock().revision(), 7);
doc.undo();
QCOMPARE(doc.revision(), 8);
QCOMPARE(doc.firstBlock().revision(), 1);
cursor.beginEditBlock();
cursor.insertText("Hello");
cursor.insertBlock();
cursor.insertText("world");
cursor.endEditBlock();
QCOMPARE(doc.revision(), 9);
doc.undo();
QCOMPARE(doc.revision(), 10);
}
void tst_QTextDocument::testUndoCommandAdded()
{
QVERIFY(doc);
QSignalSpy spy(doc, SIGNAL(undoCommandAdded()));
QVERIFY(spy.isValid());
QVERIFY(spy.isEmpty());
cursor.insertText("a");
QCOMPARE(spy.count(), 1);
cursor.insertText("b"); // should be merged
QCOMPARE(spy.count(), 1);
cursor.insertText("c"); // should be merged
QCOMPARE(spy.count(), 1);
QCOMPARE(doc->toPlainText(), QString("abc"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
doc->clear();
spy.clear();
cursor.insertText("aaa");
QCOMPARE(spy.count(), 1);
spy.clear();
cursor.insertText("aaaa\nbcd");
QCOMPARE(spy.count(), 1);
spy.clear();
cursor.beginEditBlock();
cursor.insertText("aa");
cursor.insertText("bbb\n");
cursor.setCharFormat(QTextCharFormat());
cursor.insertText("\nccc");
QVERIFY(spy.isEmpty());
cursor.endEditBlock();
QCOMPARE(spy.count(), 1);
spy.clear();
cursor.insertBlock();
QCOMPARE(spy.count(), 1);
spy.clear();
cursor.setPosition(5);
QVERIFY(spy.isEmpty());
cursor.setCharFormat(QTextCharFormat());
QVERIFY(spy.isEmpty());
cursor.setPosition(10, QTextCursor::KeepAnchor);
QVERIFY(spy.isEmpty());
QTextCharFormat cf;
cf.setFontItalic(true);
cursor.mergeCharFormat(cf);
QCOMPARE(spy.count(), 1);
spy.clear();
doc->undo();
QCOMPARE(spy.count(), 0);
doc->undo();
QCOMPARE(spy.count(), 0);
spy.clear();
doc->redo();
QCOMPARE(spy.count(), 0);
doc->redo();
QCOMPARE(spy.count(), 0);
}
void tst_QTextDocument::testUndoBlocks()
{
QVERIFY(doc);
cursor.insertText("Hello World");
cursor.insertText("period");
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
cursor.insertText("Hello World");
cursor.insertText("One\nTwo\nThree");
QCOMPARE(doc->toPlainText(), QString("Hello WorldOne\nTwo\nThree"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString("Hello World"));
cursor.insertText("One\nTwo\nThree");
cursor.insertText("Trailing text");
doc->undo();
QCOMPARE(doc->toPlainText(), QString("Hello WorldOne\nTwo\nThree"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString("Hello World"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
cursor.insertText("quod");
cursor.beginEditBlock();
cursor.insertText(" erat");
cursor.endEditBlock();
cursor.insertText(" demonstrandum");
QCOMPARE(doc->toPlainText(), QString("quod erat demonstrandum"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString("quod erat"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString("quod"));
doc->undo();
QCOMPARE(doc->toPlainText(), QString(""));
}
class Receiver : public QObject
{
Q_OBJECT
public:
QString first;
public slots:
void cursorPositionChanged() {
if (first.isEmpty())
first = QLatin1String("cursorPositionChanged");
}
void contentsChange() {
if (first.isEmpty())
first = QLatin1String("contentsChanged");
}
};
void tst_QTextDocument::receiveCursorPositionChangedAfterContentsChange()
{
QVERIFY(doc);
doc->setDocumentLayout(new MyAbstractTextDocumentLayout(doc));
Receiver rec;
connect(doc, SIGNAL(cursorPositionChanged(QTextCursor)),
&rec, SLOT(cursorPositionChanged()));
connect(doc, SIGNAL(contentsChange(int,int,int)),
&rec, SLOT(contentsChange()));
cursor.insertText("Hello World");
QCOMPARE(rec.first, QString("contentsChanged"));
}
void tst_QTextDocument::QTBUG25778_pixelSizeFromHtml()
{
QTextDocument document1;
QTextDocument document2;
document1.setHtml("<span style=\"font-size: 24px\">Foobar</span>");
document2.setHtml(document1.toHtml());
QTextCursor cursor(&document2);
QCOMPARE(cursor.charFormat().font().pixelSize(), 24);
}
void tst_QTextDocument::copiedFontSize()
{
QTextDocument documentInput;
QTextDocument documentOutput;
QFont fontInput;
fontInput.setPixelSize(24);
QTextCursor cursorInput(&documentInput);
QTextCharFormat formatInput = cursorInput.charFormat();
formatInput.setFont(fontInput);
cursorInput.insertText("Should be the same font", formatInput);
cursorInput.select(QTextCursor::Document);
QTextDocumentFragment fragmentInput(cursorInput);
QString html = fragmentInput.toHtml();
QTextCursor cursorOutput(&documentOutput);
QTextDocumentFragment fragmentOutput = QTextDocumentFragment::fromHtml(html);
cursorOutput.insertFragment(fragmentOutput);
QCOMPARE(cursorOutput.charFormat().font().pixelSize(), 24);
}
void tst_QTextDocument::htmlExportImportBlockCount()
{
QTextDocument document;
{
QTextCursor cursor(&document);
cursor.insertText("Foo");
cursor.insertBlock();
cursor.insertBlock();
cursor.insertBlock();
cursor.insertBlock();
cursor.insertText("Bar");
}
QCOMPARE(document.blockCount(), 5);
QString html = document.toHtml();
document.clear();
document.setHtml(html);
QCOMPARE(document.blockCount(), 5);
}
void tst_QTextDocument::QTBUG27354_spaceAndSoftSpace()
{
QTextDocument document;
{
QTextCursor cursor(&document);
QTextBlockFormat blockFormat;
blockFormat.setAlignment(Qt::AlignJustify);
cursor.mergeBlockFormat(blockFormat);
cursor.insertText("ac");
cursor.insertBlock();
cursor.insertText(" ");
cursor.insertText(QChar(0x2028));
}
// Trigger justification of text
QImage image(1000, 1000, QImage::Format_ARGB32);
image.fill(0);
{
QPainter p(&image);
document.drawContents(&p, image.rect());
}
}
class BaseDocument : public QTextDocument
{
public:
QUrl loadedResource() const { return resourceUrl; }
QVariant loadResource(int type, const QUrl &name)
{
resourceUrl = name;
return QTextDocument::loadResource(type, name);
}
private:
QUrl resourceUrl;
};
void tst_QTextDocument::baseUrl_data()
{
QTest::addColumn<QUrl>("base");
QTest::addColumn<QUrl>("resource");
QTest::addColumn<QUrl>("loaded");
QTest::newRow("1") << QUrl() << QUrl("images/logo.png") << QUrl("images/logo.png");
QTest::newRow("2") << QUrl("file:///path/to/content") << QUrl("images/logo.png") << QUrl("file:///path/to/images/logo.png");
QTest::newRow("3") << QUrl("file:///path/to/content/") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
QTest::newRow("4") << QUrl("file:///path/to/content/images") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
QTest::newRow("5") << QUrl("file:///path/to/content/images/") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/images/logo.png");
QTest::newRow("6") << QUrl("file:///path/to/content/images") << QUrl("../images/logo.png") << QUrl("file:///path/to/images/logo.png");
QTest::newRow("7") << QUrl("file:///path/to/content/images/") << QUrl("../images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
QTest::newRow("8") << QUrl("file:///path/to/content/index.html") << QUrl("images/logo.png") << QUrl("file:///path/to/content/images/logo.png");
}
void tst_QTextDocument::baseUrl()
{
QFETCH(QUrl, base);
QFETCH(QUrl, resource);
QFETCH(QUrl, loaded);
BaseDocument document;
QVERIFY(!document.baseUrl().isValid());
document.setBaseUrl(base);
QCOMPARE(document.baseUrl(), base);
document.setHtml(QString("<img src='%1'/>").arg(resource.toString()));
document.resource(QTextDocument::ImageResource, resource);
QCOMPARE(document.loadedResource(), loaded);
}
void tst_QTextDocument::QTBUG28998_linkColor()
{
QPalette pal;
pal.setColor(QPalette::Link, QColor("tomato"));
QGuiApplication::setPalette(pal);
QTextDocument doc;
doc.setHtml("<a href=\"http://www.qt-project.org\">Qt</a>");
QCOMPARE(doc.blockCount(), 1);
QTextBlock block = doc.firstBlock();
QVERIFY(block.isValid());
QTextFragment fragment = block.begin().fragment();
QVERIFY(fragment.isValid());
QTextCharFormat format = fragment.charFormat();
QVERIFY(format.isValid());
QVERIFY(format.isAnchor());
QCOMPARE(format.anchorHref(), QStringLiteral("http://www.qt-project.org"));
QCOMPARE(format.foreground(), pal.link());
}
class ContentsChangeHandler : public QObject
{
Q_OBJECT
public:
ContentsChangeHandler(QTextDocument *doc)
: verticalMovementX(-1)
, doc(doc)
{
connect(doc, SIGNAL(contentsChange(int,int,int)),
this, SLOT(saveModifiedText(int, int, int)));
}
private slots:
void saveModifiedText(int from, int /*charsRemoved*/, int charsAdded)
{
QTextCursor tmp(doc);
tmp.setPosition(from);
tmp.setPosition(from + charsAdded, QTextCursor::KeepAnchor);
text = tmp.selectedText();
verticalMovementX = tmp.verticalMovementX();
}
public:
QString text;
int verticalMovementX;
private:
QTextDocument *doc;
};
void tst_QTextDocument::textCursorUsageWithinContentsChange()
{
// force creation of layout
doc->documentLayout();
QTextCursor cursor(doc);
cursor.insertText("initial text");
ContentsChangeHandler handler(doc);
cursor.insertText("new text");
QCOMPARE(handler.text, QString("new text"));
QCOMPARE(handler.verticalMovementX, -1);
}
QTEST_MAIN(tst_QTextDocument)
#include "tst_qtextdocument.moc"