diff options
Diffstat (limited to 'tests/auto/gui/text')
92 files changed, 22323 insertions, 0 deletions
diff --git a/tests/auto/gui/text/qabstracttextdocumentlayout/.gitignore b/tests/auto/gui/text/qabstracttextdocumentlayout/.gitignore new file mode 100644 index 0000000000..d747466790 --- /dev/null +++ b/tests/auto/gui/text/qabstracttextdocumentlayout/.gitignore @@ -0,0 +1 @@ +tst_qabstracttextdocumentlayout diff --git a/tests/auto/gui/text/qabstracttextdocumentlayout/qabstracttextdocumentlayout.pro b/tests/auto/gui/text/qabstracttextdocumentlayout/qabstracttextdocumentlayout.pro new file mode 100644 index 0000000000..22b013e37e --- /dev/null +++ b/tests/auto/gui/text/qabstracttextdocumentlayout/qabstracttextdocumentlayout.pro @@ -0,0 +1,9 @@ +############################################################ +# Project file for autotest for file qabstracttextdocumentlayout.h +############################################################ + +load(qttest_p4) +QT += widgets +SOURCES += tst_qabstracttextdocumentlayout.cpp + + diff --git a/tests/auto/gui/text/qabstracttextdocumentlayout/tst_qabstracttextdocumentlayout.cpp b/tests/auto/gui/text/qabstracttextdocumentlayout/tst_qabstracttextdocumentlayout.cpp new file mode 100644 index 0000000000..90f212e832 --- /dev/null +++ b/tests/auto/gui/text/qabstracttextdocumentlayout/tst_qabstracttextdocumentlayout.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <qcoreapplication.h> +#include <qdebug.h> +#include <qabstracttextdocumentlayout.h> +#include <qimage.h> +#include <qtextobject.h> + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QAbstractTextDocumentLayout : public QObject +{ +Q_OBJECT + +public: + tst_QAbstractTextDocumentLayout(); + virtual ~tst_QAbstractTextDocumentLayout(); + +private slots: + void getSetCheck(); + void maximumBlockCount(); +}; + +tst_QAbstractTextDocumentLayout::tst_QAbstractTextDocumentLayout() +{ +} + +tst_QAbstractTextDocumentLayout::~tst_QAbstractTextDocumentLayout() +{ +} + +class MyAbstractTextDocumentLayout : public QAbstractTextDocumentLayout +{ + Q_OBJECT +public: + MyAbstractTextDocumentLayout(QTextDocument *doc) + : QAbstractTextDocumentLayout(doc) + , gotFullLayout(false) + , blockCount(0) + , changeEvents(0) + { + } + + 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 from, int /* oldLength */, int length) { + ++changeEvents; + + QTextBlock last = document()->lastBlock(); + int lastPos = last.position() + last.length() - 1; + if (from == 0 && length == lastPos) + gotFullLayout = true; + } + + bool gotFullLayout; + int blockCount; + int changeEvents; + +public slots: + void blockCountChanged(int bc) { blockCount = bc; } +}; + +// Testing get/set functions +void tst_QAbstractTextDocumentLayout::getSetCheck() +{ + QTextDocument doc; + MyAbstractTextDocumentLayout obj1(&doc); + // QPaintDevice * QAbstractTextDocumentLayout::paintDevice() + // void QAbstractTextDocumentLayout::setPaintDevice(QPaintDevice *) + QImage *var1 = new QImage(QSize(10,10), QImage::Format_ARGB32_Premultiplied); + obj1.setPaintDevice(var1); + QCOMPARE(static_cast<QPaintDevice *>(var1), obj1.paintDevice()); + obj1.setPaintDevice((QPaintDevice *)0); + QCOMPARE(static_cast<QPaintDevice *>(0), obj1.paintDevice()); + delete var1; +} + +void tst_QAbstractTextDocumentLayout::maximumBlockCount() +{ + QTextDocument doc; + doc.setMaximumBlockCount(10); + + MyAbstractTextDocumentLayout layout(&doc); + doc.setDocumentLayout(&layout); + QObject::connect(&doc, SIGNAL(blockCountChanged(int)), &layout, SLOT(blockCountChanged(int))); + + QTextCursor cursor(&doc); + for (int i = 0; i < 10; ++i) { + cursor.insertBlock(); + cursor.insertText("bla"); + } + + QCOMPARE(layout.blockCount, 10); + + layout.gotFullLayout = false; + layout.changeEvents = 0; + cursor.insertBlock(); + QCOMPARE(layout.changeEvents, 2); + cursor.insertText("foo"); + QCOMPARE(layout.changeEvents, 3); + cursor.insertBlock(); + QCOMPARE(layout.changeEvents, 5); + cursor.insertText("foo"); + QCOMPARE(layout.changeEvents, 6); + + QVERIFY(!layout.gotFullLayout); + + QCOMPARE(layout.blockCount, 10); +} + +QTEST_MAIN(tst_QAbstractTextDocumentLayout) +#include "tst_qabstracttextdocumentlayout.moc" diff --git a/tests/auto/gui/text/qcssparser/.gitignore b/tests/auto/gui/text/qcssparser/.gitignore new file mode 100644 index 0000000000..6cc99800e0 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/.gitignore @@ -0,0 +1 @@ +tst_qcssparser diff --git a/tests/auto/gui/text/qcssparser/qcssparser.pro b/tests/auto/gui/text/qcssparser/qcssparser.pro new file mode 100644 index 0000000000..16fa265231 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/qcssparser.pro @@ -0,0 +1,17 @@ +load(qttest_p4) +SOURCES += tst_qcssparser.cpp +QT += xml gui-private + +requires(contains(QT_CONFIG,private_tests)) +!symbian: { + DEFINES += SRCDIR=\\\"$$PWD\\\" +} + +wince*|symbian: { + addFiles.files = testdata + addFiles.path = . + timesFont.files = C:/Windows/Fonts/times.ttf + timesFont.path = . + DEPLOYMENT += addFiles timesFont +} + diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/comments/input b/tests/auto/gui/text/qcssparser/testdata/scanner/comments/input new file mode 100644 index 0000000000..af2b659a5b --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/comments/input @@ -0,0 +1 @@ +/* let's see if comments actually work *//*foo*/ "it /*should be preserved \"in strings*/ though" diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/comments/output b/tests/auto/gui/text/qcssparser/testdata/scanner/comments/output new file mode 100644 index 0000000000..80ede0ba22 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/comments/output @@ -0,0 +1,4 @@ +S|/* let's see if comments actually work */ +S|/*foo*/ +S| +STRING|"it /*should be preserved "in strings*/ though" diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/comments2/input b/tests/auto/gui/text/qcssparser/testdata/scanner/comments2/input new file mode 100644 index 0000000000..3135acd78c --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/comments2/input @@ -0,0 +1 @@ +/*foo*/{/*foo*/+/*foo*/>/*foo*/,/*foo*/}/*foo*/- diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/comments2/output b/tests/auto/gui/text/qcssparser/testdata/scanner/comments2/output new file mode 100644 index 0000000000..d1f1259869 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/comments2/output @@ -0,0 +1,12 @@ +S|/*foo*/ +LBRACE|{ +S|/*foo*/ +PLUS|+ +S|/*foo*/ +GREATER|> +S|/*foo*/ +COMMA|, +S|/*foo*/ +RBRACE|} +S|/*foo*/ +MINUS|- diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/comments3/input b/tests/auto/gui/text/qcssparser/testdata/scanner/comments3/input new file mode 100644 index 0000000000..8634543a8f --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/comments3/input @@ -0,0 +1 @@ +url(/*comment*/"www.kde.org") diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/comments3/output b/tests/auto/gui/text/qcssparser/testdata/scanner/comments3/output new file mode 100644 index 0000000000..af7bad752a --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/comments3/output @@ -0,0 +1,4 @@ +FUNCTION|url( +S|/*comment*/ +STRING|"www.kde.org" +RPAREN|) diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/comments4/input b/tests/auto/gui/text/qcssparser/testdata/scanner/comments4/input new file mode 100644 index 0000000000..62d039b00c --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/comments4/input @@ -0,0 +1 @@ +!/*hmm*/important diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/comments4/output b/tests/auto/gui/text/qcssparser/testdata/scanner/comments4/output new file mode 100644 index 0000000000..eb86e7bd15 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/comments4/output @@ -0,0 +1,3 @@ +EXCLAMATION_SYM|! +S|/*hmm*/ +IDENT|important diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/quotedstring/input b/tests/auto/gui/text/qcssparser/testdata/scanner/quotedstring/input new file mode 100644 index 0000000000..deae3a8dab --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/quotedstring/input @@ -0,0 +1 @@ +background: 'test_bug.png'; diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/quotedstring/output b/tests/auto/gui/text/qcssparser/testdata/scanner/quotedstring/output new file mode 100644 index 0000000000..ed52419594 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/quotedstring/output @@ -0,0 +1,5 @@ +IDENT|background +COLON|: +S| +STRING|'test_bug.png' +SEMICOLON|; diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/simple/input b/tests/auto/gui/text/qcssparser/testdata/scanner/simple/input new file mode 100644 index 0000000000..b37e587661 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/simple/input @@ -0,0 +1 @@ +p { display:block; } diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/simple/output b/tests/auto/gui/text/qcssparser/testdata/scanner/simple/output new file mode 100644 index 0000000000..71c60ec9bf --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/simple/output @@ -0,0 +1,9 @@ +IDENT|p +LBRACE| { +S| +IDENT|display +COLON|: +IDENT|block +SEMICOLON|; +S| +RBRACE|} diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/unicode/input b/tests/auto/gui/text/qcssparser/testdata/scanner/unicode/input new file mode 100644 index 0000000000..2c33f7be97 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/unicode/input @@ -0,0 +1 @@ +\41"\7E"\00006Df diff --git a/tests/auto/gui/text/qcssparser/testdata/scanner/unicode/output b/tests/auto/gui/text/qcssparser/testdata/scanner/unicode/output new file mode 100644 index 0000000000..0829c37e28 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/testdata/scanner/unicode/output @@ -0,0 +1,3 @@ +IDENT|A +STRING|"~" +IDENT|mf diff --git a/tests/auto/gui/text/qcssparser/tst_qcssparser.cpp b/tests/auto/gui/text/qcssparser/tst_qcssparser.cpp new file mode 100644 index 0000000000..1e82431a48 --- /dev/null +++ b/tests/auto/gui/text/qcssparser/tst_qcssparser.cpp @@ -0,0 +1,1718 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QtXml/QtXml> +#if defined(Q_OS_WINCE) +#include <QtGui/QFontDatabase> +#endif + +//TESTED_CLASS=QCss +//TESTED_FILES=gui/text/qcssparser.cpp gui/text/qcssparser_p.h + +#include "private/qcssparser_p.h" + +class tst_QCssParser : public QObject +{ + Q_OBJECT + +public slots: + void initTestCase(); + void cleanupTestCase(); + +private slots: + void scanner_data(); + void scanner(); + void term_data(); + void term(); + void expr_data(); + void expr(); + void import(); + void media(); + void page(); + void ruleset(); + void selector_data(); + void selector(); + void prio(); + void escapes(); + void malformedDeclarations_data(); + void malformedDeclarations(); + void invalidAtKeywords(); + void marginValue(); + void marginValue_data(); + void colorValue_data(); + void colorValue(); + void styleSelector_data(); + void styleSelector(); + void specificity_data(); + void specificity(); + void specificitySort_data(); + void specificitySort(); + void rulesForNode_data(); + void rulesForNode(); + void shorthandBackgroundProperty_data(); + void shorthandBackgroundProperty(); + void pseudoElement_data(); + void pseudoElement(); + void gradient_data(); + void gradient(); + void extractFontFamily_data(); + void extractFontFamily(); + void extractBorder_data(); + void extractBorder(); + void noTextDecoration(); + void quotedAndUnquotedIdentifiers(); + +private: +#if defined(Q_OS_WINCE) + int m_timesFontId; +#endif +}; + +void tst_QCssParser::initTestCase() +{ +#if defined(Q_OS_WINCE) + QFontDatabase fontDB; + m_timesFontId = -1; + if (!fontDB.families().contains("Times New Roman")) { + m_timesFontId = QFontDatabase::addApplicationFont("times.ttf"); + QVERIFY(m_timesFontId != -1); + } +#endif +} + +void tst_QCssParser::cleanupTestCase() +{ +#if defined(Q_OS_WINCE) + if (m_timesFontId != -1) + QFontDatabase::removeApplicationFont(m_timesFontId); +#endif +} + +void tst_QCssParser::scanner_data() +{ + QTest::addColumn<QString>("input"); + QTest::addColumn<QString>("output"); + +#if !defined(Q_OS_IRIX) && !defined(Q_OS_WINCE) && !defined(Q_OS_SYMBIAN) + QDir d(SRCDIR); +#else + QDir d(QDir::current()); +#endif + d.cd("testdata"); + d.cd("scanner"); + foreach (QFileInfo test, d.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QString dir = test.absoluteFilePath() + QDir::separator(); + QTest::newRow(qPrintable(test.baseName())) + << dir + "input" + << dir + "output" + ; + } +} + + +static const char *tokenName(QCss::TokenType t) +{ + switch (t) { + case QCss::NONE: return "NONE"; + case QCss::S: return "S"; + case QCss::CDO: return "CDO"; + case QCss::CDC: return "CDC"; + case QCss::INCLUDES: return "INCLUDES"; + case QCss::DASHMATCH: return "DASHMATCH"; + case QCss::LBRACE: return "LBRACE"; + case QCss::PLUS: return "PLUS"; + case QCss::GREATER: return "GREATER"; + case QCss::COMMA: return "COMMA"; + case QCss::STRING: return "STRING"; + case QCss::INVALID: return "INVALID"; + case QCss::IDENT: return "IDENT"; + case QCss::HASH: return "HASH"; + case QCss::ATKEYWORD_SYM: return "ATKEYWORD_SYM"; + case QCss::EXCLAMATION_SYM: return "EXCLAMATION_SYM"; + case QCss::LENGTH: return "LENGTH"; + case QCss::PERCENTAGE: return "PERCENTAGE"; + case QCss::NUMBER: return "NUMBER"; + case QCss::FUNCTION: return "FUNCTION"; + case QCss::COLON: return "COLON"; + case QCss::SEMICOLON: return "SEMICOLON"; + case QCss::RBRACE: return "RBRACE"; + case QCss::SLASH: return "SLASH"; + case QCss::MINUS: return "MINUS"; + case QCss::DOT: return "DOT"; + case QCss::STAR: return "STAR"; + case QCss::LBRACKET: return "LBRACKET"; + case QCss::RBRACKET: return "RBRACKET"; + case QCss::EQUAL: return "EQUAL"; + case QCss::LPAREN: return "LPAREN"; + case QCss::RPAREN: return "RPAREN"; + case QCss::OR: return "OR"; + } + return ""; +} + +static void debug(const QVector<QCss::Symbol> &symbols, int index = -1) +{ + qDebug() << "all symbols:"; + for (int i = 0; i < symbols.count(); ++i) + qDebug() << "(" << i << "); Token:" << tokenName(symbols.at(i).token) << "; Lexem:" << symbols.at(i).lexem(); + if (index != -1) + qDebug() << "failure at index" << index; +} + +//static void debug(const QCss::Parser &p) { debug(p.symbols); } + +void tst_QCssParser::scanner() +{ + QFETCH(QString, input); + QFETCH(QString, output); + + QFile inputFile(input); + QVERIFY(inputFile.open(QIODevice::ReadOnly|QIODevice::Text)); + QVector<QCss::Symbol> symbols; + QCss::Scanner::scan(QCss::Scanner::preprocess(QString::fromUtf8(inputFile.readAll())), &symbols); + + QVERIFY(symbols.count() > 1); + QVERIFY(symbols.last().token == QCss::S); + QVERIFY(symbols.last().lexem() == QLatin1String("\n")); + symbols.remove(symbols.count() - 1, 1); + + QFile outputFile(output); + QVERIFY(outputFile.open(QIODevice::ReadOnly|QIODevice::Text)); + QStringList lines; + while (!outputFile.atEnd()) { + QString line = QString::fromUtf8(outputFile.readLine()); + if (line.endsWith(QLatin1Char('\n'))) + line.chop(1); + lines.append(line); + } + + if (lines.count() != symbols.count()) { + debug(symbols); + QCOMPARE(lines.count(), symbols.count()); + } + + for (int i = 0; i < lines.count(); ++i) { + QStringList l = lines.at(i).split(QChar::fromLatin1('|')); + QCOMPARE(l.count(), 2); + const QString expectedToken = l.at(0); + const QString expectedLexem = l.at(1); + QString actualToken = QString::fromLatin1(tokenName(symbols.at(i).token)); + if (actualToken != expectedToken) { + debug(symbols, i); + QCOMPARE(actualToken, expectedToken); + } + if (symbols.at(i).lexem() != expectedLexem) { + debug(symbols, i); + QCOMPARE(symbols.at(i).lexem(), expectedLexem); + } + } +} + +Q_DECLARE_METATYPE(QCss::Value) + +void tst_QCssParser::term_data() +{ + QTest::addColumn<bool>("parseSuccess"); + QTest::addColumn<QString>("css"); + QTest::addColumn<QCss::Value>("expectedValue"); + + QCss::Value val; + + val.type = QCss::Value::Percentage; + val.variant = QVariant(double(200)); + QTest::newRow("percentage") << true << "200%" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10px"); + QTest::newRow("px") << true << "10px" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10cm"); + QTest::newRow("cm") << true << "10cm" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10mm"); + QTest::newRow("mm") << true << "10mm" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10pt"); + QTest::newRow("pt") << true << "10pt" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10pc"); + QTest::newRow("pc") << true << "10pc" << val; + + val.type = QCss::Value::Length; + val.variant = QString("42in"); + QTest::newRow("inch") << true << "42in" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10deg"); + QTest::newRow("deg") << true << "10deg" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10rad"); + QTest::newRow("rad") << true << "10rad" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10grad"); + QTest::newRow("grad") << true << "10grad" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10ms"); + QTest::newRow("time") << true << "10ms" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10s"); + QTest::newRow("times") << true << "10s" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10hz"); + QTest::newRow("hz") << true << "10hz" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10khz"); + QTest::newRow("khz") << true << "10khz" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10myunit"); + QTest::newRow("dimension") << true << "10myunit" << val; + + val.type = QCss::Value::Percentage; + + val.type = QCss::Value::Percentage; + val.variant = QVariant(double(-200)); + QTest::newRow("minuspercentage") << true << "-200%" << val; + + val.type = QCss::Value::Length; + val.variant = QString("10em"); + QTest::newRow("ems") << true << "10em" << val; + + val.type = QCss::Value::String; + val.variant = QVariant(QString("foo")); + QTest::newRow("string") << true << "\"foo\"" << val; + + val.type = QCss::Value::Function; + val.variant = QVariant(QStringList() << "myFunc" << "23, (nested text)"); + QTest::newRow("function") << true << "myFunc(23, (nested text))" << val; + + QTest::newRow("function_failure") << false << "myFunction((blah)" << val; + QTest::newRow("function_failure2") << false << "+myFunc(23, (nested text))" << val; + + val.type = QCss::Value::Color; + val.variant = QVariant(QColor("#12ff34")); + QTest::newRow("hexcolor") << true << "#12ff34" << val; + + val.type = QCss::Value::Color; + val.variant = QVariant(QColor("#ffbb00")); + QTest::newRow("hexcolor2") << true << "#fb0" << val; + + QTest::ignoreMessage(QtWarningMsg, "QCssParser::parseHexColor: Unknown color name '#cafebabe'"); + QTest::newRow("hexcolor_failure") << false << "#cafebabe" << val; + + val.type = QCss::Value::Uri; + val.variant = QString("www.kde.org"); + QTest::newRow("uri1") << true << "url(\"www.kde.org\")" << val; + + QTest::newRow("uri2") << true << "url(www.kde.org)" << val; + + val.type = QCss::Value::KnownIdentifier; + val.variant = int(QCss::Value_Italic); + QTest::newRow("italic") << true << "italic" << val; + + val.type = QCss::Value::KnownIdentifier; + val.variant = int(QCss::Value_Italic); + QTest::newRow("ItaLIc") << true << "ItaLIc" << val; +} + +void tst_QCssParser::term() +{ + QFETCH(bool, parseSuccess); + QFETCH(QString, css); + QFETCH(QCss::Value, expectedValue); + + QCss::Parser parser(css); + QCss::Value val; + QVERIFY(parser.testTerm()); + QCOMPARE(parser.parseTerm(&val), parseSuccess); + if (parseSuccess) { + QCOMPARE(int(val.type), int(expectedValue.type)); + if (val.variant != expectedValue.variant) { + qDebug() << "val.variant:" << val.variant << "expectedValue.variant:" << expectedValue.variant; + QCOMPARE(val.variant, expectedValue.variant); + } + } +} + +Q_DECLARE_METATYPE(QVector<QCss::Value>) + +void tst_QCssParser::expr_data() +{ + QTest::addColumn<bool>("parseSuccess"); + QTest::addColumn<QString>("css"); + QTest::addColumn<QVector<QCss::Value> >("expectedValues"); + + QVector<QCss::Value> values; + QCss::Value val; + + QCss::Value comma; + comma.type = QCss::Value::TermOperatorComma; + + val.type = QCss::Value::Identifier; + val.variant = QLatin1String("foo"); + values << val; + values << comma; + val.variant = QLatin1String("bar"); + values << val; + values << comma; + val.variant = QLatin1String("baz"); + values << val; + QTest::newRow("list") << true << "foo, bar, baz" << values; + values.clear(); +} + +void tst_QCssParser::expr() +{ + QFETCH(bool, parseSuccess); + QFETCH(QString, css); + QFETCH(QVector<QCss::Value>, expectedValues); + + QCss::Parser parser(css); + QVector<QCss::Value> values; + QVERIFY(parser.testExpr()); + QCOMPARE(parser.parseExpr(&values), parseSuccess); + if (parseSuccess) { + QCOMPARE(values.count(), expectedValues.count()); + + for (int i = 0; i < values.count(); ++i) { + QCOMPARE(int(values.at(i).type), int(expectedValues.at(i).type)); + QCOMPARE(values.at(i).variant, expectedValues.at(i).variant); + } + } +} + +void tst_QCssParser::import() +{ + QCss::Parser parser("@import \"plainstring\";"); + QVERIFY(parser.testImport()); + QCss::ImportRule rule; + QVERIFY(parser.parseImport(&rule)); + QCOMPARE(rule.href, QString("plainstring")); + + parser = QCss::Parser("@import url(\"www.kde.org\") print/*comment*/,screen;"); + QVERIFY(parser.testImport()); + QVERIFY(parser.parseImport(&rule)); + QCOMPARE(rule.href, QString("www.kde.org")); + QCOMPARE(rule.media.count(), 2); + QCOMPARE(rule.media.at(0), QString("print")); + QCOMPARE(rule.media.at(1), QString("screen")); +} + +void tst_QCssParser::media() +{ + QCss::Parser parser("@media print/*comment*/,screen /*comment to ignore*/{ }"); + QVERIFY(parser.testMedia()); + QCss::MediaRule rule; + QVERIFY(parser.parseMedia(&rule)); + QCOMPARE(rule.media.count(), 2); + QCOMPARE(rule.media.at(0), QString("print")); + QCOMPARE(rule.media.at(1), QString("screen")); + QVERIFY(rule.styleRules.isEmpty()); +} + +void tst_QCssParser::page() +{ + QCss::Parser parser("@page :first/*comment to ignore*/{ }"); + QVERIFY(parser.testPage()); + QCss::PageRule rule; + QVERIFY(parser.parsePage(&rule)); + QCOMPARE(rule.selector, QString("first")); + QVERIFY(rule.declarations.isEmpty()); +} + +void tst_QCssParser::ruleset() +{ + { + QCss::Parser parser("p/*foo*/{ }"); + QVERIFY(parser.testRuleset()); + QCss::StyleRule rule; + QVERIFY(parser.parseRuleset(&rule)); + QCOMPARE(rule.selectors.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).elementName, QString("p")); + QVERIFY(rule.declarations.isEmpty()); + } + + { + QCss::Parser parser("p/*comment*/,div{ }"); + QVERIFY(parser.testRuleset()); + QCss::StyleRule rule; + QVERIFY(parser.parseRuleset(&rule)); + QCOMPARE(rule.selectors.count(), 2); + QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).elementName, QString("p")); + QCOMPARE(rule.selectors.at(1).basicSelectors.count(), 1); + QCOMPARE(rule.selectors.at(1).basicSelectors.at(0).elementName, QString("div")); + QVERIFY(rule.declarations.isEmpty()); + } + + { + QCss::Parser parser(":before, :after { }"); + QVERIFY(parser.testRuleset()); + QCss::StyleRule rule; + QVERIFY(parser.parseRuleset(&rule)); + QCOMPARE(rule.selectors.count(), 2); + + QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).pseudos.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).pseudos.at(0).name, QString("before")); + + QCOMPARE(rule.selectors.at(1).basicSelectors.count(), 1); + QCOMPARE(rule.selectors.at(1).basicSelectors.at(0).pseudos.count(), 1); + QCOMPARE(rule.selectors.at(1).basicSelectors.at(0).pseudos.at(0).name, QString("after")); + + QVERIFY(rule.declarations.isEmpty()); + } + +} + +Q_DECLARE_METATYPE(QCss::Selector) + +void tst_QCssParser::selector_data() +{ + QTest::addColumn<QString>("css"); + QTest::addColumn<QCss::Selector>("expectedSelector"); + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "p"; + basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfPreceeds; + sel.basicSelectors << basic; + + basic = QCss::BasicSelector(); + basic.elementName = "div"; + sel.basicSelectors << basic; + + QTest::newRow("comment") << QString("p/* */+ div") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = QString(); + sel.basicSelectors << basic; + + QTest::newRow("any") << QString("*") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + sel.basicSelectors << basic; + + QTest::newRow("element") << QString("e") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfAncestor; + sel.basicSelectors << basic; + + basic.elementName = "f"; + basic.relationToNext = QCss::BasicSelector::NoRelation; + sel.basicSelectors << basic; + + QTest::newRow("descendant") << QString("e f") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfParent; + sel.basicSelectors << basic; + + basic.elementName = "f"; + basic.relationToNext = QCss::BasicSelector::NoRelation; + sel.basicSelectors << basic; + + QTest::newRow("parent") << QString("e > f") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + QCss::Pseudo pseudo; + pseudo.name = "first-child"; + basic.pseudos.append(pseudo); + sel.basicSelectors << basic; + + QTest::newRow("first-child") << QString("e:first-child") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + QCss::Pseudo pseudo; + pseudo.name = "c"; + pseudo.function = "lang"; + basic.pseudos.append(pseudo); + sel.basicSelectors << basic; + + QTest::newRow("lang") << QString("e:lang(c)") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + basic.relationToNext = QCss::BasicSelector::MatchNextSelectorIfPreceeds; + sel.basicSelectors << basic; + + basic.elementName = "f"; + basic.relationToNext = QCss::BasicSelector::NoRelation; + sel.basicSelectors << basic; + + QTest::newRow("precede") << QString("e + f") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + QCss::AttributeSelector attrSel; + attrSel.name = "foo"; + basic.attributeSelectors << attrSel; + sel.basicSelectors << basic; + + QTest::newRow("attr") << QString("e[foo]") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + QCss::AttributeSelector attrSel; + attrSel.name = "foo"; + attrSel.value = "warning"; + attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchEqual; + basic.attributeSelectors << attrSel; + sel.basicSelectors << basic; + + QTest::newRow("attr-equal") << QString("e[foo=\"warning\"]") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + QCss::AttributeSelector attrSel; + attrSel.name = "foo"; + attrSel.value = "warning"; + attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchContains; + basic.attributeSelectors << attrSel; + sel.basicSelectors << basic; + + QTest::newRow("attr-contains") << QString("e[foo~=\"warning\"]") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + QCss::AttributeSelector attrSel; + attrSel.name = "lang"; + attrSel.value = "en"; + attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchBeginsWith; + basic.attributeSelectors << attrSel; + sel.basicSelectors << basic; + + QTest::newRow("attr-contains") << QString("e[lang|=\"en\"]") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "div"; + + QCss::AttributeSelector attrSel; + attrSel.name = "class"; + attrSel.valueMatchCriterium = QCss::AttributeSelector::MatchContains; + attrSel.value = "warning"; + basic.attributeSelectors.append(attrSel); + + attrSel.value = "foo"; + basic.attributeSelectors.append(attrSel); + + sel.basicSelectors << basic; + + QTest::newRow("class") << QString("div.warning.foo") << sel; + } + + { + QCss::Selector sel; + QCss::BasicSelector basic; + + basic.elementName = "e"; + basic.ids << "myid"; + sel.basicSelectors << basic; + + QTest::newRow("id") << QString("e#myid") << sel; + } +} + +void tst_QCssParser::selector() +{ + QFETCH(QString, css); + QFETCH(QCss::Selector, expectedSelector); + + QCss::Parser parser(css); + QVERIFY(parser.testSelector()); + QCss::Selector selector; + QVERIFY(parser.parseSelector(&selector)); + + QCOMPARE(selector.basicSelectors.count(), expectedSelector.basicSelectors.count()); + for (int i = 0; i < selector.basicSelectors.count(); ++i) { + const QCss::BasicSelector sel = selector.basicSelectors.at(i); + const QCss::BasicSelector expectedSel = expectedSelector.basicSelectors.at(i); + QCOMPARE(sel.elementName, expectedSel.elementName); + QCOMPARE(int(sel.relationToNext), int(expectedSel.relationToNext)); + + QCOMPARE(sel.pseudos.count(), expectedSel.pseudos.count()); + for (int i = 0; i < sel.pseudos.count(); ++i) { + QCOMPARE(sel.pseudos.at(i).name, expectedSel.pseudos.at(i).name); + QCOMPARE(sel.pseudos.at(i).function, expectedSel.pseudos.at(i).function); + } + + QCOMPARE(sel.attributeSelectors.count(), expectedSel.attributeSelectors.count()); + for (int i = 0; i < sel.attributeSelectors.count(); ++i) { + QCOMPARE(sel.attributeSelectors.at(i).name, expectedSel.attributeSelectors.at(i).name); + QCOMPARE(sel.attributeSelectors.at(i).value, expectedSel.attributeSelectors.at(i).value); + QCOMPARE(int(sel.attributeSelectors.at(i).valueMatchCriterium), int(expectedSel.attributeSelectors.at(i).valueMatchCriterium)); + } + } +} + +void tst_QCssParser::prio() +{ + { + QCss::Parser parser("!important"); + QVERIFY(parser.testPrio()); + } + { + QCss::Parser parser("!impOrTAnt"); + QVERIFY(parser.testPrio()); + } + { + QCss::Parser parser("!\"important\""); + QVERIFY(!parser.testPrio()); + QCOMPARE(parser.index, 0); + } + { + QCss::Parser parser("!importbleh"); + QVERIFY(!parser.testPrio()); + QCOMPARE(parser.index, 0); + } +} + +void tst_QCssParser::escapes() +{ + QCss::Parser parser("\\hello"); + parser.test(QCss::IDENT); + QCOMPARE(parser.lexem(), QString("hello")); +} + +void tst_QCssParser::malformedDeclarations_data() +{ + QTest::addColumn<QString>("css"); + + QTest::newRow("1") << QString("p { color:green }"); + QTest::newRow("2") << QString("p { color:green; color } /* malformed declaration missing ':', value */"); + QTest::newRow("3") << QString("p { color:red; color; color:green } /* same with expected recovery */"); + QTest::newRow("4") << QString("p { color:green; color: } /* malformed declaration missing value */"); + QTest::newRow("5") << QString("p { color:red; color:; color:green } /* same with expected recovery */"); + QTest::newRow("6") << QString("p { color:green; color{;color:maroon} } /* unexpected tokens { } */"); + QTest::newRow("7") << QString("p { color:red; color{;color:maroon}; color:green } /* same with recovery */"); +} + +void tst_QCssParser::malformedDeclarations() +{ + QFETCH(QString, css); + QCss::Parser parser(css); + QVERIFY(parser.testRuleset()); + QCss::StyleRule rule; + QVERIFY(parser.parseRuleset(&rule)); + + QCOMPARE(rule.selectors.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).elementName, QString("p")); + + QVERIFY(rule.declarations.count() >= 1); + QCOMPARE(int(rule.declarations.last().d->propertyId), int(QCss::Color)); + QCOMPARE(rule.declarations.last().d->values.count(), 1); + QCOMPARE(int(rule.declarations.last().d->values.at(0).type), int(QCss::Value::Identifier)); + QCOMPARE(rule.declarations.last().d->values.at(0).variant.toString(), QString("green")); +} + +void tst_QCssParser::invalidAtKeywords() +{ + QCss::Parser parser("" + "@three-dee {" + " @background-lighting {" + " azimuth: 30deg;" + " elevation: 190deg;" + " }" + " h1 { color: red }" + "}" + "h1 { color: blue }"); + + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1); + QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? + sheet.styleRules.at(0) : *sheet.nameIndex.begin(); + + QCOMPARE(rule.selectors.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.count(), 1); + QCOMPARE(rule.selectors.at(0).basicSelectors.at(0).elementName, QString("h1")); + + QCOMPARE(rule.declarations.count(), 1); + QCOMPARE(int(rule.declarations.at(0).d->propertyId), int(QCss::Color)); + QCOMPARE(rule.declarations.at(0).d->values.count(), 1); + QCOMPARE(int(rule.declarations.at(0).d->values.at(0).type), int(QCss::Value::Identifier)); + QCOMPARE(rule.declarations.at(0).d->values.at(0).variant.toString(), QString("blue")); +} + +Q_DECLARE_METATYPE(QColor) + +void tst_QCssParser::colorValue_data() +{ + QTest::addColumn<QString>("css"); + QTest::addColumn<QColor>("expectedColor"); + + QTest::newRow("identifier") << "color: black" << QColor("black"); + QTest::newRow("string") << "color: \"green\"" << QColor("green"); + QTest::newRow("hexcolor") << "color: #12af0e" << QColor(0x12, 0xaf, 0x0e); + QTest::newRow("functional1") << "color: rgb(21, 45, 73)" << QColor(21, 45, 73); + QTest::newRow("functional2") << "color: rgb(100%, 0%, 100%)" << QColor(0xff, 0, 0xff); + QTest::newRow("rgba") << "color: rgba(10, 20, 30, 40)" << QColor(10, 20, 30, 40); + QTest::newRow("rgb") << "color: rgb(10, 20, 30, 40)" << QColor(10, 20, 30, 40); + QTest::newRow("hsl") << "color: hsv(10, 20, 30)" << QColor::fromHsv(10, 20, 30, 255); + QTest::newRow("hsla") << "color: hsva(10, 20, 30, 40)" << QColor::fromHsv(10, 20, 30, 40); + QTest::newRow("invalid1") << "color: rgb(why, does, it, always, rain, on, me)" << QColor(); + QTest::newRow("invalid2") << "color: rgba(i, meant, norway)" << QColor(); + QTest::newRow("role") << "color: palette(base)" << qApp->palette().color(QPalette::Base); + QTest::newRow("role2") << "color: palette( window-text ) " << qApp->palette().color(QPalette::WindowText); + QTest::newRow("transparent") << "color: transparent" << QColor(Qt::transparent); +} + +void tst_QCssParser::colorValue() +{ + QFETCH(QString, css); + QFETCH(QColor, expectedColor); + + QCss::Parser parser(css); + QCss::Declaration decl; + QVERIFY(parser.parseNextDeclaration(&decl)); + const QColor col = decl.colorValue(); + QVERIFY(expectedColor.isValid() == col.isValid()); + QCOMPARE(col, expectedColor); +} + +class DomStyleSelector : public QCss::StyleSelector +{ +public: + inline DomStyleSelector(const QDomDocument &doc, const QCss::StyleSheet &sheet) + : doc(doc) + { + styleSheets.append(sheet); + } + + virtual QStringList nodeNames(NodePtr node) const { return QStringList(reinterpret_cast<QDomElement *>(node.ptr)->tagName()); } + virtual QString attribute(NodePtr node, const QString &name) const { return reinterpret_cast<QDomElement *>(node.ptr)->attribute(name); } + virtual bool hasAttribute(NodePtr node, const QString &name) const { return reinterpret_cast<QDomElement *>(node.ptr)->hasAttribute(name); } + virtual bool hasAttributes(NodePtr node) const { return reinterpret_cast<QDomElement *>(node.ptr)->hasAttributes(); } + + virtual bool isNullNode(NodePtr node) const { + return reinterpret_cast<QDomElement *>(node.ptr)->isNull(); + } + virtual NodePtr parentNode(NodePtr node) const { + NodePtr parent; + parent.ptr = new QDomElement(reinterpret_cast<QDomElement *>(node.ptr)->parentNode().toElement()); + return parent; + } + virtual NodePtr duplicateNode(NodePtr node) const { + NodePtr n; + n.ptr = new QDomElement(*reinterpret_cast<QDomElement *>(node.ptr)); + return n; + } + virtual NodePtr previousSiblingNode(NodePtr node) const { + NodePtr sibling; + sibling.ptr = new QDomElement(reinterpret_cast<QDomElement *>(node.ptr)->previousSiblingElement()); + return sibling; + } + virtual void freeNode(NodePtr node) const { + delete reinterpret_cast<QDomElement *>(node.ptr); + } + +private: + QDomDocument doc; +}; + +Q_DECLARE_METATYPE(QDomDocument) + +void tst_QCssParser::marginValue_data() +{ + QTest::addColumn<QString>("css"); + QTest::addColumn<QString>("expectedMargin"); + + QFont f; + int ex = QFontMetrics(f).xHeight(); + int em = QFontMetrics(f).height(); + + QTest::newRow("one value") << "margin: 1px" << "1 1 1 1"; + QTest::newRow("two values") << "margin: 1px 2px" << "1 2 1 2"; + QTest::newRow("three value") << "margin: 1px 2px 3px" << "1 2 3 2"; + QTest::newRow("four values") << "margin: 1px 2px 3px 4px" << "1 2 3 4"; + QTest::newRow("default px") << "margin: 1 2 3 4" << "1 2 3 4"; + QTest::newRow("no unit") << "margin: 1 2 3 4" << "1 2 3 4"; + QTest::newRow("em") << "margin: 1ex 2ex 3ex 4ex" << QString("%1 %2 %3 %4").arg(ex).arg(2*ex).arg(3*ex).arg(4*ex); + QTest::newRow("ex") << "margin: 1 2em 3px 4ex" << QString("%1 %2 %3 %4").arg(1).arg(2*em).arg(3).arg(4*ex); + + f.setPointSize(20); + f.setBold(true); + ex = QFontMetrics(f).xHeight(); + em = QFontMetrics(f).height(); + QTest::newRow("em2") << "font: bold 20pt; margin: 1ex 2ex 3ex 4ex" << QString("%1 %2 %3 %4").arg(ex).arg(2*ex).arg(3*ex).arg(4*ex); + QTest::newRow("ex2") << "margin: 1 2em 3px 4ex; font-size: 20pt; font-weight: bold;" << QString("%1 %2 %3 %4").arg(1).arg(2*em).arg(3).arg(4*ex); + + QTest::newRow("crap") << "margin: crap" << "0 0 0 0"; +} + +void tst_QCssParser::marginValue() +{ + QFETCH(QString, css); + QFETCH(QString, expectedMargin); + + QDomDocument doc; + QVERIFY(doc.setContent(QLatin1String("<!DOCTYPE test><test> <dummy/> </test>"))); + + css.prepend("dummy {"); + css.append("}"); + + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + DomStyleSelector testSelector(doc, sheet); + QDomElement e = doc.documentElement().firstChildElement(); + QCss::StyleSelector::NodePtr n; + n.ptr = &e; + QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(n); + QVector<QCss::Declaration> decls = rules.at(0).declarations; + QCss::ValueExtractor v(decls); + + { + int m[4]; + int p[4]; + int spacing; + v.extractBox(m, p, &spacing); + QString str = QString("%1 %2 %3 %4").arg(m[0]).arg(m[1]).arg(m[2]).arg(m[3]); + QCOMPARE(str, expectedMargin); + } +} + +void tst_QCssParser::styleSelector_data() +{ + QTest::addColumn<bool>("match"); + QTest::addColumn<QString>("selector"); + QTest::addColumn<QString>("xml"); + QTest::addColumn<QString>("elementToCheck"); + + QTest::newRow("plain") << true << QString("p") << QString("<p />") << QString(); + QTest::newRow("noplain") << false << QString("bar") << QString("<p />") << QString(); + + QTest::newRow("class") << true << QString(".foo") << QString("<p class=\"foo\" />") << QString(); + QTest::newRow("noclass") << false << QString(".bar") << QString("<p class=\"foo\" />") << QString(); + + QTest::newRow("attrset") << true << QString("[justset]") << QString("<p justset=\"bar\" />") << QString(); + QTest::newRow("notattrset") << false << QString("[justset]") << QString("<p otherattribute=\"blub\" />") << QString(); + + QTest::newRow("attrmatch") << true << QString("[foo=bar]") << QString("<p foo=\"bar\" />") << QString(); + QTest::newRow("noattrmatch") << false << QString("[foo=bar]") << QString("<p foo=\"xyz\" />") << QString(); + + QTest::newRow("contains") << true << QString("[foo~=bar]") << QString("<p foo=\"baz bleh bar\" />") << QString(); + QTest::newRow("notcontains") << false << QString("[foo~=bar]") << QString("<p foo=\"test\" />") << QString(); + + QTest::newRow("beingswith") << true << QString("[foo|=bar]") << QString("<p foo=\"bar-bleh\" />") << QString(); + QTest::newRow("notbeingswith") << false << QString("[foo|=bar]") << QString("<p foo=\"bleh-bar\" />") << QString(); + + QTest::newRow("attr2") << true << QString("[bar=foo]") << QString("<p bleh=\"bar\" bar=\"foo\" />") << QString(); + + QTest::newRow("universal1") << true << QString("*") << QString("<p />") << QString(); + + QTest::newRow("universal3") << false << QString("*[foo=bar]") << QString("<p foo=\"bleh\" />") << QString(); + QTest::newRow("universal4") << true << QString("*[foo=bar]") << QString("<p foo=\"bar\" />") << QString(); + + QTest::newRow("universal5") << false << QString("[foo=bar]") << QString("<p foo=\"bleh\" />") << QString(); + QTest::newRow("universal6") << true << QString("[foo=bar]") << QString("<p foo=\"bar\" />") << QString(); + + QTest::newRow("universal7") << true << QString(".charfmt1") << QString("<p class=\"charfmt1\" />") << QString(); + + QTest::newRow("id") << true << QString("#blub") << QString("<p id=\"blub\" />") << QString(); + QTest::newRow("noid") << false << QString("#blub") << QString("<p id=\"other\" />") << QString(); + + QTest::newRow("childselector") << true << QString("parent > child") + << QString("<parent><child /></parent>") + << QString("parent/child"); + + QTest::newRow("nochildselector2") << false << QString("parent > child") + << QString("<child><parent /></child>") + << QString("child/parent"); + + QTest::newRow("nochildselector3") << false << QString("parent > child") + << QString("<parent><intermediate><child /></intermediate></parent>") + << QString("parent/intermediate/child"); + + QTest::newRow("childselector2") << true << QString("parent[foo=bar] > child") + << QString("<parent foo=\"bar\"><child /></parent>") + << QString("parent/child"); + + QTest::newRow("nochildselector4") << false << QString("parent[foo=bar] > child") + << QString("<parent><child /></parent>") + << QString("parent/child"); + + QTest::newRow("nochildselector5") << false << QString("parent[foo=bar] > child") + << QString("<parent foo=\"bar\"><parent><child /></parent></parent>") + << QString("parent/parent/child"); + + QTest::newRow("childselectors") << true << QString("grandparent > parent > child") + << QString("<grandparent><parent><child /></parent></grandparent>") + << QString("grandparent/parent/child"); + + QTest::newRow("descendant") << true << QString("grandparent child") + << QString("<grandparent><parent><child /></parent></grandparent>") + << QString("grandparent/parent/child"); + + QTest::newRow("nodescendant") << false << QString("grandparent child") + << QString("<other><parent><child /></parent></other>") + << QString("other/parent/child"); + + QTest::newRow("descendant2") << true << QString("grandgrandparent grandparent child") + << QString("<grandgrandparent><inbetween><grandparent><parent><child /></parent></grandparent></inbetween></grandgrandparent>") + << QString("grandgrandparent/inbetween/grandparent/parent/child"); + + QTest::newRow("combined") << true << QString("grandparent parent > child") + << QString("<grandparent><inbetween><parent><child /></parent></inbetween></grandparent>") + << QString("grandparent/inbetween/parent/child"); + + QTest::newRow("combined2") << true << QString("grandparent > parent child") + << QString("<grandparent><parent><inbetween><child /></inbetween></parent></grandparent>") + << QString("grandparent/parent/inbetween/child"); + + QTest::newRow("combined3") << true << QString("grandparent > parent child") + << QString("<grandparent><parent><inbetween><child /></inbetween></parent></grandparent>") + << QString("grandparent/parent/inbetween/child"); + + QTest::newRow("nocombined") << false << QString("grandparent parent > child") + << QString("<inbetween><parent><child /></parent></inbetween>") + << QString("inbetween/parent/child"); + + QTest::newRow("nocombined2") << false << QString("grandparent parent > child") + << QString("<parent><child /></parent>") + << QString("parent/child"); + + QTest::newRow("previoussibling") << true << QString("p1 + p2") + << QString("<p1 /><p2 />") + << QString("p2"); + + QTest::newRow("noprevioussibling") << false << QString("p2 + p1") + << QString("<p1 /><p2 />") + << QString("p2"); + + QTest::newRow("ancestry_firstmismatch") << false << QString("parent child[foo=bar]") + << QString("<parent><child /></parent>") + << QString("parent/child"); + + QTest::newRow("unknown-pseudo") << false << QString("p:enabled:foobar") << QString("<p/>") << QString(); +} + +void tst_QCssParser::styleSelector() +{ + QFETCH(bool, match); + QFETCH(QString, selector); + QFETCH(QString, xml); + QFETCH(QString, elementToCheck); + + QString css = QString("%1 { background-color: green }").arg(selector); + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QDomDocument doc; + xml.prepend("<!DOCTYPE test><test>"); + xml.append("</test>"); + QVERIFY(doc.setContent(xml)); + + DomStyleSelector testSelector(doc, sheet); + + QDomElement e = doc.documentElement(); + if (elementToCheck.isEmpty()) { + e = e.firstChildElement(); + } else { + QStringList path = elementToCheck.split(QLatin1Char('/')); + do { + e = e.namedItem(path.takeFirst()).toElement(); + } while (!path.isEmpty()); + } + QVERIFY(!e.isNull()); + QCss::StyleSelector::NodePtr n; + n.ptr = &e; + QVector<QCss::Declaration> decls = testSelector.declarationsForNode(n); + + if (match) { + QCOMPARE(decls.count(), 1); + QCOMPARE(int(decls.at(0).d->propertyId), int(QCss::BackgroundColor)); + QCOMPARE(decls.at(0).d->values.count(), 1); + QCOMPARE(int(decls.at(0).d->values.at(0).type), int(QCss::Value::Identifier)); + QCOMPARE(decls.at(0).d->values.at(0).variant.toString(), QString("green")); + } else { + QVERIFY(decls.isEmpty()); + } +} + +void tst_QCssParser::specificity_data() +{ + QTest::addColumn<QString>("selector"); + QTest::addColumn<int>("specificity"); + + QTest::newRow("universal") << QString("*") << 0; + + QTest::newRow("elements+pseudos1") << QString("foo") << 1; + QTest::newRow("elements+pseudos2") << QString("foo *[blah]") << 1 + (1 * 0x10); + + // should strictly speaking be '2', but we don't support pseudo-elements yet, + // only pseudo-classes + QTest::newRow("elements+pseudos3") << QString("li:first-line") << 1 + (1 * 0x10); + + QTest::newRow("elements+pseudos4") << QString("ul li") << 2; + QTest::newRow("elements+pseudos5") << QString("ul ol+li") << 3; + QTest::newRow("elements+pseudos6") << QString("h1 + *[rel=up]") << 1 + (1 * 0x10); + + QTest::newRow("elements+pseudos7") << QString("ul ol li.red") << 3 + (1 * 0x10); + QTest::newRow("elements+pseudos8") << QString("li.red.level") << 1 + (2 * 0x10); + QTest::newRow("id") << QString("#x34y") << 1 * 0x100; +} + +void tst_QCssParser::specificity() +{ + QFETCH(QString, selector); + + QString css = QString("%1 { }").arg(selector); + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count() + sheet.idIndex.count() , 1); + QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? sheet.styleRules.at(0) + : (!sheet.nameIndex.isEmpty()) ? *sheet.nameIndex.begin() + : *sheet.idIndex.begin(); + QCOMPARE(rule.selectors.count(), 1); + QTEST(rule.selectors.at(0).specificity(), "specificity"); +} + +void tst_QCssParser::specificitySort_data() +{ + QTest::addColumn<QString>("firstSelector"); + QTest::addColumn<QString>("secondSelector"); + QTest::addColumn<QString>("xml"); + + QTest::newRow("universal1") << QString("*") << QString("p") << QString("<p />"); + QTest::newRow("attr") << QString("p") << QString("p[foo=bar]") << QString("<p foo=\"bar\" />"); + QTest::newRow("id") << QString("p") << QString("#hey") << QString("<p id=\"hey\" />"); + QTest::newRow("id2") << QString("[id=hey]") << QString("#hey") << QString("<p id=\"hey\" />"); + QTest::newRow("class") << QString("p") << QString(".hey") << QString("<p class=\"hey\" />"); +} + +void tst_QCssParser::specificitySort() +{ + QFETCH(QString, firstSelector); + QFETCH(QString, secondSelector); + QFETCH(QString, xml); + + firstSelector.append(" { color: green; }"); + secondSelector.append(" { color: red; }"); + + QDomDocument doc; + xml.prepend("<!DOCTYPE test><test>"); + xml.append("</test>"); + QVERIFY(doc.setContent(xml)); + + for (int i = 0; i < 2; ++i) { + QString css; + if (i == 0) + css = firstSelector + secondSelector; + else + css = secondSelector + firstSelector; + + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + DomStyleSelector testSelector(doc, sheet); + + QDomElement e = doc.documentElement().firstChildElement(); + QCss::StyleSelector::NodePtr n; + n.ptr = &e; + QVector<QCss::Declaration> decls = testSelector.declarationsForNode(n); + + QCOMPARE(decls.count(), 2); + + QCOMPARE(int(decls.at(0).d->propertyId), int(QCss::Color)); + QCOMPARE(decls.at(0).d->values.count(), 1); + QCOMPARE(int(decls.at(0).d->values.at(0).type), int(QCss::Value::Identifier)); + QCOMPARE(decls.at(0).d->values.at(0).variant.toString(), QString("green")); + + QCOMPARE(int(decls.at(1).d->propertyId), int(QCss::Color)); + QCOMPARE(decls.at(1).d->values.count(), 1); + QCOMPARE(int(decls.at(1).d->values.at(0).type), int(QCss::Value::Identifier)); + QCOMPARE(decls.at(1).d->values.at(0).variant.toString(), QString("red")); + } +} + +void tst_QCssParser::rulesForNode_data() +{ + QTest::addColumn<QString>("xml"); + QTest::addColumn<QString>("css"); + QTest::addColumn<quint64>("pseudoClass"); + QTest::addColumn<int>("declCount"); + QTest::addColumn<QString>("value0"); + QTest::addColumn<QString>("value1"); + + QTest::newRow("universal1") << QString("<p/>") << QString("* { color: red }") + << (quint64)QCss::PseudoClass_Unspecified << 1 << "red" << ""; + + QTest::newRow("basic") << QString("<p/>") << QString("p:enabled { color: red; bg:blue; }") + << (quint64)QCss::PseudoClass_Enabled << 2 << "red" << "blue"; + + QTest::newRow("single") << QString("<p/>") + << QString("p:enabled { color: red; } *:hover { color: white }") + << (quint64)QCss::PseudoClass_Hover << 1 << "white" << ""; + + QTest::newRow("multisel") << QString("<p/>") + << QString("p:enabled { color: red; } p:hover { color: gray } *:hover { color: white } ") + << (quint64)QCss::PseudoClass_Hover << 2 << "white" << "gray"; + + QTest::newRow("multisel2") << QString("<p/>") + << QString("p:enabled { color: red; } p:hover:focus { color: gray } *:hover { color: white } ") + << quint64(QCss::PseudoClass_Hover|QCss::PseudoClass_Focus) << 2 << "white" << "gray"; + + QTest::newRow("multisel3-diffspec") << QString("<p/>") + << QString("p:enabled { color: red; } p:hover:focus { color: gray } *:hover { color: white } ") + << quint64(QCss::PseudoClass_Hover) << 1 << "white" << ""; + + QTest::newRow("!-1") << QString("<p/>") + << QString("p:checked:!hover { color: red; } p:checked:hover { color: gray } p:checked { color: white }") + << quint64(QCss::PseudoClass_Hover|QCss::PseudoClass_Checked) << 2 << "white" << "gray"; + + QTest::newRow("!-2") << QString("<p/>") + << QString("p:checked:!hover:!pressed { color: red; } p:!checked:hover { color: gray } p:!focus { color: blue }") + << quint64(QCss::PseudoClass_Focus) << 0 << "" << ""; + + QTest::newRow("!-3") << QString("<p/>") + << QString("p:checked:!hover:!pressed { color: red; } p:!checked:hover { color: gray } p:!focus { color: blue; }") + << quint64(QCss::PseudoClass_Pressed) << 1 << "blue" << ""; +} + +void tst_QCssParser::rulesForNode() +{ + QFETCH(QString, xml); + QFETCH(QString, css); + QFETCH(quint64, pseudoClass); + QFETCH(int, declCount); + QFETCH(QString, value0); + QFETCH(QString, value1); + + QDomDocument doc; + xml.prepend("<!DOCTYPE test><test>"); + xml.append("</test>"); + QVERIFY(doc.setContent(xml)); + + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + DomStyleSelector testSelector(doc, sheet); + QDomElement e = doc.documentElement().firstChildElement(); + QCss::StyleSelector::NodePtr n; + n.ptr = &e; + QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(n); + + QVector<QCss::Declaration> decls; + for (int i = 0; i < rules.count(); i++) { + const QCss::Selector &selector = rules.at(i).selectors.at(0); + quint64 negated = 0; + quint64 cssClass = selector.pseudoClass(&negated); + if ((cssClass == QCss::PseudoClass_Unspecified) + || ((((cssClass & pseudoClass) == cssClass)) && ((negated & pseudoClass) == 0))) + decls += rules.at(i).declarations; + } + + QVERIFY(decls.count() == declCount); + + if (declCount > 0) + QCOMPARE(decls.at(0).d->values.at(0).variant.toString(), value0); + if (declCount > 1) + QCOMPARE(decls.at(1).d->values.at(0).variant.toString(), value1); +} + +void tst_QCssParser::shorthandBackgroundProperty_data() +{ + QTest::addColumn<QString>("css"); + QTest::addColumn<QBrush>("expectedBrush"); + QTest::addColumn<QString>("expectedImage"); + QTest::addColumn<int>("expectedRepeatValue"); + QTest::addColumn<int>("expectedAlignment"); + + QTest::newRow("simple color") << "background: red" << QBrush(QColor("red")) << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignTop); + QTest::newRow("plain color") << "background-color: red" << QBrush(QColor("red")) << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignTop); + QTest::newRow("palette color") << "background-color: palette(mid)" << qApp->palette().mid() << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignTop); + QTest::newRow("multiple") << "background: url(chess.png) blue repeat-y" << QBrush(QColor("blue")) << QString("chess.png") << int(QCss::Repeat_Y) << int(Qt::AlignLeft | Qt::AlignTop); + QTest::newRow("plain alignment") << "background-position: center" << QBrush() << QString() << int(QCss::Repeat_XY) << int(Qt::AlignCenter); + QTest::newRow("plain alignment2") << "background-position: left top" << QBrush() << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignTop); + QTest::newRow("plain alignment3") << "background-position: left" << QBrush() << QString() << int(QCss::Repeat_XY) << int(Qt::AlignLeft | Qt::AlignVCenter); + QTest::newRow("multi") << "background: left url(blah.png) repeat-x" << QBrush() << QString("blah.png") << int(QCss::Repeat_X) << int(Qt::AlignLeft | Qt::AlignVCenter); + QTest::newRow("multi2") << "background: url(blah.png) repeat-x top" << QBrush() << QString("blah.png") << int(QCss::Repeat_X) << int(Qt::AlignTop | Qt::AlignHCenter); + QTest::newRow("multi3") << "background: url(blah.png) top right" << QBrush() << QString("blah.png") << int(QCss::Repeat_XY) << int(Qt::AlignTop | Qt::AlignRight); +} + +void tst_QCssParser::shorthandBackgroundProperty() +{ + QFETCH(QString, css); + + QDomDocument doc; + QVERIFY(doc.setContent(QLatin1String("<!DOCTYPE test><test> <dummy/> </test>"))); + + css.prepend("dummy {"); + css.append("}"); + + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + DomStyleSelector testSelector(doc, sheet); + QDomElement e = doc.documentElement().firstChildElement(); + QCss::StyleSelector::NodePtr n; + n.ptr = &e; + QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(n); + QVector<QCss::Declaration> decls = rules.at(0).declarations; + QCss::ValueExtractor v(decls); + + QBrush brush; + QString image; + QCss::Repeat repeat = QCss::Repeat_XY; + Qt::Alignment alignment = Qt::AlignTop | Qt::AlignLeft; + QCss::Origin origin = QCss::Origin_Padding; + QCss::Attachment attachment; + QCss::Origin ignoredOrigin; + v.extractBackground(&brush, &image, &repeat, &alignment, &origin, &attachment, &ignoredOrigin); + + QFETCH(QBrush, expectedBrush); + QVERIFY(expectedBrush.color() == brush.color()); + + QTEST(image, "expectedImage"); + QTEST(int(repeat), "expectedRepeatValue"); + QTEST(int(alignment), "expectedAlignment"); + + //QTBUG-9674 : a second evaluation should give the same results + QVERIFY(v.extractBackground(&brush, &image, &repeat, &alignment, &origin, &attachment, &ignoredOrigin)); + QVERIFY(expectedBrush.color() == brush.color()); + QTEST(image, "expectedImage"); + QTEST(int(repeat), "expectedRepeatValue"); + QTEST(int(alignment), "expectedAlignment"); +} + +void tst_QCssParser::pseudoElement_data() +{ + QTest::addColumn<QString>("css"); + QTest::addColumn<QString>("pseudoElement"); + QTest::addColumn<int>("declCount"); + + // QComboBox::dropDown { border-image: blah; } + QTest::newRow("no pseudo-elements") << QString("dummy:hover { color: red }") << "" << 1; + QTest::newRow("no pseudo-elements") << QString("dummy:hover { color: red }") << "pe" << 0; + + QTest::newRow("1 pseudo-element (1)") << QString("dummy::pe:hover { color: red }") << "pe" << 1; + QTest::newRow("1 pseudo-element (2)") << QString("dummy::pe:hover { color: red }") << "x" << 0; + QTest::newRow("1 pseudo-element (2)") << QString("whatever::pe:hover { color: red }") << "pe" << 0; + + QTest::newRow("1 pseudo-element (3)") + << QString("dummy { color: white; } dummy::pe:hover { color: red }") << "x" << 0; + QTest::newRow("1 pseudo-element (4)") + << QString("dummy { color: white; } dummy::pe:hover { color: red } dummy { x:y }") << "" << 2; + QTest::newRow("1 pseudo-element (5)") + << QString("dummy { color: white; } dummy::pe:hover { color: red }") << "pe" << 1; + QTest::newRow("1 pseudo-element (6)") + << QString("dummy { color: white; } dummy::pe:hover { color: red } dummy::pe:checked { x: y} ") << "pe" << 2; + + QTest::newRow("2 pseudo-elements (1)") + << QString("dummy { color: white; } dummy::pe1:hover { color: red } dummy::pe2:checked { x: y} ") + << "" << 1; + QTest::newRow("2 pseudo-elements (1)") + << QString("dummy { color: white; } dummy::pe1:hover { color: red } dummy::pe2:checked { x: y} ") + << "pe1" << 1; + QTest::newRow("2 pseudo-elements (2)") + << QString("dummy { color: white; } dummy::pe1:hover { color: red } dummy::pe2:checked { x: y} ") + << "pe2" << 1; +} + +void tst_QCssParser::pseudoElement() +{ + QFETCH(QString, css); + QFETCH(QString, pseudoElement); + QFETCH(int, declCount); + + QDomDocument doc; + QVERIFY(doc.setContent(QLatin1String("<!DOCTYPE test><test> <dummy/> </test>"))); + + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + DomStyleSelector testSelector(doc, sheet); + QDomElement e = doc.documentElement().firstChildElement(); + QCss::StyleSelector::NodePtr n; + n.ptr = &e; + QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(n); + QVector<QCss::Declaration> decls; + for (int i = 0; i < rules.count(); i++) { + const QCss::Selector& selector = rules.at(i).selectors.at(0); + if (pseudoElement.compare(selector.pseudoElement(), Qt::CaseInsensitive) != 0) + continue; + decls += rules.at(i).declarations; + + } + QVERIFY(decls.count() == declCount); +} + +void tst_QCssParser::gradient_data() +{ + QTest::addColumn<QString>("css"); + QTest::addColumn<QString>("type"); + QTest::addColumn<QPointF>("start"); + QTest::addColumn<QPointF>("finalStop"); + QTest::addColumn<int>("spread"); + QTest::addColumn<qreal>("stop0"); + QTest::addColumn<QColor>("color0"); + QTest::addColumn<qreal>("stop1"); + QTest::addColumn<QColor>("color1"); + + QTest::newRow("color-string") << + "selection-background-color: qlineargradient(x1:1, y1:2, x2:3, y2:4, " + "stop:0.2 red, stop:0.5 green)" << "linear" << QPointF(1, 2) << QPointF(3, 4) + << 0 << qreal(0.2) << QColor("red") << qreal(0.5) << QColor("green"); + + QTest::newRow("color-#") << + "selection-background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, " + "spread: reflect, stop:0.2 #123, stop:0.5 #456)" << "linear" << QPointF(0, 0) << QPointF(0, 1) + << 1 << qreal(0.2) << QColor("#123") << qreal(0.5) << QColor("#456"); + + QTest::newRow("color-rgb") << + "selection-background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, " + "spread: reflect, stop:0.2 rgb(1, 2, 3), stop:0.5 rgba(1, 2, 3, 4))" << "linear" << QPointF(0, 0) << QPointF(0, 1) + << 1 << qreal(0.2) << QColor(1, 2, 3) << qreal(0.5) << QColor(1, 2, 3, 4); + + QTest::newRow("color-spaces") << + "selection-background-color: qlineargradient(x1: 0, y1 :0,x2:0, y2 : 1 , " + "spread: reflect, stop:0.2 rgb(1, 2, 3), stop: 0.5 rgba(1, 2, 3, 4))" << "linear" << QPointF(0, 0) << QPointF(0, 1) + << 1 << qreal(0.2) << QColor(1, 2, 3) << qreal(0.5) << QColor(1, 2, 3, 4); + + QTest::newRow("conical gradient") << + "selection-background-color: qconicalgradient(cx: 4, cy : 2, angle: 23, " + "spread: repeat, stop:0.2 rgb(1, 2, 3), stop:0.5 rgba(1, 2, 3, 4))" << "conical" << QPointF(4, 2) << QPointF() + << 2 << qreal(0.2) << QColor(1, 2, 3) << qreal(0.5) << QColor(1, 2, 3, 4); + + /* wont pass: stop values are expected to be sorted + QTest::newRow("unsorted-stop") << + "selection-background: lineargradient(x1:0, y1:0, x2:0, y2:1, " + "stop:0.5 green, stop:0.2 red)" << QPointF(0, 0) << QPointF(0, 1) + 0 << 0.2 << QColor("red") << 0.5 << QColor("green"); + */ +} + +void tst_QCssParser::gradient() +{ + QFETCH(QString, css); + QFETCH(QString, type); + QFETCH(QPointF, finalStop); + QFETCH(QPointF, start); + QFETCH(int, spread); + QFETCH(qreal, stop0); QFETCH(QColor, color0); + QFETCH(qreal, stop1); QFETCH(QColor, color1); + + QDomDocument doc; + QVERIFY(doc.setContent(QLatin1String("<!DOCTYPE test><test> <dummy/> </test>"))); + + css.prepend("dummy {"); + css.append("}"); + + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + DomStyleSelector testSelector(doc, sheet); + QDomElement e = doc.documentElement().firstChildElement(); + QCss::StyleSelector::NodePtr n; + n.ptr = &e; + QVector<QCss::StyleRule> rules = testSelector.styleRulesForNode(n); + QVector<QCss::Declaration> decls = rules.at(0).declarations; + QCss::ValueExtractor ve(decls); + QBrush fg, sfg; + QBrush sbg, abg; + QVERIFY(ve.extractPalette(&fg, &sfg, &sbg, &abg)); + if (type == "linear") { + QVERIFY(sbg.style() == Qt::LinearGradientPattern); + const QLinearGradient *lg = static_cast<const QLinearGradient *>(sbg.gradient()); + QCOMPARE(lg->start(), start); + QCOMPARE(lg->finalStop(), finalStop); + } else if (type == "conical") { + QVERIFY(sbg.style() == Qt::ConicalGradientPattern); + const QConicalGradient *cg = static_cast<const QConicalGradient *>(sbg.gradient()); + QCOMPARE(cg->center(), start); + } + const QGradient *g = sbg.gradient(); + QCOMPARE(g->spread(), QGradient::Spread(spread)); + QVERIFY(g->stops().at(0).first == stop0); + QVERIFY(g->stops().at(0).second == color0); + QVERIFY(g->stops().at(1).first == stop1); + QVERIFY(g->stops().at(1).second == color1); +} + +void tst_QCssParser::extractFontFamily_data() +{ + if (QFontInfo(QFont("Times New Roman")).family() != "Times New Roman") + QSKIP("'Times New Roman' font not found ", SkipAll); + + QTest::addColumn<QString>("css"); + QTest::addColumn<QString>("expectedFamily"); + + QTest::newRow("quoted-family-name") << "font-family: 'Times New Roman'" << QString("Times New Roman"); + QTest::newRow("unquoted-family-name") << "font-family: Times New Roman" << QString("Times New Roman"); + QTest::newRow("unquoted-family-name2") << "font-family: Times New Roman" << QString("Times New Roman"); + QTest::newRow("multiple") << "font-family: Times New Roman , foobar, 'baz'" << QString("Times New Roman"); + QTest::newRow("multiple2") << "font-family: invalid, Times New Roman " << QString("Times New Roman"); + QTest::newRow("invalid") << "font-family: invalid" << QFontInfo(QFont("invalid font")).family(); + QTest::newRow("shorthand") << "font: 12pt Times New Roman" << QString("Times New Roman"); + QTest::newRow("shorthand multiple quote") << "font: 12pt invalid, \"Times New Roman\" " << QString("Times New Roman"); + QTest::newRow("shorthand multiple") << "font: 12pt invalid, Times New Roman " << QString("Times New Roman"); + QTest::newRow("invalid spaces") << "font-family: invalid spaces, Times New Roman " << QString("Times New Roman"); + QTest::newRow("invalid spaces quotes") << "font-family: 'invalid spaces', 'Times New Roman' " << QString("Times New Roman"); +} + + +void tst_QCssParser::extractFontFamily() +{ + QFETCH(QString, css); + css.prepend("dummy {"); + css.append("}"); + + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1); + QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? + sheet.styleRules.at(0) : *sheet.nameIndex.begin(); + + const QVector<QCss::Declaration> decls = rule.declarations; + QVERIFY(!decls.isEmpty()); + QCss::ValueExtractor extractor(decls); + + int adjustment = 0; + QFont fnt; + extractor.extractFont(&fnt, &adjustment); + QFontInfo info(fnt); + +#ifdef Q_WS_QPA + // Note, we have to QSKIP rather than QEXPECT_FAIL because font lookup is broken + // such that it may work or not work depending on the order in which fonts were + // loaded from disk + QSKIP("QTBUG-20986 may fail on qpa", SkipSingle); +#endif + + QTEST(info.family(), "expectedFamily"); +} + +void tst_QCssParser::extractBorder_data() +{ + QTest::addColumn<QString>("css"); + QTest::addColumn<int>("expectedTopWidth"); + QTest::addColumn<int>("expectedTopStyle"); + QTest::addColumn<QColor>("expectedTopColor"); + + QTest::newRow("all values") << "border: 2px solid green" << 2 << (int)QCss::BorderStyle_Solid << QColor("green"); + QTest::newRow("palette") << "border: 2px solid palette(highlight)" << 2 << (int)QCss::BorderStyle_Solid << qApp->palette().color(QPalette::Highlight); + QTest::newRow("just width") << "border: 2px" << 2 << (int)QCss::BorderStyle_None << QColor(); + QTest::newRow("just style") << "border: solid" << 0 << (int)QCss::BorderStyle_Solid << QColor(); + QTest::newRow("just color") << "border: green" << 0 << (int)QCss::BorderStyle_None << QColor("green"); + QTest::newRow("width+style") << "border: 2px solid" << 2 << (int)QCss::BorderStyle_Solid << QColor(); + QTest::newRow("style+color") << "border: solid green" << 0 << (int)QCss::BorderStyle_Solid << QColor("green"); + QTest::newRow("width+color") << "border: 3px green" << 3 << (int)QCss::BorderStyle_None << QColor("green"); + QTest::newRow("groove style") << "border: groove" << 0 << (int)QCss::BorderStyle_Groove << QColor(); + QTest::newRow("ridge style") << "border: ridge" << 0 << (int)QCss::BorderStyle_Ridge << QColor(); + QTest::newRow("double style") << "border: double" << 0 << (int)QCss::BorderStyle_Double << QColor(); + QTest::newRow("inset style") << "border: inset" << 0 << (int)QCss::BorderStyle_Inset << QColor(); + QTest::newRow("outset style") << "border: outset" << 0 << (int)QCss::BorderStyle_Outset << QColor(); + QTest::newRow("dashed style") << "border: dashed" << 0 << (int)QCss::BorderStyle_Dashed << QColor(); + QTest::newRow("dotted style") << "border: dotted" << 0 << (int)QCss::BorderStyle_Dotted << QColor(); + QTest::newRow("dot-dash style") << "border: dot-dash" << 0 << (int)QCss::BorderStyle_DotDash << QColor(); + QTest::newRow("dot-dot-dash style") << "border: dot-dot-dash" << 0 << (int)QCss::BorderStyle_DotDotDash << QColor(); + + QTest::newRow("top-width+color") << "border-top: 3px green" << 3 << (int)QCss::BorderStyle_None << QColor("green"); +} + +void tst_QCssParser::extractBorder() +{ + QFETCH(QString, css); + QFETCH(int, expectedTopWidth); + QFETCH(int, expectedTopStyle); + QFETCH(QColor, expectedTopColor); + + css.prepend("dummy {"); + css.append("}"); + + QCss::Parser parser(css); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1); + QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? + sheet.styleRules.at(0) : *sheet.nameIndex.begin(); + const QVector<QCss::Declaration> decls = rule.declarations; + QVERIFY(!decls.isEmpty()); + QCss::ValueExtractor extractor(decls); + + int widths[4]; + QBrush colors[4]; + QCss::BorderStyle styles[4]; + QSize radii[4]; + + extractor.extractBorder(widths, colors, styles, radii); + QVERIFY(widths[QCss::TopEdge] == expectedTopWidth); + QVERIFY(styles[QCss::TopEdge] == expectedTopStyle); + QVERIFY(colors[QCss::TopEdge] == expectedTopColor); + + //QTBUG-9674 : a second evaluation should give the same results + QVERIFY(extractor.extractBorder(widths, colors, styles, radii)); + QVERIFY(widths[QCss::TopEdge] == expectedTopWidth); + QVERIFY(styles[QCss::TopEdge] == expectedTopStyle); + QVERIFY(colors[QCss::TopEdge] == expectedTopColor); +} + +void tst_QCssParser::noTextDecoration() +{ + QCss::Parser parser("dummy { text-decoration: none; }"); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1); + QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? + sheet.styleRules.at(0) : *sheet.nameIndex.begin(); + const QVector<QCss::Declaration> decls = rule.declarations; + QVERIFY(!decls.isEmpty()); + QCss::ValueExtractor extractor(decls); + + int adjustment = 0; + QFont f; + f.setUnderline(true); + f.setOverline(true); + f.setStrikeOut(true); + QVERIFY(extractor.extractFont(&f, &adjustment)); + + QVERIFY(!f.underline()); + QVERIFY(!f.overline()); + QVERIFY(!f.strikeOut()); +} + +void tst_QCssParser::quotedAndUnquotedIdentifiers() +{ + QCss::Parser parser("foo { font-style: \"italic\"; font-weight: bold }"); + QCss::StyleSheet sheet; + QVERIFY(parser.parse(&sheet)); + + QCOMPARE(sheet.styleRules.count() + sheet.nameIndex.count(), 1); + QCss::StyleRule rule = (!sheet.styleRules.isEmpty()) ? + sheet.styleRules.at(0) : *sheet.nameIndex.begin(); + const QVector<QCss::Declaration> decls = rule.declarations; + QCOMPARE(decls.size(), 2); + + QCOMPARE(decls.at(0).d->values.first().type, QCss::Value::String); + QCOMPARE(decls.at(0).d->property, QLatin1String("font-style")); + QCOMPARE(decls.at(0).d->values.first().toString(), QLatin1String("italic")); + + QCOMPARE(decls.at(1).d->values.first().type, QCss::Value::KnownIdentifier); + QCOMPARE(decls.at(1).d->property, QLatin1String("font-weight")); + QCOMPARE(decls.at(1).d->values.first().toString(), QLatin1String("bold")); +} + +QTEST_MAIN(tst_QCssParser) +#include "tst_qcssparser.moc" + diff --git a/tests/auto/gui/text/qfont/.gitignore b/tests/auto/gui/text/qfont/.gitignore new file mode 100644 index 0000000000..61aa3df16c --- /dev/null +++ b/tests/auto/gui/text/qfont/.gitignore @@ -0,0 +1 @@ +tst_qfont diff --git a/tests/auto/gui/text/qfont/qfont.pro b/tests/auto/gui/text/qfont/qfont.pro new file mode 100644 index 0000000000..891cb0a093 --- /dev/null +++ b/tests/auto/gui/text/qfont/qfont.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +QT += widgets +SOURCES += tst_qfont.cpp + + diff --git a/tests/auto/gui/text/qfont/tst_qfont.cpp b/tests/auto/gui/text/qfont/tst_qfont.cpp new file mode 100644 index 0000000000..a564e71e19 --- /dev/null +++ b/tests/auto/gui/text/qfont/tst_qfont.cpp @@ -0,0 +1,629 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + + +#include <qfont.h> +#include <qfontdatabase.h> +#include <qfontinfo.h> +#include <qstringlist.h> +#include <qapplication.h> +#include <qwidget.h> +#include <qlist.h> + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QFont : public QObject +{ +Q_OBJECT + +public: + tst_QFont(); + virtual ~tst_QFont(); + +public slots: + void init(); + void cleanup(); +private slots: + void getSetCheck(); + void exactMatch(); + void compare(); + void resolve(); + void resetFont(); + void isCopyOf(); + void setFontRaw(); + void italicOblique(); + void insertAndRemoveSubstitutions(); + void serializeSpacing(); + void lastResortFont(); + void styleName(); +}; + +// Testing get/set functions +void tst_QFont::getSetCheck() +{ + QFont obj1; + // Style QFont::style() + // void QFont::setStyle(Style) + obj1.setStyle(QFont::Style(QFont::StyleNormal)); + QCOMPARE(QFont::Style(QFont::StyleNormal), obj1.style()); + obj1.setStyle(QFont::Style(QFont::StyleItalic)); + QCOMPARE(QFont::Style(QFont::StyleItalic), obj1.style()); + obj1.setStyle(QFont::Style(QFont::StyleOblique)); + QCOMPARE(QFont::Style(QFont::StyleOblique), obj1.style()); + + // StyleStrategy QFont::styleStrategy() + // void QFont::setStyleStrategy(StyleStrategy) + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferDefault)); + QCOMPARE(QFont::StyleStrategy(QFont::PreferDefault), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferBitmap)); + QCOMPARE(QFont::StyleStrategy(QFont::PreferBitmap), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferDevice)); + QCOMPARE(QFont::StyleStrategy(QFont::PreferDevice), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferOutline)); + QCOMPARE(QFont::StyleStrategy(QFont::PreferOutline), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::ForceOutline)); + QCOMPARE(QFont::StyleStrategy(QFont::ForceOutline), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferMatch)); + QCOMPARE(QFont::StyleStrategy(QFont::PreferMatch), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferQuality)); + QCOMPARE(QFont::StyleStrategy(QFont::PreferQuality), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::PreferAntialias)); + QCOMPARE(QFont::StyleStrategy(QFont::PreferAntialias), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::NoAntialias)); + QCOMPARE(QFont::StyleStrategy(QFont::NoAntialias), obj1.styleStrategy()); + obj1.setStyleStrategy(QFont::StyleStrategy(QFont::OpenGLCompatible)); + QCOMPARE(QFont::StyleStrategy(QFont::OpenGLCompatible), obj1.styleStrategy()); +} + +tst_QFont::tst_QFont() +{ +} + +tst_QFont::~tst_QFont() +{ + +} + +void tst_QFont::init() +{ +// TODO: Add initialization code here. +// This will be executed immediately before each test is run. +} + +void tst_QFont::cleanup() +{ +// TODO: Add cleanup code here. +// This will be executed immediately after each test is run. +} + +void tst_QFont::exactMatch() +{ + QFont font; + + // Check if a non-existing font hasn't an exact match + font = QFont( "BogusFont", 33 ); + QVERIFY( !font.exactMatch() ); + +#ifdef Q_WS_WIN + QSKIP("Exact matching on windows misses a lot because of the sample chars", SkipAll); + return; +#endif + +#ifdef Q_WS_X11 + QVERIFY(QFont("sans").exactMatch()); + QVERIFY(QFont("sans-serif").exactMatch()); + QVERIFY(QFont("serif").exactMatch()); + QVERIFY(QFont("monospace").exactMatch()); +#endif + + QSKIP("This test is bogus on Unix with support for font aliases in fontconfig", SkipAll); + return; + + QFontDatabase fdb; + + QList<QFontDatabase::WritingSystem> systems = fdb.writingSystems(); + for (int system = 0; system < systems.count(); ++system) { + QStringList families = fdb.families(systems[system]); + if (families.isEmpty()) + return; + + QStringList::ConstIterator f_it, f_end = families.end(); + for (f_it = families.begin(); f_it != f_end; ++f_it) { + const QString &family = *f_it; + if (family.contains('[')) + continue; + + QStringList styles = fdb.styles(family); + QVERIFY(!styles.isEmpty()); + QStringList::ConstIterator s_it, s_end = styles.end(); + for (s_it = styles.begin(); s_it != s_end; ++s_it) { + const QString &style = *s_it; + + if (fdb.isSmoothlyScalable(family, style)) { + // smoothly scalable font... don't need to load every pointsize + font = fdb.font(family, style, 12); + QFontInfo fontinfo(font); + + if (! fontinfo.exactMatch()) { + // Unfortunately, this can fail, since + // QFontDatabase does not fill in all font + // properties. Check to make sure that the + // test didn't fail for obvious reasons + + if (fontinfo.family().isEmpty() + && fontinfo.pointSize() == 0) { + // this is a box rendering engine... this can happen from + // time to time, especially on X11 with iso10646-1 or + // unknown font encodings + continue; + } + +#ifdef Q_WS_WIN32 + if (font.family().startsWith("MS ") || fontinfo.family().startsWith("MS ")) { + /* qDebug("Family matching skipped for MS-Alias font: %s, fontinfo: %s", + font.family().latin1(), fontinfo.family().latin1()); + */ + } else +#endif + { + if (!(font.family() == fontinfo.family() + || fontinfo.family().contains(font.family()) + || fontinfo.family().isEmpty())) { + qDebug("Test about to fail for font: %s, fontinfo: %s", + font.family().toLatin1().constData(), + fontinfo.family().toLatin1().constData()); + } + QVERIFY(font.family() == fontinfo.family() + || fontinfo.family().contains(font.family()) + || fontinfo.family().isEmpty()); + } + if (font.pointSize() != -1) { + QVERIFY(font.pointSize() == fontinfo.pointSize()); + } else { + QVERIFY(font.pixelSize() == fontinfo.pixelSize()); + } + QVERIFY(font.italic() == fontinfo.italic()); + if (font.weight() != fontinfo.weight()) { + qDebug("font is %s", font.toString().toLatin1().constData()); + } + QVERIFY(font.weight() == fontinfo.weight()); + } else { + font.setFixedPitch(!fontinfo.fixedPitch()); + QFontInfo fontinfo1(font); + QVERIFY( !fontinfo1.exactMatch() ); + + font.setFixedPitch(fontinfo.fixedPitch()); + QFontInfo fontinfo2(font); + QVERIFY( fontinfo2.exactMatch() ); + } + } +#if 0 + // ############## can only work if we have float point sizes in QFD + else { + QList<int> sizes = fdb.pointSizes(family, style); + QVERIFY(!sizes.isEmpty()); + QList<int>::ConstIterator z_it, z_end = sizes.end(); + for (z_it = sizes.begin(); z_it != z_end; ++z_it) { + const int size = *z_it; + + // Initialize the font, and check if it is an exact match + font = fdb.font(family, style, size); + QFontInfo fontinfo(font, (QFont::Script) script); + + if (! fontinfo.exactMatch()) { + // Unfortunately, this can fail, since + // QFontDatabase does not fill in all font + // properties. Check to make sure that the + // test didn't fail for obvious reasons + + if (fontinfo.family().isEmpty() + && fontinfo.pointSize() == 0) { + // this is a box rendering engine... this can happen from + // time to time, especially on X11 with iso10646-1 or + // unknown font encodings + continue; + } + + // no need to skip MS-fonts here it seems + if (!(font.family() == fontinfo.family() + || fontinfo.family().contains(font.family()) + || fontinfo.family().isEmpty())) { + qDebug("Test about to fail for font: %s, fontinfo: %s", + font.family().latin1(), fontinfo.family().latin1()); + } + QVERIFY(font.family() == fontinfo.family() + || fontinfo.family().contains(font.family()) + || fontinfo.family().isEmpty()); + if (font.pointSize() != -1) { + QVERIFY(font.pointSize() == fontinfo.pointSize()); + } else { + QVERIFY(font.pixelSize() == fontinfo.pixelSize()); + } + QVERIFY(font.italic() == fontinfo.italic()); + QVERIFY(font.weight() == fontinfo.weight()); + } else { + font.setFixedPitch(!fontinfo.fixedPitch()); + QFontInfo fontinfo1(font, (QFont::Script) script); + QVERIFY( !fontinfo1.exactMatch() ); + + font.setFixedPitch(fontinfo.fixedPitch()); + QFontInfo fontinfo2(font, (QFont::Script) script); + QVERIFY( fontinfo2.exactMatch() ); + } + } + } +#endif + } + } + } +} + +void tst_QFont::italicOblique() +{ + QFontDatabase fdb; + + QStringList families = fdb.families(); + if (families.isEmpty()) + return; + + QStringList::ConstIterator f_it, f_end = families.end(); + for (f_it = families.begin(); f_it != f_end; ++f_it) { + + QString family = *f_it; + QStringList styles = fdb.styles(family); + QVERIFY(!styles.isEmpty()); + QStringList::ConstIterator s_it, s_end = styles.end(); + for (s_it = styles.begin(); s_it != s_end; ++s_it) { + QString style = *s_it; + + if (fdb.isSmoothlyScalable(family, style)) { + if (style.contains("Oblique")) { + style.replace("Oblique", "Italic"); + } else if (style.contains("Italic")) { + style.replace("Italic", "Oblique"); + } else { + continue; + } + QFont f = fdb.font(family, style, 12); + QVERIFY(f.italic()); + } + } + } +} + +void tst_QFont::compare() +{ + QFont font; + { + QFont font2 = font; + font2.setPointSize( 24 ); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + } + { + QFont font2 = font; + font2.setPixelSize( 24 ); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + } + + font.setPointSize(12); + font.setItalic(false); + font.setWeight(QFont::Normal); + font.setUnderline(false); + font.setStrikeOut(false); + font.setOverline(false); + { + QFont font2 = font; + font2.setPointSize( 24 ); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + } + { + QFont font2 = font; + font2.setPixelSize( 24 ); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + } + { + QFont font2 = font; + + font2.setItalic(true); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + font2.setItalic(false); + QVERIFY( font == font2 ); + QVERIFY(!(font < font2)); + + font2.setWeight(QFont::Bold); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + font2.setWeight(QFont::Normal); + QVERIFY( font == font2 ); + QVERIFY(!(font < font2)); + + font.setUnderline(true); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + font.setUnderline(false); + QVERIFY( font == font2 ); + QVERIFY(!(font < font2)); + + font.setStrikeOut(true); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + font.setStrikeOut(false); + QVERIFY( font == font2 ); + QVERIFY(!(font < font2)); + + font.setOverline(true); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + font.setOverline(false); + QVERIFY( font == font2 ); + QVERIFY(!(font < font2)); + + font.setCapitalization(QFont::SmallCaps); + QVERIFY( font != font2 ); + QCOMPARE(font < font2,!(font2 < font)); + font.setCapitalization(QFont::MixedCase); + QVERIFY( font == font2 ); + QVERIFY(!(font < font2)); + } + +#if defined(Q_WS_X11) + { + QFont font1, font2; + font1.setRawName("-Adobe-Helvetica-medium-r-normal--12-120-75-75-p-67-iso8859-1"); + font2.setRawName("-Adobe-Helvetica-medium-r-normal--24-240-75-75-p-130-iso8859-1"); + QVERIFY(font1 != font2); + } +#endif +} + +void tst_QFont::resolve() +{ + QFont font; + font.setPointSize(font.pointSize() * 2); + font.setItalic(false); + font.setWeight(QFont::Normal); + font.setUnderline(false); + font.setStrikeOut(false); + font.setOverline(false); + font.setStretch(QFont::Unstretched); + + QFont font1; + font1.setWeight(QFont::Bold); + QFont font2 = font1.resolve(font); + + QVERIFY(font2.weight() == font1.weight()); + + QVERIFY(font2.pointSize() == font.pointSize()); + QVERIFY(font2.italic() == font.italic()); + QVERIFY(font2.underline() == font.underline()); + QVERIFY(font2.overline() == font.overline()); + QVERIFY(font2.strikeOut() == font.strikeOut()); + QVERIFY(font2.stretch() == font.stretch()); + + QFont font3; + font3.setStretch(QFont::UltraCondensed); + QFont font4 = font3.resolve(font1).resolve(font); + + QVERIFY(font4.stretch() == font3.stretch()); + + QVERIFY(font4.weight() == font.weight()); + QVERIFY(font4.pointSize() == font.pointSize()); + QVERIFY(font4.italic() == font.italic()); + QVERIFY(font4.underline() == font.underline()); + QVERIFY(font4.overline() == font.overline()); + QVERIFY(font4.strikeOut() == font.strikeOut()); + + + QFont f1,f2,f3; + f2.setPointSize(45); + f3.setPointSize(55); + + QFont f4 = f1.resolve(f2); + QCOMPARE(f4.pointSize(), 45); + f4 = f4.resolve(f3); + QCOMPARE(f4.pointSize(), 55); +} + +void tst_QFont::resetFont() +{ + QWidget parent; + QFont parentFont = parent.font(); + parentFont.setPointSize(parentFont.pointSize() + 2); + parent.setFont(parentFont); + + QWidget *child = new QWidget(&parent); + + QFont childFont = child->font(); + childFont.setBold(!childFont.bold()); + child->setFont(childFont); + + QVERIFY(parentFont.resolve() != 0); + QVERIFY(childFont.resolve() != 0); + QVERIFY(childFont != parentFont); + + child->setFont(QFont()); // reset font + + QVERIFY(child->font().resolve() == 0); + QVERIFY(child->font().pointSize() == parent.font().pointSize()); + QVERIFY(parent.font().resolve() != 0); +} + +void tst_QFont::isCopyOf() +{ + QFont font; + QVERIFY(font.isCopyOf(QApplication::font())); + + QFont font2("bogusfont", 23); + QVERIFY(! font2.isCopyOf(QApplication::font())); + + QFont font3 = font; + QVERIFY(font3.isCopyOf(font)); + + font3.setPointSize(256); + QVERIFY(!font3.isCopyOf(font)); + font3.setPointSize(font.pointSize()); + QVERIFY(!font3.isCopyOf(font)); +} + +void tst_QFont::setFontRaw() +{ +#ifndef Q_WS_X11 + QSKIP("Only tested on X11", SkipAll); +#else + QFont f; + f.setRawName("-*-fixed-bold-r-normal--0-0-*-*-*-0-iso8859-1"); +// qDebug("font family: %s", f.family().utf8()); + QFontDatabase fdb; + QStringList families = fdb.families(); + bool found = false; + for (int i = 0; i < families.size(); ++i) { + QString str = families.at(i); + if (str.contains('[')) + str = str.left(str.indexOf('[')-1); + if (str.toLower() == "fixed") + found = true; + } + if (!found) { + QSKIP("Fixed font not available.", SkipSingle); + } + QCOMPARE(QFontInfo(f).family().left(5).toLower(), QString("fixed")); +#endif +} + +void tst_QFont::insertAndRemoveSubstitutions() +{ + QFont::removeSubstitution("BogusFontFamily"); + // make sure it is empty before we start + QVERIFY(QFont::substitutes("BogusFontFamily").isEmpty()); + QVERIFY(QFont::substitutes("bogusfontfamily").isEmpty()); + + // inserting Foo + QFont::insertSubstitution("BogusFontFamily", "Foo"); + QCOMPARE(QFont::substitutes("BogusFontFamily").count(), 1); + QCOMPARE(QFont::substitutes("bogusfontfamily").count(), 1); + + // inserting Bar and Baz + QStringList moreFonts; + moreFonts << "Bar" << "Baz"; + QFont::insertSubstitutions("BogusFontFamily", moreFonts); + QCOMPARE(QFont::substitutes("BogusFontFamily").count(), 3); + QCOMPARE(QFont::substitutes("bogusfontfamily").count(), 3); + + QFont::removeSubstitution("BogusFontFamily"); + // make sure it is empty again + QVERIFY(QFont::substitutes("BogusFontFamily").isEmpty()); + QVERIFY(QFont::substitutes("bogusfontfamily").isEmpty()); +} + + +static QFont copyFont(const QFont &font1) // copy using a QDataStream +{ + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + QDataStream ds(&buffer); + ds << font1; + buffer.close(); + + buffer.open(QIODevice::ReadOnly); + QFont font2; + ds >> font2; + return font2; +} + +void tst_QFont::serializeSpacing() +{ + QFont font; + QCOMPARE(font.letterSpacing(), 0.); + QCOMPARE(font.wordSpacing(), 0.); + + font.setLetterSpacing(QFont::AbsoluteSpacing, 105); + QCOMPARE(font.letterSpacing(), 105.); + QCOMPARE(font.letterSpacingType(), QFont::AbsoluteSpacing); + QCOMPARE(font.wordSpacing(), 0.); + QFont font2 = copyFont(font); + QCOMPARE(font2.letterSpacing(), 105.); + QCOMPARE(font2.letterSpacingType(), QFont::AbsoluteSpacing); + QCOMPARE(font2.wordSpacing(), 0.); + + font.setWordSpacing(50.0); + QCOMPARE(font.letterSpacing(), 105.); + QCOMPARE(font.wordSpacing(), 50.); + + QFont font3 = copyFont(font); + QCOMPARE(font3.letterSpacing(), 105.); + QCOMPARE(font3.letterSpacingType(), QFont::AbsoluteSpacing); + QCOMPARE(font3.wordSpacing(), 50.); +} + +void tst_QFont::lastResortFont() +{ +#if defined(Q_WS_QWS) || defined(Q_WS_QPA) + QSKIP("QFont::lastResortFont() may abort with qFatal() on QWS/QPA", SkipAll); + // ...if absolutely no font is found. Just as ducumented for QFont::lastResortFont(). + // This happens on our CI machines which run QWS autotests. +#endif + QFont font; + QVERIFY(!font.lastResortFont().isEmpty()); +} + +void tst_QFont::styleName() +{ +#if !defined(Q_WS_MAC) + QSKIP("Only tested on Mac", SkipAll); +#else + QFont font("Helvetica Neue"); + font.setStyleName("UltraLight"); + + QCOMPARE(QFontInfo(font).styleName(), QString("UltraLight")); +#endif +} + +QTEST_MAIN(tst_QFont) +#include "tst_qfont.moc" diff --git a/tests/auto/gui/text/qfontdatabase/.gitignore b/tests/auto/gui/text/qfontdatabase/.gitignore new file mode 100644 index 0000000000..65edf9b279 --- /dev/null +++ b/tests/auto/gui/text/qfontdatabase/.gitignore @@ -0,0 +1 @@ +tst_qfontdatabase diff --git a/tests/auto/gui/text/qfontdatabase/FreeMono.ttf b/tests/auto/gui/text/qfontdatabase/FreeMono.ttf Binary files differnew file mode 100644 index 0000000000..d7ce52ddc7 --- /dev/null +++ b/tests/auto/gui/text/qfontdatabase/FreeMono.ttf diff --git a/tests/auto/gui/text/qfontdatabase/qfontdatabase.pro b/tests/auto/gui/text/qfontdatabase/qfontdatabase.pro new file mode 100644 index 0000000000..e7dfc3c73d --- /dev/null +++ b/tests/auto/gui/text/qfontdatabase/qfontdatabase.pro @@ -0,0 +1,10 @@ +load(qttest_p4) +SOURCES += tst_qfontdatabase.cpp +!symbian:DEFINES += SRCDIR=\\\"$$PWD\\\" + +wince*|symbian { + additionalFiles.files = FreeMono.ttf + additionalFiles.path = . + DEPLOYMENT += additionalFiles +} + diff --git a/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp b/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp new file mode 100644 index 0000000000..1df61d0a06 --- /dev/null +++ b/tests/auto/gui/text/qfontdatabase/tst_qfontdatabase.cpp @@ -0,0 +1,294 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + + +#include <qfontdatabase.h> + +#ifdef Q_OS_SYMBIAN +#define SRCDIR "." +#endif + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QFontDatabase : public QObject +{ +Q_OBJECT + +public: + tst_QFontDatabase(); + virtual ~tst_QFontDatabase(); + +public slots: + void init(); + void cleanup(); +private slots: + void styles_data(); + void styles(); + + void fixedPitch_data(); + void fixedPitch(); + +#ifdef Q_WS_MAC + void trickyFonts_data(); + void trickyFonts(); +#endif + + void widthTwoTimes_data(); + void widthTwoTimes(); + + void addAppFont_data(); + void addAppFont(); +}; + +tst_QFontDatabase::tst_QFontDatabase() +{ +#ifndef Q_OS_IRIX + QDir::setCurrent(SRCDIR); +#endif +} + +tst_QFontDatabase::~tst_QFontDatabase() +{ + +} + +void tst_QFontDatabase::init() +{ +// TODO: Add initialization code here. +// This will be executed immediately before each test is run. +} + +void tst_QFontDatabase::cleanup() +{ +// TODO: Add cleanup code here. +// This will be executed immediately after each test is run. +} + +void tst_QFontDatabase::styles_data() +{ + QTest::addColumn<QString>("font"); + + QTest::newRow( "data0" ) << QString( "Times New Roman" ); +} + +void tst_QFontDatabase::styles() +{ + QFETCH( QString, font ); + + QFontDatabase fdb; + QStringList styles = fdb.styles( font ); + QStringList::Iterator it = styles.begin(); + while ( it != styles.end() ) { + QString style = *it; + QString trimmed = style.trimmed(); + ++it; + + QCOMPARE( style, trimmed ); + } +} + +void tst_QFontDatabase::fixedPitch_data() +{ + QTest::addColumn<QString>("font"); + QTest::addColumn<bool>("fixedPitch"); + + QTest::newRow( "Times New Roman" ) << QString( "Times New Roman" ) << false; + QTest::newRow( "Arial" ) << QString( "Arial" ) << false; + QTest::newRow( "Andale Mono" ) << QString( "Andale Mono" ) << true; + QTest::newRow( "Courier" ) << QString( "Courier" ) << true; + QTest::newRow( "Courier New" ) << QString( "Courier New" ) << true; +#ifndef Q_WS_MAC + QTest::newRow( "Script" ) << QString( "Script" ) << false; + QTest::newRow( "Lucida Console" ) << QString( "Lucida Console" ) << true; +#else + QTest::newRow( "Menlo" ) << QString( "Menlo" ) << true; + QTest::newRow( "Monaco" ) << QString( "Monaco" ) << true; +#endif +} + +void tst_QFontDatabase::fixedPitch() +{ +#ifdef Q_WS_QWS + QSKIP("fixedPitch not implemented for Qtopia Core", SkipAll); +#endif + QFETCH(QString, font); + QFETCH(bool, fixedPitch); + + QFontDatabase fdb; + if (!fdb.families().contains(font)) + QSKIP( "Font not installed", SkipSingle); + +#ifdef Q_WS_QPA + if (fixedPitch) { + // fixedPitch() never returns true on qpa + QEXPECT_FAIL("", "QTBUG-20754 fails on qpa", Abort); + } +#endif + + QCOMPARE(fdb.isFixedPitch(font), fixedPitch); + + QFont qfont(font); + QFontInfo fi(qfont); + QCOMPARE(fi.fixedPitch(), fixedPitch); +} + +#ifdef Q_WS_MAC +void tst_QFontDatabase::trickyFonts_data() +{ + QTest::addColumn<QString>("font"); + + QTest::newRow( "Geeza Pro" ) << QString( "Geeza Pro" ); +} + +void tst_QFontDatabase::trickyFonts() +{ + QFETCH(QString, font); + + QFontDatabase fdb; + if (!fdb.families().contains(font)) + QSKIP( "Font not installed", SkipSingle); + + QFont qfont(font); + QFontInfo fi(qfont); + QCOMPARE(fi.family(), font); +} +#endif + +void tst_QFontDatabase::widthTwoTimes_data() +{ + QTest::addColumn<QString>("font"); + QTest::addColumn<int>("pixelSize"); + QTest::addColumn<QString>("text"); + + QTest::newRow("Arial") << QString("Arial") << 1000 << QString("Some text"); +} + +void tst_QFontDatabase::widthTwoTimes() +{ + QFETCH(QString, font); + QFETCH(int, pixelSize); + QFETCH(QString, text); + + QFont f; + f.setFamily(font); + f.setPixelSize(pixelSize); + + QFontMetrics fm(f); + int w1 = fm.charWidth(text, 0); + int w2 = fm.charWidth(text, 0); + + QCOMPARE(w1, w2); +} + +void tst_QFontDatabase::addAppFont_data() +{ + QTest::addColumn<bool>("useMemoryFont"); + QTest::newRow("font file") << false; + QTest::newRow("memory font") << true; +} + +void tst_QFontDatabase::addAppFont() +{ + QFETCH(bool, useMemoryFont); + QSignalSpy fontDbChangedSpy(QApplication::instance(), SIGNAL(fontDatabaseChanged())); + + QFontDatabase db; + +#ifdef Q_WS_QPA + QEXPECT_FAIL("memory font", "QTBUG-20754 fails on qpa", Abort); +#endif + + const QStringList oldFamilies = db.families(); + QVERIFY(!oldFamilies.isEmpty()); + + fontDbChangedSpy.clear(); + + int id; + if (useMemoryFont) { + QFile fontfile("FreeMono.ttf"); + fontfile.open(QIODevice::ReadOnly); + QByteArray fontdata = fontfile.readAll(); + QVERIFY(!fontdata.isEmpty()); + id = QFontDatabase::addApplicationFontFromData(fontdata); + } else { + id = QFontDatabase::addApplicationFont("FreeMono.ttf"); + } +#if defined(Q_OS_HPUX) && defined(QT_NO_FONTCONFIG) + // Documentation says that X11 systems that don't have fontconfig + // don't support application fonts. + QCOMPARE(id, -1); + return; +#endif + QCOMPARE(fontDbChangedSpy.count(), 1); +// addApplicationFont is supported on Mac, don't skip the test if it breaks. +#ifndef Q_WS_MAC + if (id == -1) { + QSKIP("Skip the test since app fonts are not supported on this system", SkipSingle); + return; + } +#endif + + const QStringList addedFamilies = QFontDatabase::applicationFontFamilies(id); + QVERIFY(!addedFamilies.isEmpty()); + +#ifdef Q_WS_QPA + QEXPECT_FAIL("font file", "QTBUG-20754 fails on qpa", Abort); +#endif + + const QStringList newFamilies = db.families(); + QVERIFY(!newFamilies.isEmpty()); + QVERIFY(newFamilies.count() >= oldFamilies.count()); + + for (int i = 0; i < addedFamilies.count(); ++i) + QVERIFY(newFamilies.contains(addedFamilies.at(i))); + + QVERIFY(QFontDatabase::removeApplicationFont(id)); + QCOMPARE(fontDbChangedSpy.count(), 2); + + QVERIFY(db.families() == oldFamilies); +} + +QTEST_MAIN(tst_QFontDatabase) +#include "tst_qfontdatabase.moc" diff --git a/tests/auto/gui/text/qfontmetrics/.gitignore b/tests/auto/gui/text/qfontmetrics/.gitignore new file mode 100644 index 0000000000..0b428672a3 --- /dev/null +++ b/tests/auto/gui/text/qfontmetrics/.gitignore @@ -0,0 +1 @@ +tst_qfontmetrics diff --git a/tests/auto/gui/text/qfontmetrics/qfontmetrics.pro b/tests/auto/gui/text/qfontmetrics/qfontmetrics.pro new file mode 100644 index 0000000000..c0dc1abbe6 --- /dev/null +++ b/tests/auto/gui/text/qfontmetrics/qfontmetrics.pro @@ -0,0 +1,4 @@ +load(qttest_p4) +SOURCES += tst_qfontmetrics.cpp +RESOURCES += testfont.qrc + diff --git a/tests/auto/gui/text/qfontmetrics/testfont.qrc b/tests/auto/gui/text/qfontmetrics/testfont.qrc new file mode 100644 index 0000000000..bc0c0b0959 --- /dev/null +++ b/tests/auto/gui/text/qfontmetrics/testfont.qrc @@ -0,0 +1,5 @@ +<RCC> + <qresource prefix="/fonts"> + <file>ucs4font.ttf</file> + </qresource> +</RCC> diff --git a/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp b/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp new file mode 100644 index 0000000000..982cfc11d9 --- /dev/null +++ b/tests/auto/gui/text/qfontmetrics/tst_qfontmetrics.cpp @@ -0,0 +1,323 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qfontdatabase.h> +#include <qstringlist.h> +#include <qlist.h> + + + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QFontMetrics : public QObject +{ +Q_OBJECT + +public: + tst_QFontMetrics(); + virtual ~tst_QFontMetrics(); + +public slots: + void init(); + void cleanup(); +private slots: + void same(); + void metrics(); + void boundingRect(); + void elidedText_data(); + void elidedText(); + void veryNarrowElidedText(); + void averageCharWidth(); + void bypassShaping(); + void elidedMultiLength(); + void elidedMultiLengthF(); + void inFontUcs4(); + void lineWidth(); +}; + +tst_QFontMetrics::tst_QFontMetrics() + +{ +} + +tst_QFontMetrics::~tst_QFontMetrics() +{ + +} + +void tst_QFontMetrics::init() +{ +} + +void tst_QFontMetrics::cleanup() +{ +} + +void tst_QFontMetrics::same() +{ + QFont font; + font.setBold(true); + QFontMetrics fm(font); + const QString text = QLatin1String("Some stupid STRING"); + QCOMPARE(fm.size(0, text), fm.size(0, text)) ; + + { + QImage image; + QFontMetrics fm2(font, &image); + QString text2 = QLatin1String("Foo Foo"); + QCOMPARE(fm2.size(0, text2), fm2.size(0, text2)); //used to crash + } + + { + QImage image; + QFontMetricsF fm3(font, &image); + QString text2 = QLatin1String("Foo Foo"); + QCOMPARE(fm3.size(0, text2), fm3.size(0, text2)); //used to crash + } +} + + +void tst_QFontMetrics::metrics() +{ + QFont font; + QFontDatabase fdb; + + // Query the QFontDatabase for a specific font, store the + // result in family, style and size. + QStringList families = fdb.families(); + if (families.isEmpty()) + return; + + QStringList::ConstIterator f_it, f_end = families.end(); + for (f_it = families.begin(); f_it != f_end; ++f_it) { + const QString &family = *f_it; + + QStringList styles = fdb.styles(family); + QStringList::ConstIterator s_it, s_end = styles.end(); + for (s_it = styles.begin(); s_it != s_end; ++s_it) { + const QString &style = *s_it; + + if (fdb.isSmoothlyScalable(family, style)) { + // smoothly scalable font... don't need to load every pointsize + font = fdb.font(family, style, 12); + + QFontMetrics fontmetrics(font); + QCOMPARE(fontmetrics.ascent() + fontmetrics.descent() + 1, + fontmetrics.height()); + + QCOMPARE(fontmetrics.height() + fontmetrics.leading(), + fontmetrics.lineSpacing()); + } else { + QList<int> sizes = fdb.pointSizes(family, style); + QVERIFY(!sizes.isEmpty()); + QList<int>::ConstIterator z_it, z_end = sizes.end(); + for (z_it = sizes.begin(); z_it != z_end; ++z_it) { + const int size = *z_it; + + // Initialize the font, and check if it is an exact match + font = fdb.font(family, style, size); + + QFontMetrics fontmetrics(font); + QCOMPARE(fontmetrics.ascent() + fontmetrics.descent() + 1, + fontmetrics.height()); + QCOMPARE(fontmetrics.height() + fontmetrics.leading(), + fontmetrics.lineSpacing()); + } + } + } + } +} + +void tst_QFontMetrics::boundingRect() +{ + QFont f; + f.setPointSize(24); + QFontMetrics fm(f); + QRect r = fm.boundingRect(QChar('Y')); + QVERIFY(r.top() < 0); + r = fm.boundingRect(QString("Y")); + QVERIFY(r.top() < 0); +} + +void tst_QFontMetrics::elidedText_data() +{ + QTest::addColumn<QFont>("font"); + QTest::addColumn<QString>("text"); + + QTest::newRow("helvetica hello") << QFont("helvetica",10) << QString("hello") ; + QTest::newRow("helvetica hello &Bye") << QFont("helvetica",10) << QString("hello&Bye") ; +} + + +void tst_QFontMetrics::elidedText() +{ + QFETCH(QFont, font); + QFETCH(QString, text); + QFontMetrics fm(font); + int w = fm.width(text); + QString newtext = fm.elidedText(text,Qt::ElideRight,w+1, 0); + QCOMPARE(text,newtext); // should not elide + newtext = fm.elidedText(text,Qt::ElideRight,w-1, 0); + QVERIFY(text != newtext); // should elide +} + +void tst_QFontMetrics::veryNarrowElidedText() +{ + QFont f; + QFontMetrics fm(f); + QString text("hello"); + QCOMPARE(fm.elidedText(text, Qt::ElideRight, 0), QString()); +} + +void tst_QFontMetrics::averageCharWidth() +{ + QFont f; + QFontMetrics fm(f); + QVERIFY(fm.averageCharWidth() != 0); + QFontMetricsF fmf(f); + QVERIFY(fmf.averageCharWidth() != 0); +} + +void tst_QFontMetrics::bypassShaping() +{ + QFont f; + f.setStyleStrategy(QFont::ForceIntegerMetrics); + QFontMetrics fm(f); + QString text = " A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z"; + int textWidth = fm.width(text, -1, Qt::TextBypassShaping); + QVERIFY(textWidth != 0); + int charsWidth = 0; + for (int i = 0; i < text.size(); ++i) + charsWidth += fm.width(text[i]); + // This assertion is needed in QtWebKit's WebCore::Font::offsetForPositionForSimpleText + QCOMPARE(textWidth, charsWidth); +} + +template<class FontMetrics> void elidedMultiLength_helper() +{ + QString text1 = "Long Text 1\x9cShorter\x9csmall"; + QString text1_long = "Long Text 1"; + QString text1_short = "Shorter"; + QString text1_small = "small"; + FontMetrics fm = FontMetrics(QFont()); + int width_long = fm.size(0, text1_long).width(); + QCOMPARE(fm.elidedText(text1,Qt::ElideRight, 8000), text1_long); + QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_long + 1), text1_long); + QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_long - 1), text1_short); + int width_short = fm.size(0, text1_short).width(); + QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_short + 1), text1_short); + QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_short - 1), text1_small); + + // Not even wide enough for "small" - should use ellipsis + QChar ellipsisChar(0x2026); + QString text1_el = QString::fromLatin1("s") + ellipsisChar; + int width_small = fm.width(text1_el); + QCOMPARE(fm.elidedText(text1,Qt::ElideRight, width_small + 1), text1_el); +} + +void tst_QFontMetrics::elidedMultiLength() +{ + elidedMultiLength_helper<QFontMetrics>(); +} + +void tst_QFontMetrics::elidedMultiLengthF() +{ + elidedMultiLength_helper<QFontMetricsF>(); +} + +void tst_QFontMetrics::inFontUcs4() +{ + int id = QFontDatabase::addApplicationFont(":/fonts/ucs4font.ttf"); + QVERIFY(id >= 0); + + QFont font("QtTestUcs4"); + { + QFontMetrics fm(font); + +#ifdef Q_WS_QPA + QEXPECT_FAIL("", "QTBUG-20759 fails for qpa", Continue); +#endif + + QVERIFY(fm.inFontUcs4(0x1D7FF)); + } + + { + QFontMetricsF fm(font); + +#ifdef Q_WS_QPA + QEXPECT_FAIL("", "QTBUG-20759 fails for qpa", Continue); +#endif + + QVERIFY(fm.inFontUcs4(0x1D7FF)); + } + + QFontDatabase::removeApplicationFont(id); +} + +void tst_QFontMetrics::lineWidth() +{ + // QTBUG-13009, QTBUG-13011 + QFont smallFont; + smallFont.setPointSize(8); + smallFont.setWeight(QFont::Light); + const QFontMetrics smallFontMetrics(smallFont); + + QFont bigFont; + bigFont.setPointSize(40); + bigFont.setWeight(QFont::Black); + const QFontMetrics bigFontMetrics(bigFont); + + QVERIFY(smallFontMetrics.lineWidth() >= 1); + +#ifdef Q_WS_QPA + QEXPECT_FAIL("", "QTBUG-20759 fails for qpa", Continue); +#endif + + QVERIFY(smallFontMetrics.lineWidth() < bigFontMetrics.lineWidth()); +} + +QTEST_MAIN(tst_QFontMetrics) +#include "tst_qfontmetrics.moc" diff --git a/tests/auto/gui/text/qfontmetrics/ucs4font.ttf b/tests/auto/gui/text/qfontmetrics/ucs4font.ttf Binary files differnew file mode 100644 index 0000000000..31b6997779 --- /dev/null +++ b/tests/auto/gui/text/qfontmetrics/ucs4font.ttf diff --git a/tests/auto/gui/text/qglyphrun/qglyphrun.pro b/tests/auto/gui/text/qglyphrun/qglyphrun.pro new file mode 100644 index 0000000000..480ad5b9a4 --- /dev/null +++ b/tests/auto/gui/text/qglyphrun/qglyphrun.pro @@ -0,0 +1,11 @@ +load(qttest_p4) +QT = core gui + +SOURCES += \ + tst_qglyphrun.cpp + +wince*|symbian*: { + DEFINES += SRCDIR=\\\"\\\" +} else { + DEFINES += SRCDIR=\\\"$$PWD/\\\" +} diff --git a/tests/auto/gui/text/qglyphrun/test.ttf b/tests/auto/gui/text/qglyphrun/test.ttf Binary files differnew file mode 100644 index 0000000000..9043a576ef --- /dev/null +++ b/tests/auto/gui/text/qglyphrun/test.ttf diff --git a/tests/auto/gui/text/qglyphrun/tst_qglyphrun.cpp b/tests/auto/gui/text/qglyphrun/tst_qglyphrun.cpp new file mode 100644 index 0000000000..e84915428f --- /dev/null +++ b/tests/auto/gui/text/qglyphrun/tst_qglyphrun.cpp @@ -0,0 +1,679 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <qglyphrun.h> +#include <qpainter.h> +#include <qtextlayout.h> +#include <qfontdatabase.h> + +// #define DEBUG_SAVE_IMAGE + +class tst_QGlyphRun: public QObject +{ + Q_OBJECT + +#if !defined(QT_NO_RAWFONT) +private slots: + void initTestCase(); + void init(); + void cleanupTestCase(); + + void constructionAndDestruction(); + void copyConstructor(); + void assignment(); + void equalsOperator_data(); + void equalsOperator(); + void textLayoutGlyphIndexes(); + void drawExistingGlyphs(); + void drawNonExistentGlyphs(); + void drawMultiScriptText1(); + void drawMultiScriptText2(); + void drawStruckOutText(); + void drawOverlinedText(); + void drawUnderlinedText(); + void drawRightToLeft(); + void detach(); + void setRawData(); + void setRawDataAndGetAsVector(); + +private: + int m_testFontId; + QFont m_testFont; + bool m_testFont_ok; +#endif // QT_NO_RAWFONT + +}; + +#if !defined(QT_NO_RAWFONT) + +Q_DECLARE_METATYPE(QGlyphRun); + +void tst_QGlyphRun::initTestCase() +{ + m_testFont_ok = false; + + m_testFontId = QFontDatabase::addApplicationFont(SRCDIR "test.ttf"); + QVERIFY(m_testFontId >= 0); + + m_testFont = QFont("QtsSpecialTestFont"); + +#ifdef Q_WS_QPA + QEXPECT_FAIL("", "QTBUG-20760 fails on qpa", Abort); +#endif + + QCOMPARE(QFontInfo(m_testFont).family(), QString::fromLatin1("QtsSpecialTestFont")); + + m_testFont_ok = true; +} + +void tst_QGlyphRun::init() +{ + if (!m_testFont_ok) { + QSKIP("Test font is not working correctly", SkipAll); + } +} + +void tst_QGlyphRun::cleanupTestCase() +{ + QFontDatabase::removeApplicationFont(m_testFontId); +} + +void tst_QGlyphRun::constructionAndDestruction() +{ + QGlyphRun glyphIndexes; +} + +static QGlyphRun make_dummy_indexes() +{ + QGlyphRun glyphs; + + QVector<quint32> glyphIndexes; + QVector<QPointF> positions; + QFont font; + font.setPointSize(18); + + glyphIndexes.append(1); + glyphIndexes.append(2); + glyphIndexes.append(3); + + positions.append(QPointF(1, 2)); + positions.append(QPointF(3, 4)); + positions.append(QPointF(5, 6)); + + glyphs.setRawFont(QRawFont::fromFont(font)); + glyphs.setGlyphIndexes(glyphIndexes); + glyphs.setPositions(positions); + + return glyphs; +} + +void tst_QGlyphRun::copyConstructor() +{ + QGlyphRun glyphs; + + { + QVector<quint32> glyphIndexes; + QVector<QPointF> positions; + QFont font; + font.setPointSize(18); + + glyphIndexes.append(1); + glyphIndexes.append(2); + glyphIndexes.append(3); + + positions.append(QPointF(1, 2)); + positions.append(QPointF(3, 4)); + positions.append(QPointF(5, 6)); + + glyphs.setRawFont(QRawFont::fromFont(font)); + glyphs.setGlyphIndexes(glyphIndexes); + glyphs.setPositions(positions); + } + + QGlyphRun otherGlyphs(glyphs); + QCOMPARE(otherGlyphs.rawFont(), glyphs.rawFont()); + QCOMPARE(glyphs.glyphIndexes(), otherGlyphs.glyphIndexes()); + QCOMPARE(glyphs.positions(), otherGlyphs.positions()); +} + +void tst_QGlyphRun::assignment() +{ + QGlyphRun glyphs(make_dummy_indexes()); + + QGlyphRun otherGlyphs = glyphs; + QCOMPARE(otherGlyphs.rawFont(), glyphs.rawFont()); + QCOMPARE(glyphs.glyphIndexes(), otherGlyphs.glyphIndexes()); + QCOMPARE(glyphs.positions(), otherGlyphs.positions()); +} + +void tst_QGlyphRun::equalsOperator_data() +{ + QTest::addColumn<QGlyphRun>("one"); + QTest::addColumn<QGlyphRun>("two"); + QTest::addColumn<bool>("equals"); + + QGlyphRun one(make_dummy_indexes()); + QGlyphRun two(make_dummy_indexes()); + + QTest::newRow("Identical") << one << two << true; + + { + QGlyphRun busted(two); + + QVector<QPointF> positions = busted.positions(); + positions[2] += QPointF(1, 1); + busted.setPositions(positions); + + + QTest::newRow("Different positions") << one << busted << false; + } + + { + QGlyphRun busted(two); + + QFont font; + font.setPixelSize(busted.rawFont().pixelSize() * 2); + busted.setRawFont(QRawFont::fromFont(font)); + + QTest::newRow("Different fonts") << one << busted << false; + } + + { + QGlyphRun busted(two); + + QVector<quint32> glyphIndexes = busted.glyphIndexes(); + glyphIndexes[2] += 1; + busted.setGlyphIndexes(glyphIndexes); + + QTest::newRow("Different glyph indexes") << one << busted << false; + } + +} + +void tst_QGlyphRun::equalsOperator() +{ + QFETCH(QGlyphRun, one); + QFETCH(QGlyphRun, two); + QFETCH(bool, equals); + + QCOMPARE(one == two, equals); + QCOMPARE(one != two, !equals); +} + + +void tst_QGlyphRun::textLayoutGlyphIndexes() +{ + QString s; + s.append(QLatin1Char('A')); + s.append(QChar(0xe000)); + + QTextLayout layout(s); + layout.setFont(m_testFont); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList<QGlyphRun> listOfGlyphs = layout.glyphRuns(); + + QCOMPARE(listOfGlyphs.size(), 1); + + QGlyphRun glyphs = listOfGlyphs.at(0); + + QCOMPARE(glyphs.glyphIndexes().size(), 2); + QCOMPARE(glyphs.glyphIndexes().at(0), quint32(2)); + QCOMPARE(glyphs.glyphIndexes().at(1), quint32(1)); +} + +void tst_QGlyphRun::drawExistingGlyphs() +{ + QPixmap textLayoutDraw(1000, 1000); + QPixmap drawGlyphs(1000, 1000); + + textLayoutDraw.fill(Qt::white); + drawGlyphs.fill(Qt::white); + + QString s; + s.append(QLatin1Char('A')); + s.append(QChar(0xe000)); + + QTextLayout layout(s); + layout.setFont(m_testFont); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + { + QPainter p(&textLayoutDraw); + layout.draw(&p, QPointF(50, 50)); + } + + QGlyphRun glyphs = layout.glyphRuns().size() > 0 + ? layout.glyphRuns().at(0) + : QGlyphRun(); + + { + QPainter p(&drawGlyphs); + p.drawGlyphRun(QPointF(50, 50), glyphs); + } + +#if defined(DEBUG_SAVE_IMAGE) + textLayoutDraw.save("drawExistingGlyphs_textLayoutDraw.png"); + drawGlyphs.save("drawExistingGlyphs_drawGlyphIndexes.png"); +#endif + + QCOMPARE(textLayoutDraw, drawGlyphs); +} + +void tst_QGlyphRun::setRawData() +{ + QGlyphRun glyphRun; + glyphRun.setRawFont(QRawFont::fromFont(m_testFont)); + glyphRun.setGlyphIndexes(QVector<quint32>() << 2 << 2 << 2); + glyphRun.setPositions(QVector<QPointF>() << QPointF(2, 3) << QPointF(20, 3) << QPointF(10, 20)); + + QPixmap baseline(100, 50); + baseline.fill(Qt::white); + { + QPainter p(&baseline); + p.drawGlyphRun(QPointF(3, 2), glyphRun); + } + + QGlyphRun baselineCopied = glyphRun; + + quint32 glyphIndexArray[3] = { 2, 2, 2 }; + QPointF glyphPositionArray[3] = { QPointF(2, 3), QPointF(20, 3), QPointF(10, 20) }; + + glyphRun.setRawData(glyphIndexArray, glyphPositionArray, 3); + + QPixmap rawDataGlyphs(100, 50); + rawDataGlyphs.fill(Qt::white); + { + QPainter p(&rawDataGlyphs); + p.drawGlyphRun(QPointF(3, 2), glyphRun); + } + + quint32 otherGlyphIndexArray[1] = { 2 }; + QPointF otherGlyphPositionArray[1] = { QPointF(2, 3) }; + + glyphRun.setRawData(otherGlyphIndexArray, otherGlyphPositionArray, 1); + + QPixmap baselineCopiedPixmap(100, 50); + baselineCopiedPixmap.fill(Qt::white); + { + QPainter p(&baselineCopiedPixmap); + p.drawGlyphRun(QPointF(3, 2), baselineCopied); + } + +#if defined(DEBUG_SAVE_IMAGE) + baseline.save("setRawData_baseline.png"); + rawDataGlyphs.save("setRawData_rawDataGlyphs.png"); + baselineCopiedPixmap.save("setRawData_baselineCopiedPixmap.png"); +#endif + + QCOMPARE(rawDataGlyphs, baseline); + QCOMPARE(baselineCopiedPixmap, baseline); +} + +void tst_QGlyphRun::setRawDataAndGetAsVector() +{ + QVector<quint32> glyphIndexArray; + glyphIndexArray << 3 << 2 << 1 << 4; + + QVector<QPointF> glyphPositionArray; + glyphPositionArray << QPointF(1, 2) << QPointF(3, 4) << QPointF(5, 6) << QPointF(7, 8); + + QGlyphRun glyphRun; + glyphRun.setRawData(glyphIndexArray.constData(), glyphPositionArray.constData(), 4); + + QVector<quint32> glyphIndexes = glyphRun.glyphIndexes(); + QVector<QPointF> glyphPositions = glyphRun.positions(); + + QCOMPARE(glyphIndexes.size(), 4); + QCOMPARE(glyphPositions.size(), 4); + + QCOMPARE(glyphIndexes, glyphIndexArray); + QCOMPARE(glyphPositions, glyphPositionArray); + + QGlyphRun otherGlyphRun; + otherGlyphRun.setGlyphIndexes(glyphIndexArray); + otherGlyphRun.setPositions(glyphPositionArray); + + QCOMPARE(glyphRun, otherGlyphRun); +} + +void tst_QGlyphRun::drawNonExistentGlyphs() +{ + QVector<quint32> glyphIndexes; + glyphIndexes.append(3); + + QVector<QPointF> glyphPositions; + glyphPositions.append(QPointF(0, 0)); + + QGlyphRun glyphs; + glyphs.setGlyphIndexes(glyphIndexes); + glyphs.setPositions(glyphPositions); + glyphs.setRawFont(QRawFont::fromFont(m_testFont)); + + QPixmap image(1000, 1000); + image.fill(Qt::white); + + QPixmap imageBefore = image; + { + QPainter p(&image); + p.drawGlyphRun(QPointF(50, 50), glyphs); + } + +#if defined(DEBUG_SAVE_IMAGE) + image.save("drawNonExistentGlyphs.png"); +#endif + + QCOMPARE(image, imageBefore); // Should be unchanged +} + +void tst_QGlyphRun::drawMultiScriptText1() +{ + QString text; + text += QChar(0x03D0); // Greek, beta + + QTextLayout textLayout(text); + textLayout.beginLayout(); + textLayout.createLine(); + textLayout.endLayout(); + + QPixmap textLayoutDraw(1000, 1000); + textLayoutDraw.fill(Qt::white); + + QPixmap drawGlyphs(1000, 1000); + drawGlyphs.fill(Qt::white); + + QList<QGlyphRun> glyphsList = textLayout.glyphRuns(); + QCOMPARE(glyphsList.size(), 1); + + { + QPainter p(&textLayoutDraw); + textLayout.draw(&p, QPointF(50, 50)); + } + + { + QPainter p(&drawGlyphs); + foreach (QGlyphRun glyphs, glyphsList) + p.drawGlyphRun(QPointF(50, 50), glyphs); + } + +#if defined(DEBUG_SAVE_IMAGE) + textLayoutDraw.save("drawMultiScriptText1_textLayoutDraw.png"); + drawGlyphs.save("drawMultiScriptText1_drawGlyphIndexes.png"); +#endif + + QCOMPARE(drawGlyphs, textLayoutDraw); +} + + +void tst_QGlyphRun::drawMultiScriptText2() +{ + QString text; + text += QChar(0x0621); // Arabic, Hamza + text += QChar(0x03D0); // Greek, beta + + QTextLayout textLayout(text); + textLayout.beginLayout(); + textLayout.createLine(); + textLayout.endLayout(); + + QPixmap textLayoutDraw(1000, 1000); + textLayoutDraw.fill(Qt::white); + + QPixmap drawGlyphs(1000, 1000); + drawGlyphs.fill(Qt::white); + + QList<QGlyphRun> glyphsList = textLayout.glyphRuns(); + QCOMPARE(glyphsList.size(), 2); + + { + QPainter p(&textLayoutDraw); + textLayout.draw(&p, QPointF(50, 50)); + } + + { + QPainter p(&drawGlyphs); + foreach (QGlyphRun glyphs, glyphsList) + p.drawGlyphRun(QPointF(50, 50), glyphs); + } + +#if defined(DEBUG_SAVE_IMAGE) + textLayoutDraw.save("drawMultiScriptText2_textLayoutDraw.png"); + drawGlyphs.save("drawMultiScriptText2_drawGlyphIndexes.png"); +#endif + + QCOMPARE(drawGlyphs, textLayoutDraw); +} + +void tst_QGlyphRun::detach() +{ + QGlyphRun glyphs; + + glyphs.setGlyphIndexes(QVector<quint32>() << 1 << 2 << 3); + + QGlyphRun otherGlyphs; + otherGlyphs = glyphs; + + QCOMPARE(otherGlyphs.glyphIndexes(), glyphs.glyphIndexes()); + + otherGlyphs.setGlyphIndexes(QVector<quint32>() << 4 << 5 << 6); + + QCOMPARE(otherGlyphs.glyphIndexes(), QVector<quint32>() << 4 << 5 << 6); + QCOMPARE(glyphs.glyphIndexes(), QVector<quint32>() << 1 << 2 << 3); +} + +void tst_QGlyphRun::drawStruckOutText() +{ + QPixmap textLayoutDraw(1000, 1000); + QPixmap drawGlyphs(1000, 1000); + + textLayoutDraw.fill(Qt::white); + drawGlyphs.fill(Qt::white); + + QString s = QString::fromLatin1("Foobar"); + + QFont font; + font.setStrikeOut(true); + + QTextLayout layout(s); + layout.setFont(font); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + { + QPainter p(&textLayoutDraw); + layout.draw(&p, QPointF(50, 50)); + } + + QGlyphRun glyphs = layout.glyphRuns().size() > 0 + ? layout.glyphRuns().at(0) + : QGlyphRun(); + + { + QPainter p(&drawGlyphs); + p.drawGlyphRun(QPointF(50, 50), glyphs); + } + +#if defined(DEBUG_SAVE_IMAGE) + textLayoutDraw.save("drawStruckOutText_textLayoutDraw.png"); + drawGlyphs.save("drawStruckOutText_drawGlyphIndexes.png"); +#endif + + QCOMPARE(textLayoutDraw, drawGlyphs); +} + +void tst_QGlyphRun::drawOverlinedText() +{ + QPixmap textLayoutDraw(1000, 1000); + QPixmap drawGlyphs(1000, 1000); + + textLayoutDraw.fill(Qt::white); + drawGlyphs.fill(Qt::white); + + QString s = QString::fromLatin1("Foobar"); + + QFont font; + font.setOverline(true); + + QTextLayout layout(s); + layout.setFont(font); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + { + QPainter p(&textLayoutDraw); + layout.draw(&p, QPointF(50, 50)); + } + + QGlyphRun glyphs = layout.glyphRuns().size() > 0 + ? layout.glyphRuns().at(0) + : QGlyphRun(); + + { + QPainter p(&drawGlyphs); + p.drawGlyphRun(QPointF(50, 50), glyphs); + } + +#if defined(DEBUG_SAVE_IMAGE) + textLayoutDraw.save("drawOverlineText_textLayoutDraw.png"); + drawGlyphs.save("drawOverlineText_drawGlyphIndexes.png"); +#endif + + QCOMPARE(textLayoutDraw, drawGlyphs); +} + +void tst_QGlyphRun::drawUnderlinedText() +{ + QPixmap textLayoutDraw(1000, 1000); + QPixmap drawGlyphs(1000, 1000); + + textLayoutDraw.fill(Qt::white); + drawGlyphs.fill(Qt::white); + + QString s = QString::fromLatin1("Foobar"); + + QFont font; + font.setUnderline(true); + + QTextLayout layout(s); + layout.setFont(font); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + { + QPainter p(&textLayoutDraw); + layout.draw(&p, QPointF(50, 50)); + } + + QGlyphRun glyphs = layout.glyphRuns().size() > 0 + ? layout.glyphRuns().at(0) + : QGlyphRun(); + + { + QPainter p(&drawGlyphs); + p.drawGlyphRun(QPointF(50, 50), glyphs); + } + +#if defined(DEBUG_SAVE_IMAGE) + textLayoutDraw.save("drawUnderlineText_textLayoutDraw.png"); + drawGlyphs.save("drawUnderlineText_drawGlyphIndexes.png"); +#endif + + QCOMPARE(textLayoutDraw, drawGlyphs); +} + +void tst_QGlyphRun::drawRightToLeft() +{ + QString s; + s.append(QChar(1575)); + s.append(QChar(1578)); + + QPixmap textLayoutDraw(1000, 1000); + QPixmap drawGlyphs(1000, 1000); + + textLayoutDraw.fill(Qt::white); + drawGlyphs.fill(Qt::white); + + QFont font; + font.setUnderline(true); + + QTextLayout layout(s); + layout.setFont(font); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + { + QPainter p(&textLayoutDraw); + layout.draw(&p, QPointF(50, 50)); + } + + QGlyphRun glyphs = layout.glyphRuns().size() > 0 + ? layout.glyphRuns().at(0) + : QGlyphRun(); + + { + QPainter p(&drawGlyphs); + p.drawGlyphRun(QPointF(50, 50), glyphs); + } + +#if defined(DEBUG_SAVE_IMAGE) + textLayoutDraw.save("drawRightToLeft_textLayoutDraw.png"); + drawGlyphs.save("drawRightToLeft_drawGlyphIndexes.png"); +#endif + + QCOMPARE(textLayoutDraw, drawGlyphs); + +} + +#endif // QT_NO_RAWFONT + +QTEST_MAIN(tst_QGlyphRun) +#include "tst_qglyphrun.moc" + diff --git a/tests/auto/gui/text/qrawfont/qrawfont.pro b/tests/auto/gui/text/qrawfont/qrawfont.pro new file mode 100644 index 0000000000..1c08299699 --- /dev/null +++ b/tests/auto/gui/text/qrawfont/qrawfont.pro @@ -0,0 +1,16 @@ +load(qttest_p4) + +QT = core core-private gui gui-private + +SOURCES += \ + tst_qrawfont.cpp + +INCLUDEPATH += $$QT_SOURCE_TREE/src/3rdparty/harfbuzz/src + +wince*|symbian*: { + DEFINES += SRCDIR=\\\"\\\" +} else { + DEFINES += SRCDIR=\\\"$$PWD/\\\" +} + +CONFIG += insignificant_test # QTBUG-21402 diff --git a/tests/auto/gui/text/qrawfont/testfont.ttf b/tests/auto/gui/text/qrawfont/testfont.ttf Binary files differnew file mode 100644 index 0000000000..d6042d2e58 --- /dev/null +++ b/tests/auto/gui/text/qrawfont/testfont.ttf diff --git a/tests/auto/gui/text/qrawfont/testfont_bold_italic.ttf b/tests/auto/gui/text/qrawfont/testfont_bold_italic.ttf Binary files differnew file mode 100644 index 0000000000..9f65ac8df7 --- /dev/null +++ b/tests/auto/gui/text/qrawfont/testfont_bold_italic.ttf diff --git a/tests/auto/gui/text/qrawfont/tst_qrawfont.cpp b/tests/auto/gui/text/qrawfont/tst_qrawfont.cpp new file mode 100644 index 0000000000..1c18f2f1e8 --- /dev/null +++ b/tests/auto/gui/text/qrawfont/tst_qrawfont.cpp @@ -0,0 +1,897 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> + +#include <qrawfont.h> +#include <private/qrawfont_p.h> + +class tst_QRawFont: public QObject +{ + Q_OBJECT +#if !defined(QT_NO_RAWFONT) +private slots: + void init(); + + void invalidRawFont(); + + void explicitRawFontNotLoadedInDatabase_data(); + void explicitRawFontNotLoadedInDatabase(); + + void explicitRawFontNotAvailableInSystem_data(); + void explicitRawFontNotAvailableInSystem(); + + void correctFontData_data(); + void correctFontData(); + + void glyphIndices(); + + void advances_data(); + void advances(); + + void textLayout(); + + void fontTable_data(); + void fontTable(); + + void supportedWritingSystems_data(); + void supportedWritingSystems(); + + void supportsCharacter_data(); + void supportsCharacter(); + + void supportsUcs4Character_data(); + void supportsUcs4Character(); + + void fromFont_data(); + void fromFont(); + + void copyConstructor_data(); + void copyConstructor(); + + void detach_data(); + void detach(); + + void unsupportedWritingSystem_data(); + void unsupportedWritingSystem(); + + void rawFontSetPixelSize_data(); + void rawFontSetPixelSize(); + +#if defined(Q_WS_X11) || defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA) + void multipleRawFontsFromData(); +#endif + +#endif // QT_NO_RAWFONT +}; + +#if !defined(QT_NO_RAWFONT) +Q_DECLARE_METATYPE(QFont::HintingPreference) +Q_DECLARE_METATYPE(QFont::Style) +Q_DECLARE_METATYPE(QFont::Weight) +Q_DECLARE_METATYPE(QFontDatabase::WritingSystem) + +void tst_QRawFont::init() +{ +#ifdef Q_WS_QPA + // Loading fonts from a QByteArray seems unimplemented for all qpa plugins at time of writing; + // almost all testfunctions fail on qpa due to this, except these few: + const QByteArray func = QTest::currentTestFunction(); + if (func != "invalidRawFont" + && func != "explicitRawFontNotAvailableInSystem" + && func != "fromFont" + && func != "textLayout") + QEXPECT_FAIL("", "QTBUG-20976 fails on qpa", Abort); +#endif +} + +void tst_QRawFont::invalidRawFont() +{ + QRawFont font; + QVERIFY(!font.isValid()); + QCOMPARE(font.pixelSize(), 0.0); + QVERIFY(font.familyName().isEmpty()); + QCOMPARE(font.style(), QFont::StyleNormal); + QCOMPARE(font.weight(), -1); + QCOMPARE(font.ascent(), 0.0); + QCOMPARE(font.descent(), 0.0); + QVERIFY(font.glyphIndexesForString(QLatin1String("Test")).isEmpty()); +} + +void tst_QRawFont::explicitRawFontNotLoadedInDatabase_data() +{ + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + + QTest::newRow("Default hinting preference") << QFont::PreferDefaultHinting; + QTest::newRow("No hinting") << QFont::PreferNoHinting; + QTest::newRow("Vertical hinting") << QFont::PreferVerticalHinting; + QTest::newRow("Full hinting") << QFont::PreferFullHinting; +} + +void tst_QRawFont::explicitRawFontNotLoadedInDatabase() +{ + QFETCH(QFont::HintingPreference, hintingPreference); + + QRawFont font(QLatin1String(SRCDIR "testfont.ttf"), 10, hintingPreference); + QVERIFY(font.isValid()); + + QVERIFY(!QFontDatabase().families().contains(font.familyName())); +} + +void tst_QRawFont::explicitRawFontNotAvailableInSystem_data() +{ + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + + QTest::newRow("Default hinting preference") << QFont::PreferDefaultHinting; + QTest::newRow("No hinting") << QFont::PreferNoHinting; + QTest::newRow("Vertical hinting") << QFont::PreferVerticalHinting; + QTest::newRow("Full hinting") << QFont::PreferFullHinting; +} + +void tst_QRawFont::explicitRawFontNotAvailableInSystem() +{ + QFETCH(QFont::HintingPreference, hintingPreference); + + QRawFont rawfont(QLatin1String(SRCDIR "testfont.ttf"), 10, hintingPreference); + + { + QFont font(rawfont.familyName(), 10); + + QVERIFY(!font.exactMatch()); + QVERIFY(font.family() != QFontInfo(font).family()); + } +} + +void tst_QRawFont::correctFontData_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QString>("expectedFamilyName"); + QTest::addColumn<QFont::Style>("style"); + QTest::addColumn<QFont::Weight>("weight"); + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + QTest::addColumn<qreal>("unitsPerEm"); + QTest::addColumn<qreal>("pixelSize"); + + int hintingPreferences[] = { + int(QFont::PreferDefaultHinting), + int(QFont::PreferNoHinting), + int(QFont::PreferVerticalHinting), + int(QFont::PreferFullHinting), + -1 + }; + int *hintingPreference = hintingPreferences; + + while (*hintingPreference >= 0) { + QString fileName = QLatin1String(SRCDIR "testfont.ttf"); + QString title = fileName + + QLatin1String(": hintingPreference=") + + QString::number(*hintingPreference); + + QTest::newRow(qPrintable(title)) + << fileName + << QString::fromLatin1("QtBidiTestFont") + << QFont::StyleNormal + << QFont::Normal + << QFont::HintingPreference(*hintingPreference) + << 1000.0 + << 10.0; + + fileName = QLatin1String(SRCDIR "testfont_bold_italic.ttf"); + title = fileName + + QLatin1String(": hintingPreference=") + + QString::number(*hintingPreference); + + QTest::newRow(qPrintable(title)) + << fileName + << QString::fromLatin1("QtBidiTestFont") + << QFont::StyleItalic + << QFont::Bold + << QFont::HintingPreference(*hintingPreference) + << 1000.0 + << 10.0; + + ++hintingPreference; + } +} + +void tst_QRawFont::correctFontData() +{ + QFETCH(QString, fileName); + QFETCH(QString, expectedFamilyName); + QFETCH(QFont::Style, style); + QFETCH(QFont::Weight, weight); + QFETCH(QFont::HintingPreference, hintingPreference); + QFETCH(qreal, unitsPerEm); + QFETCH(qreal, pixelSize); + + QRawFont font(fileName, 10, hintingPreference); + QVERIFY(font.isValid()); + + QCOMPARE(font.familyName(), expectedFamilyName); + QCOMPARE(font.style(), style); + QCOMPARE(font.weight(), int(weight)); + QCOMPARE(font.hintingPreference(), hintingPreference); + QCOMPARE(font.unitsPerEm(), unitsPerEm); + QCOMPARE(font.pixelSize(), pixelSize); +} + +void tst_QRawFont::glyphIndices() +{ + QRawFont font(QLatin1String(SRCDIR "testfont.ttf"), 10); + QVERIFY(font.isValid()); + + QVector<quint32> glyphIndices = font.glyphIndexesForString(QLatin1String("Foobar")); + QVector<quint32> expectedGlyphIndices; + expectedGlyphIndices << 44 << 83 << 83 << 70 << 69 << 86; + + QCOMPARE(glyphIndices, expectedGlyphIndices); +} + +void tst_QRawFont::advances_data() +{ + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + + QTest::newRow("Default hinting preference") << QFont::PreferDefaultHinting; + QTest::newRow("No hinting") << QFont::PreferNoHinting; + QTest::newRow("Vertical hinting") << QFont::PreferVerticalHinting; + QTest::newRow("Full hinting") << QFont::PreferFullHinting; +} + +void tst_QRawFont::advances() +{ + QFETCH(QFont::HintingPreference, hintingPreference); + + QRawFont font(QLatin1String(SRCDIR "testfont.ttf"), 10, hintingPreference); + QVERIFY(font.isValid()); + + QRawFontPrivate *font_d = QRawFontPrivate::get(font); + QVERIFY(font_d->fontEngine != 0); + + QVector<quint32> glyphIndices; + glyphIndices << 44 << 83 << 83 << 70 << 69 << 86; // "Foobar" + + bool supportsSubPixelPositions = font_d->fontEngine->supportsSubPixelPositions(); + QVector<QPointF> advances = font.advancesForGlyphIndexes(glyphIndices); + for (int i=0; i<glyphIndices.size(); ++i) { + QVERIFY(qFuzzyCompare(qRound(advances.at(i).x()), 8.0)); + if (supportsSubPixelPositions) + QVERIFY(advances.at(i).x() > 8.0); + + QVERIFY(qFuzzyIsNull(advances.at(i).y())); + } +} + +void tst_QRawFont::textLayout() +{ + QFontDatabase fontDatabase; + int id = fontDatabase.addApplicationFont(SRCDIR "testfont.ttf"); + QVERIFY(id >= 0); + + QString familyName = QString::fromLatin1("QtBidiTestFont"); + QFont font(familyName); + font.setPixelSize(18.0); +#ifdef Q_WS_QPA + QEXPECT_FAIL("", "QTBUG-20976 fails on qpa", Abort); +#endif + QCOMPARE(QFontInfo(font).family(), familyName); + + QTextLayout layout(QLatin1String("Foobar")); + layout.setFont(font); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList<QGlyphRun> glyphRuns = layout.glyphRuns(); + QCOMPARE(glyphRuns.size(), 1); + + QGlyphRun glyphs = glyphRuns.at(0); + + QRawFont rawFont = glyphs.rawFont(); + QVERIFY(rawFont.isValid()); + QCOMPARE(rawFont.familyName(), familyName); + QCOMPARE(rawFont.pixelSize(), 18.0); + + QVector<quint32> expectedGlyphIndices; + expectedGlyphIndices << 44 << 83 << 83 << 70 << 69 << 86; + + QCOMPARE(glyphs.glyphIndexes(), expectedGlyphIndices); + + QVERIFY(fontDatabase.removeApplicationFont(id)); +} + +void tst_QRawFont::fontTable_data() +{ + QTest::addColumn<QByteArray>("tagName"); + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + QTest::addColumn<int>("offset"); + QTest::addColumn<quint32>("expectedValue"); + + QTest::newRow("Head table, magic number, default hinting") + << QByteArray("head") + << QFont::PreferDefaultHinting + << 12 + << (QSysInfo::ByteOrder == QSysInfo::BigEndian + ? 0x5F0F3CF5 + : 0xF53C0F5F); + + QTest::newRow("Head table, magic number, no hinting") + << QByteArray("head") + << QFont::PreferNoHinting + << 12 + << (QSysInfo::ByteOrder == QSysInfo::BigEndian + ? 0x5F0F3CF5 + : 0xF53C0F5F); + + QTest::newRow("Head table, magic number, vertical hinting") + << QByteArray("head") + << QFont::PreferVerticalHinting + << 12 + << (QSysInfo::ByteOrder == QSysInfo::BigEndian + ? 0x5F0F3CF5 + : 0xF53C0F5F); + + QTest::newRow("Head table, magic number, full hinting") + << QByteArray("head") + << QFont::PreferFullHinting + << 12 + << (QSysInfo::ByteOrder == QSysInfo::BigEndian + ? 0x5F0F3CF5 + : 0xF53C0F5F); +} + +void tst_QRawFont::fontTable() +{ + QFETCH(QByteArray, tagName); + QFETCH(QFont::HintingPreference, hintingPreference); + QFETCH(int, offset); + QFETCH(quint32, expectedValue); + + QRawFont font(QString::fromLatin1(SRCDIR "testfont.ttf"), 10, hintingPreference); + QVERIFY(font.isValid()); + + QByteArray table = font.fontTable(tagName); + QVERIFY(!table.isEmpty()); + + const quint32 *value = reinterpret_cast<const quint32 *>(table.constData() + offset); + QCOMPARE(*value, expectedValue); +} + +typedef QList<QFontDatabase::WritingSystem> WritingSystemList; +Q_DECLARE_METATYPE(WritingSystemList) + +void tst_QRawFont::supportedWritingSystems_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<WritingSystemList>("writingSystems"); + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + + for (int hintingPreference=QFont::PreferDefaultHinting; + hintingPreference<=QFont::PreferFullHinting; + ++hintingPreference) { + + QTest::newRow(qPrintable(QString::fromLatin1("testfont.ttf, hintingPreference=%1") + .arg(hintingPreference))) + << QString::fromLatin1(SRCDIR "testfont.ttf") + << (QList<QFontDatabase::WritingSystem>() + << QFontDatabase::Latin + << QFontDatabase::Hebrew + << QFontDatabase::Vietnamese) // Vietnamese uses same unicode bits as Latin + << QFont::HintingPreference(hintingPreference); + + QTest::newRow(qPrintable(QString::fromLatin1("testfont_bold_italic.ttf, hintingPreference=%1") + .arg(hintingPreference))) + << QString::fromLatin1(SRCDIR "testfont_bold_italic.ttf") + << (QList<QFontDatabase::WritingSystem>() + << QFontDatabase::Latin + << QFontDatabase::Hebrew + << QFontDatabase::Devanagari + << QFontDatabase::Vietnamese) // Vietnamese uses same unicode bits as Latin + << QFont::HintingPreference(hintingPreference); + } +} + +void tst_QRawFont::supportedWritingSystems() +{ + QFETCH(QString, fileName); + QFETCH(WritingSystemList, writingSystems); + QFETCH(QFont::HintingPreference, hintingPreference); + + QRawFont font(fileName, 10, hintingPreference); + QVERIFY(font.isValid()); + + WritingSystemList actualWritingSystems = font.supportedWritingSystems(); + QCOMPARE(actualWritingSystems.size(), writingSystems.size()); + + foreach (QFontDatabase::WritingSystem writingSystem, writingSystems) + QVERIFY(actualWritingSystems.contains(writingSystem)); +} + +void tst_QRawFont::supportsCharacter_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + QTest::addColumn<QChar>("character"); + QTest::addColumn<bool>("shouldBeSupported"); + + const char *fileNames[2] = { + SRCDIR "testfont.ttf", + SRCDIR "testfont_bold_italic.ttf" + }; + + for (int hintingPreference=QFont::PreferDefaultHinting; + hintingPreference<=QFont::PreferFullHinting; + ++hintingPreference) { + + for (int i=0; i<2; ++i) { + QString fileName = QLatin1String(fileNames[i]); + + // Latin text + for (char ch='!'; ch<='~'; ++ch) { + QString title = QString::fromLatin1("%1, character=0x%2, hintingPreference=%3") + .arg(fileName).arg(QString::number(ch, 16)).arg(hintingPreference); + + QTest::newRow(qPrintable(title)) + << fileName + << QFont::HintingPreference(hintingPreference) + << QChar::fromLatin1(ch) + << true; + } + + // Hebrew text + for (quint16 ch=0x05D0; ch<=0x05EA; ++ch) { + QString title = QString::fromLatin1("%1, character=0x%2, hintingPreference=%3") + .arg(fileName).arg(QString::number(ch, 16)).arg(hintingPreference); + + QTest::newRow(qPrintable(title)) + << fileName + << QFont::HintingPreference(hintingPreference) + << QChar(ch) + << true; + } + + QTest::newRow(qPrintable(QString::fromLatin1("Missing character, %1, hintingPreference=%2") + .arg(fileName).arg(hintingPreference))) + << fileName + << QFont::HintingPreference(hintingPreference) + << QChar(0xD8) + << false; + } + } +} + +void tst_QRawFont::supportsCharacter() +{ + QFETCH(QString, fileName); + QFETCH(QFont::HintingPreference, hintingPreference); + QFETCH(QChar, character); + QFETCH(bool, shouldBeSupported); + + QRawFont font(fileName, 10, hintingPreference); + QVERIFY(font.isValid()); + + QCOMPARE(font.supportsCharacter(character), shouldBeSupported); +} + +void tst_QRawFont::supportsUcs4Character_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + QTest::addColumn<quint32>("ucs4"); + QTest::addColumn<bool>("shouldBeSupported"); + + // Gothic text + for (int hintingPreference=QFont::PreferDefaultHinting; + hintingPreference<=QFont::PreferFullHinting; + ++hintingPreference) { + for (quint32 ch=0x10330; ch<=0x1034A; ++ch) { + { + QString fileName = QString::fromLatin1(SRCDIR "testfont.ttf"); + QString title = QString::fromLatin1("%1, character=0x%2, hintingPreference=%3") + .arg(fileName).arg(QString::number(ch, 16)).arg(hintingPreference); + + QTest::newRow(qPrintable(title)) + << fileName + << QFont::HintingPreference(hintingPreference) + << ch + << true; + } + + { + QString fileName = QString::fromLatin1(SRCDIR "testfont_bold_italic.ttf"); + QString title = QString::fromLatin1("%1, character=0x%2, hintingPreference=%3") + .arg(fileName).arg(QString::number(ch, 16)).arg(hintingPreference); + + QTest::newRow(qPrintable(title)) + << fileName + << QFont::HintingPreference(hintingPreference) + << ch + << false; + } + } + } +} + +void tst_QRawFont::supportsUcs4Character() +{ + QFETCH(QString, fileName); + QFETCH(QFont::HintingPreference, hintingPreference); + QFETCH(quint32, ucs4); + QFETCH(bool, shouldBeSupported); + + QRawFont font(fileName, 10, hintingPreference); + QVERIFY(font.isValid()); + + QCOMPARE(font.supportsCharacter(ucs4), shouldBeSupported); +} + +void tst_QRawFont::fromFont_data() +{ + QTest::addColumn<QString>("fileName"); + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + QTest::addColumn<QString>("familyName"); + QTest::addColumn<QFontDatabase::WritingSystem>("writingSystem"); + + for (int i=QFont::PreferDefaultHinting; i<=QFont::PreferFullHinting; ++i) { + QString titleBase = QString::fromLatin1("%2, hintingPreference=%1, writingSystem=%3") + .arg(i); + { + QString fileName = QString::fromLatin1(SRCDIR "testfont.ttf"); + QFontDatabase::WritingSystem writingSystem = QFontDatabase::Any; + + QString title = titleBase.arg(fileName).arg(writingSystem); + QTest::newRow(qPrintable(title)) + << fileName + << QFont::HintingPreference(i) + << "QtBidiTestFont" + << writingSystem; + } + + { + QString fileName = QString::fromLatin1(SRCDIR "testfont.ttf"); + QFontDatabase::WritingSystem writingSystem = QFontDatabase::Hebrew; + + QString title = titleBase.arg(fileName).arg(writingSystem); + QTest::newRow(qPrintable(title)) + << fileName + << QFont::HintingPreference(i) + << "QtBidiTestFont" + << writingSystem; + } + + { + QString fileName = QString::fromLatin1(SRCDIR "testfont.ttf"); + QFontDatabase::WritingSystem writingSystem = QFontDatabase::Latin; + + QString title = titleBase.arg(fileName).arg(writingSystem); + QTest::newRow(qPrintable(title)) + << fileName + << QFont::HintingPreference(i) + << "QtBidiTestFont" + << writingSystem; + } + } +} + +void tst_QRawFont::fromFont() +{ + QFETCH(QString, fileName); + QFETCH(QFont::HintingPreference, hintingPreference); + QFETCH(QString, familyName); + QFETCH(QFontDatabase::WritingSystem, writingSystem); + + QFontDatabase fontDatabase; + int id = fontDatabase.addApplicationFont(fileName); + QVERIFY(id >= 0); + + QFont font(familyName); + font.setHintingPreference(hintingPreference); + font.setPixelSize(26.0); + + QRawFont rawFont = QRawFont::fromFont(font, writingSystem); + QVERIFY(rawFont.isValid()); + +#ifdef Q_WS_QPA + QEXPECT_FAIL("", "QTBUG-20976 fails on qpa", Abort); +#endif + + QCOMPARE(rawFont.familyName(), familyName); + QCOMPARE(rawFont.pixelSize(), 26.0); + + QVERIFY(fontDatabase.removeApplicationFont(id)); +} + +void tst_QRawFont::copyConstructor_data() +{ + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + + QTest::newRow("Default hinting preference") << QFont::PreferDefaultHinting; + QTest::newRow("No hinting preference") << QFont::PreferNoHinting; + QTest::newRow("Vertical hinting preference") << QFont::PreferVerticalHinting; + QTest::newRow("Full hinting preference") << QFont::PreferFullHinting; +} + +void tst_QRawFont::copyConstructor() +{ + QFETCH(QFont::HintingPreference, hintingPreference); + + { + QString rawFontFamilyName; + qreal rawFontPixelSize; + qreal rawFontAscent; + qreal rawFontDescent; + int rawFontTableSize; + + QRawFont outerRawFont; + { + QRawFont rawFont(QString::fromLatin1(SRCDIR "testfont.ttf"), 11, hintingPreference); + QVERIFY(rawFont.isValid()); + + rawFontFamilyName = rawFont.familyName(); + rawFontPixelSize = rawFont.pixelSize(); + rawFontAscent = rawFont.ascent(); + rawFontDescent = rawFont.descent(); + rawFontTableSize = rawFont.fontTable("glyf").size(); + QVERIFY(rawFontTableSize > 0); + + { + QRawFont otherRawFont(rawFont); + QVERIFY(otherRawFont.isValid()); + QCOMPARE(otherRawFont.pixelSize(), rawFontPixelSize); + QCOMPARE(otherRawFont.familyName(), rawFontFamilyName); + QCOMPARE(otherRawFont.hintingPreference(), hintingPreference); + QCOMPARE(otherRawFont.ascent(), rawFontAscent); + QCOMPARE(otherRawFont.descent(), rawFontDescent); + QCOMPARE(otherRawFont.fontTable("glyf").size(), rawFontTableSize); + } + + { + QRawFont otherRawFont = rawFont; + QVERIFY(otherRawFont.isValid()); + QCOMPARE(otherRawFont.pixelSize(), rawFontPixelSize); + QCOMPARE(otherRawFont.familyName(), rawFontFamilyName); + QCOMPARE(otherRawFont.hintingPreference(), hintingPreference); + QCOMPARE(otherRawFont.ascent(), rawFontAscent); + QCOMPARE(otherRawFont.descent(), rawFontDescent); + QCOMPARE(otherRawFont.fontTable("glyf").size(), rawFontTableSize); + } + + outerRawFont = rawFont; + } + + QVERIFY(outerRawFont.isValid()); + QCOMPARE(outerRawFont.pixelSize(), rawFontPixelSize); + QCOMPARE(outerRawFont.familyName(), rawFontFamilyName); + QCOMPARE(outerRawFont.hintingPreference(), hintingPreference); + QCOMPARE(outerRawFont.ascent(), rawFontAscent); + QCOMPARE(outerRawFont.descent(), rawFontDescent); + QCOMPARE(outerRawFont.fontTable("glyf").size(), rawFontTableSize); + } +} + +void tst_QRawFont::detach_data() +{ + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + + QTest::newRow("Default hinting preference") << QFont::PreferDefaultHinting; + QTest::newRow("No hinting preference") << QFont::PreferNoHinting; + QTest::newRow("Vertical hinting preference") << QFont::PreferVerticalHinting; + QTest::newRow("Full hinting preference") << QFont::PreferFullHinting; +} + +void tst_QRawFont::detach() +{ + QFETCH(QFont::HintingPreference, hintingPreference); + + { + QString rawFontFamilyName; + qreal rawFontPixelSize; + qreal rawFontAscent; + qreal rawFontDescent; + int rawFontTableSize; + + QRawFont outerRawFont; + { + QRawFont rawFont(QString::fromLatin1(SRCDIR "testfont.ttf"), 11, hintingPreference); + QVERIFY(rawFont.isValid()); + + rawFontFamilyName = rawFont.familyName(); + rawFontPixelSize = rawFont.pixelSize(); + rawFontAscent = rawFont.ascent(); + rawFontDescent = rawFont.descent(); + rawFontTableSize = rawFont.fontTable("glyf").size(); + QVERIFY(rawFontTableSize > 0); + + { + QRawFont otherRawFont(rawFont); + + otherRawFont.loadFromFile(QLatin1String(SRCDIR "testfont.ttf"), + rawFontPixelSize, hintingPreference); + + QVERIFY(otherRawFont.isValid()); + QCOMPARE(otherRawFont.pixelSize(), rawFontPixelSize); + QCOMPARE(otherRawFont.familyName(), rawFontFamilyName); + QCOMPARE(otherRawFont.hintingPreference(), hintingPreference); + QCOMPARE(otherRawFont.ascent(), rawFontAscent); + QCOMPARE(otherRawFont.descent(), rawFontDescent); + QCOMPARE(otherRawFont.fontTable("glyf").size(), rawFontTableSize); + } + + { + QRawFont otherRawFont = rawFont; + + otherRawFont.loadFromFile(QLatin1String(SRCDIR "testfont.ttf"), + rawFontPixelSize, hintingPreference); + + QVERIFY(otherRawFont.isValid()); + QCOMPARE(otherRawFont.pixelSize(), rawFontPixelSize); + QCOMPARE(otherRawFont.familyName(), rawFontFamilyName); + QCOMPARE(otherRawFont.hintingPreference(), hintingPreference); + QCOMPARE(otherRawFont.ascent(), rawFontAscent); + QCOMPARE(otherRawFont.descent(), rawFontDescent); + QCOMPARE(otherRawFont.fontTable("glyf").size(), rawFontTableSize); + } + + outerRawFont = rawFont; + + rawFont.loadFromFile(QLatin1String(SRCDIR "testfont.ttf"), rawFontPixelSize, + hintingPreference); + } + + QVERIFY(outerRawFont.isValid()); + QCOMPARE(outerRawFont.pixelSize(), rawFontPixelSize); + QCOMPARE(outerRawFont.familyName(), rawFontFamilyName); + QCOMPARE(outerRawFont.hintingPreference(), hintingPreference); + QCOMPARE(outerRawFont.ascent(), rawFontAscent); + QCOMPARE(outerRawFont.descent(), rawFontDescent); + QCOMPARE(outerRawFont.fontTable("glyf").size(), rawFontTableSize); + } +} + +void tst_QRawFont::unsupportedWritingSystem_data() +{ + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + + QTest::newRow("Default hinting preference") << QFont::PreferDefaultHinting; + QTest::newRow("No hinting preference") << QFont::PreferNoHinting; + QTest::newRow("Vertical hinting preference") << QFont::PreferVerticalHinting; + QTest::newRow("Full hinting preference") << QFont::PreferFullHinting; +} + +void tst_QRawFont::unsupportedWritingSystem() +{ + QFETCH(QFont::HintingPreference, hintingPreference); + + QFontDatabase fontDatabase; + int id = fontDatabase.addApplicationFont(QLatin1String(SRCDIR "testfont.ttf")); + + QFont font("QtBidiTestFont"); + font.setHintingPreference(hintingPreference); + font.setPixelSize(12.0); + + QRawFont rawFont = QRawFont::fromFont(font, QFontDatabase::Any); + QCOMPARE(rawFont.familyName(), QString::fromLatin1("QtBidiTestFont")); + QCOMPARE(rawFont.pixelSize(), 12.0); + + rawFont = QRawFont::fromFont(font, QFontDatabase::Hebrew); + QCOMPARE(rawFont.familyName(), QString::fromLatin1("QtBidiTestFont")); + QCOMPARE(rawFont.pixelSize(), 12.0); + + QString arabicText = QFontDatabase::writingSystemSample(QFontDatabase::Arabic); + + QTextLayout layout; + layout.setFont(font); + layout.setText(arabicText); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QList<QGlyphRun> glyphRuns = layout.glyphRuns(); + QCOMPARE(glyphRuns.size(), 1); + + QGlyphRun glyphs = glyphRuns.at(0); + QRawFont layoutFont = glyphs.rawFont(); + QVERIFY(layoutFont.familyName() != QString::fromLatin1("QtBidiTestFont")); + QCOMPARE(layoutFont.pixelSize(), 12.0); + + rawFont = QRawFont::fromFont(font, QFontDatabase::Arabic); + QCOMPARE(rawFont.familyName(), layoutFont.familyName()); + QCOMPARE(rawFont.pixelSize(), 12.0); + + fontDatabase.removeApplicationFont(id); +} + +void tst_QRawFont::rawFontSetPixelSize_data() +{ + QTest::addColumn<QFont::HintingPreference>("hintingPreference"); + + QTest::newRow("Default hinting preference") << QFont::PreferDefaultHinting; + QTest::newRow("No hinting preference") << QFont::PreferNoHinting; + QTest::newRow("Vertical hinting preference") << QFont::PreferVerticalHinting; + QTest::newRow("Full hinting preference") << QFont::PreferFullHinting; +} + +void tst_QRawFont::rawFontSetPixelSize() +{ + QFETCH(QFont::HintingPreference, hintingPreference); + + QTextLayout layout("Foobar"); + + QFont font = layout.font(); + font.setHintingPreference(hintingPreference); + font.setPixelSize(12); + layout.setFont(font); + + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QGlyphRun glyphs = layout.glyphRuns().at(0); + QRawFont rawFont = glyphs.rawFont(); + QCOMPARE(rawFont.pixelSize(), 12.0); + + rawFont.setPixelSize(24); + QCOMPARE(rawFont.pixelSize(), 24.0); +} + +#if defined(Q_WS_X11) || defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA) +void tst_QRawFont::multipleRawFontsFromData() +{ + QFile file(QString::fromLatin1(SRCDIR "testfont.ttf")); + QRawFont testFont; + if (file.open(QIODevice::ReadOnly)) { + testFont.loadFromData(file.readAll(), 11, QFont::PreferDefaultHinting); + file.close(); + } + file.setFileName(QLatin1String(SRCDIR "testfont_bold_italic.ttf")); + QRawFont testFontBoldItalic; + if (file.open(QIODevice::ReadOnly)) + testFontBoldItalic.loadFromData(file.readAll(), 11, QFont::PreferDefaultHinting); + + QVERIFY(testFont.familyName() != (testFontBoldItalic.familyName()) + || testFont.styleName() != (testFontBoldItalic.styleName())); +} +#endif + +#endif // QT_NO_RAWFONT + +QTEST_MAIN(tst_QRawFont) +#include "tst_qrawfont.moc" + diff --git a/tests/auto/gui/text/qstatictext/qstatictext.pro b/tests/auto/gui/text/qstatictext/qstatictext.pro new file mode 100644 index 0000000000..a8398dccf9 --- /dev/null +++ b/tests/auto/gui/text/qstatictext/qstatictext.pro @@ -0,0 +1,6 @@ +load(qttest_p4) +QT += widgets widgets-private +QT += core core-private gui gui-private +SOURCES += tst_qstatictext.cpp + +CONFIG += insignificant_test # QTBUG-21290 - crashes on qpa, xcb diff --git a/tests/auto/gui/text/qstatictext/tst_qstatictext.cpp b/tests/auto/gui/text/qstatictext/tst_qstatictext.cpp new file mode 100644 index 0000000000..79cbd692ea --- /dev/null +++ b/tests/auto/gui/text/qstatictext/tst_qstatictext.cpp @@ -0,0 +1,870 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QtWidgets/QApplication> +#include <QtGui/QPainter> +#include <QtGui/QImage> + +#include <qstatictext.h> +#include <qpaintengine.h> + +#include <private/qstatictext_p.h> +#include <private/qapplication_p.h> + +// #define DEBUG_SAVE_IMAGE + +class tst_QStaticText: public QObject +{ + Q_OBJECT +public: + tst_QStaticText() {} + +private slots: + void initTestCase(); + + void init(); + void cleanup(); + + void constructionAndDestruction(); + void drawToPoint_data(); + void drawToPoint(); + void drawToRect_data(); + void drawToRect(); + void setFont(); + void setTextWidth(); + void prepareToCorrectData(); + void prepareToWrongData(); + + void copyConstructor(); + + void translatedPainter(); + void rotatedPainter(); + void scaledPainter(); + void projectedPainter(); +#if 0 + void rotatedScaledAndTranslatedPainter_data(); + void rotatedScaledAndTranslatedPainter(); +#endif + void transformationChanged(); + + void plainTextVsRichText(); + + void setPenPlainText(); + void setPenRichText(); + void richTextOverridesPen(); + + void drawStruckOutText(); + void drawOverlinedText(); + void drawUnderlinedText(); + + void unprintableCharacter_qtbug12614(); + + void underlinedColor_qtbug20159(); + void textDocumentColor(); + +private: + bool supportsTransformations() const; + + QImage const m_whiteSquare; +}; + +void tst_QStaticText::initTestCase() +{ + // a "blank" square; we compare against in our testfunctions to verify + // that we have actually painted something + QPixmap pm(1000, 1000); + pm.fill(Qt::white); + const_cast<QImage&>(m_whiteSquare) = pm.toImage(); +} + +void tst_QStaticText::init() +{ +} + +void tst_QStaticText::cleanup() +{ +} + +void tst_QStaticText::constructionAndDestruction() +{ + QStaticText text("My text"); +} + +void tst_QStaticText::copyConstructor() +{ + QStaticText text(QLatin1String("My text")); + + QTextOption textOption(Qt::AlignRight); + text.setTextOption(textOption); + + text.setPerformanceHint(QStaticText::AggressiveCaching); + text.setTextWidth(123.456); + text.setTextFormat(Qt::PlainText); + + QStaticText copiedText(text); + copiedText.setText(QLatin1String("Other text")); + + QCOMPARE(copiedText.textOption().alignment(), Qt::AlignRight); + QCOMPARE(copiedText.performanceHint(), QStaticText::AggressiveCaching); + QCOMPARE(copiedText.textWidth(), 123.456); + QCOMPARE(copiedText.textFormat(), Qt::PlainText); + + QStaticText otherCopiedText(copiedText); + otherCopiedText.setTextWidth(789); + + QCOMPARE(otherCopiedText.text(), QString::fromLatin1("Other text")); +} + +Q_DECLARE_METATYPE(QStaticText::PerformanceHint) +void tst_QStaticText::drawToPoint_data() +{ + QTest::addColumn<QStaticText::PerformanceHint>("performanceHint"); + + QTest::newRow("Moderate caching") << QStaticText::ModerateCaching; + QTest::newRow("Aggressive caching") << QStaticText::AggressiveCaching; +} + +void tst_QStaticText::drawToPoint() +{ + QFETCH(QStaticText::PerformanceHint, performanceHint); + + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextFormat(Qt::PlainText); + text.setPerformanceHint(performanceHint); + p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text); + } + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +void tst_QStaticText::drawToRect_data() +{ + QTest::addColumn<QStaticText::PerformanceHint>("performanceHint"); + + QTest::newRow("Moderate caching") << QStaticText::ModerateCaching; + QTest::newRow("Aggressive caching") << QStaticText::AggressiveCaching; +} + +void tst_QStaticText::drawToRect() +{ + QFETCH(QStaticText::PerformanceHint, performanceHint); + + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.drawText(QRectF(11, 12, 10, 500), "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextWidth(10), + p.setClipRect(QRectF(11, 12, 10, 500)); + text.setPerformanceHint(performanceHint); + text.setTextFormat(Qt::PlainText); + p.drawStaticText(QPointF(11, 12), text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("drawToRect_imageDrawText.png"); + imageDrawStaticText.save("drawToRect_imageDrawStaticText.png"); +#endif + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +void tst_QStaticText::prepareToCorrectData() +{ + QTransform transform; + transform.scale(2.0, 2.0); + transform.rotate(90, Qt::ZAxis); + + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.setTransform(transform); + p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + p.setTransform(transform); + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.prepare(transform, p.font()); + text.setTextFormat(Qt::PlainText); + p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("prepareToCorrectData_imageDrawText.png"); + imageDrawStaticText.save("prepareToCorrectData_imageDrawStaticText.png"); +#endif + +#ifdef Q_WS_QPA + QEXPECT_FAIL("", "QTBUG-20977 fails on qpa", Abort); +#endif + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + + if (!supportsTransformations()) + QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +void tst_QStaticText::prepareToWrongData() +{ + QTransform transform; + transform.scale(2.0, 2.0); + transform.rotate(90, Qt::ZAxis); + + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.prepare(transform, p.font()); + text.setTextFormat(Qt::PlainText); + p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text); + } + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + + +void tst_QStaticText::setFont() +{ + QFont font = QApplication::font(); + font.setBold(true); + font.setPointSize(28); + + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.drawText(QRectF(0, 0, 1000, 1000), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + + p.setFont(font); + p.drawText(QRectF(11, 120, 1000, 1000), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + + QStaticText text; + text.setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextFormat(Qt::PlainText); + + p.drawStaticText(0, 0, text); + + p.setFont(font); + p.drawStaticText(11, 120, text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("setFont_imageDrawText.png"); + imageDrawStaticText.save("setFont_imageDrawStaticText.png"); +#endif + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +void tst_QStaticText::setTextWidth() +{ + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.drawText(QRectF(11, 12, 10, 500), "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextWidth(10); + p.setClipRect(QRectF(11, 12, 10, 500)); + p.drawStaticText(QPointF(11, 12), text); + } + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +void tst_QStaticText::translatedPainter() +{ + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.translate(100, 200); + + p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + p.translate(100, 200); + + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextFormat(Qt::PlainText); + + p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text); + } + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +bool tst_QStaticText::supportsTransformations() const +{ + QPixmap pm(10, 10); + QPainter p(&pm); + QPaintEngine *engine = p.paintEngine(); + + QPaintEngine::Type type = engine->type(); + + if (type == QPaintEngine::OpenGL +#if !defined(Q_WS_WIN) && !defined(Q_WS_X11) && !defined(Q_WS_MAC) + || type == QPaintEngine::Raster +#endif + ) + return false; + + return true; +} + +void tst_QStaticText::rotatedPainter() +{ + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.rotate(30.0); + p.drawText(QRectF(0, 0, 1000, 100), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextFormat(Qt::PlainText); + + QPainter p(&imageDrawStaticText); + p.rotate(30.0); + p.drawStaticText(QPoint(0, 0), text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("rotatedPainter_imageDrawText.png"); + imageDrawStaticText.save("rotatedPainter_imageDrawStaticText.png"); +#endif + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + + if (!supportsTransformations()) + QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +void tst_QStaticText::scaledPainter() +{ + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.scale(2.0, 0.2); + + p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + p.scale(2.0, 0.2); + + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextFormat(Qt::PlainText); + + p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text); + } + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + + if (!supportsTransformations()) + QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +void tst_QStaticText::projectedPainter() +{ + QTransform transform; + transform.rotate(90, Qt::XAxis); + + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.setTransform(transform); + + p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + p.setTransform(transform); + + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextFormat(Qt::PlainText); + + p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text); + } + + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +#if 0 +void tst_QStaticText::rotatedScaledAndTranslatedPainter_data() +{ + QTest::addColumn<qreal>("offset"); + + for (int i=0; i<100; ++i) { + qreal offset = 300 + i / 100.; + QTest::newRow(QByteArray::number(offset).constData()) << offset; + } +} + +void tst_QStaticText::rotatedScaledAndTranslatedPainter() +{ + QFETCH(qreal, offset); + + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.translate(offset, 0); + p.rotate(45.0); + p.scale(2.0, 2.0); + p.translate(100, 200); + + p.drawText(11, 12, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + p.translate(offset, 0); + p.rotate(45.0); + p.scale(2.0, 2.0); + p.translate(100, 200); + + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextFormat(Qt::PlainText); + + p.drawStaticText(QPointF(11, 12 - QFontMetricsF(p.font()).ascent()), text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("rotatedScaledAndPainter_imageDrawText.png"); + imageDrawStaticText.save("rotatedScaledAndPainter_imageDrawStaticText.png"); +#endif + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + + if (!supportsTransformations()) + QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort); + QCOMPARE(imageDrawStaticText, imageDrawText); +} +#endif + +void tst_QStaticText::transformationChanged() +{ + QPixmap imageDrawText(1000, 1000); + imageDrawText.fill(Qt::white); + { + QPainter p(&imageDrawText); + p.rotate(33.0); + p.scale(0.5, 0.7); + + p.drawText(QRectF(0, 0, 1000, 1000), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + + p.scale(2.0, 2.5); + p.drawText(QRectF(0, 0, 1000, 1000), 0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + } + + QPixmap imageDrawStaticText(1000, 1000); + imageDrawStaticText.fill(Qt::white); + { + QPainter p(&imageDrawStaticText); + p.rotate(33.0); + p.scale(0.5, 0.7); + + QStaticText text("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + text.setTextFormat(Qt::PlainText); + + p.drawStaticText(QPointF(0, 0), text); + + p.scale(2.0, 2.5); + p.drawStaticText(QPointF(0, 0), text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("transformationChanged_imageDrawText.png"); + imageDrawStaticText.save("transformationChanged_imageDrawStaticText.png"); +#endif + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + + if (!supportsTransformations()) + QEXPECT_FAIL("", "Graphics system does not support transformed text on this platform", Abort); + QCOMPARE(imageDrawStaticText, imageDrawText); +} + +void tst_QStaticText::plainTextVsRichText() +{ + QPixmap imagePlainText(1000, 1000); + imagePlainText.fill(Qt::white); + { + QPainter p(&imagePlainText); + + QStaticText staticText; + staticText.setText("FOObar"); + staticText.setTextFormat(Qt::PlainText); + + p.drawStaticText(10, 10, staticText); + } + + QPixmap imageRichText(1000, 1000); + imageRichText.fill(Qt::white); + { + QPainter p(&imageRichText); + + QStaticText staticText; + staticText.setText("<html><body>FOObar</body></html>"); + staticText.setTextFormat(Qt::RichText); + + p.drawStaticText(10, 10, staticText); + } + +#if defined(DEBUG_SAVE_IMAGE) + imagePlainText.save("plainTextVsRichText_imagePlainText.png"); + imageRichText.save("plainTextVsRichText_imageRichText.png"); +#endif + + QVERIFY(imagePlainText.toImage() != m_whiteSquare); + QCOMPARE(imagePlainText, imageRichText); +} + +void tst_QStaticText::setPenPlainText() +{ + QFont font = QApplication::font(); + font.setStyleStrategy(QFont::NoAntialias); + + QFontMetricsF fm(font); + QPixmap image(qCeil(fm.width("XXXXX")), qCeil(fm.height())); + image.fill(Qt::white); + { + QPainter p(&image); + p.setFont(font); + p.setPen(Qt::green); + + QStaticText staticText("XXXXX"); + staticText.setTextFormat(Qt::PlainText); + p.drawStaticText(0, 0, staticText); + } + + QImage img = image.toImage(); + for (int x=0; x<img.width(); ++x) { + for (int y=0; y<img.height(); ++y) { + QRgb pixel = img.pixel(x, y); + QVERIFY(pixel == QColor(Qt::white).rgba() + || pixel == QColor(Qt::green).rgba()); + } + } +} + +void tst_QStaticText::setPenRichText() +{ + QFont font = QApplication::font(); + font.setStyleStrategy(QFont::NoAntialias); + + QFontMetricsF fm(font); + QPixmap image(qCeil(fm.width("XXXXX")), qCeil(fm.height())); + image.fill(Qt::white); + { + QPainter p(&image); + p.setFont(font); + p.setPen(Qt::green); + + QStaticText staticText; + staticText.setText("<html><body>XXXXX</body></html>"); + staticText.setTextFormat(Qt::RichText); + p.drawStaticText(0, 0, staticText); + } + + QImage img = image.toImage(); + for (int x=0; x<img.width(); ++x) { + for (int y=0; y<img.height(); ++y) { + QRgb pixel = img.pixel(x, y); + QVERIFY(pixel == QColor(Qt::white).rgba() + || pixel == QColor(Qt::green).rgba()); + } + } +} + +void tst_QStaticText::richTextOverridesPen() +{ + QFont font = QApplication::font(); + font.setStyleStrategy(QFont::NoAntialias); + + QFontMetricsF fm(font); + QPixmap image(qCeil(fm.width("XXXXX")), qCeil(fm.height())); + image.fill(Qt::white); + { + QPainter p(&image); + p.setFont(font); + p.setPen(Qt::green); + + QStaticText staticText; + staticText.setText("<html><body><font color=\"#ff0000\">XXXXX</font></body></html>"); + staticText.setTextFormat(Qt::RichText); + p.drawStaticText(0, 0, staticText); + } + + QImage img = image.toImage(); + for (int x=0; x<img.width(); ++x) { + for (int y=0; y<img.height(); ++y) { + QRgb pixel = img.pixel(x, y); + QVERIFY(pixel == QColor(Qt::white).rgba() + || pixel == QColor(Qt::red).rgba()); + } + } +} + +void tst_QStaticText::drawStruckOutText() +{ + QPixmap imageDrawText(1000, 1000); + QPixmap imageDrawStaticText(1000, 1000); + + imageDrawText.fill(Qt::white); + imageDrawStaticText.fill(Qt::white); + + QString s = QString::fromLatin1("Foobar"); + + QFont font; + font.setStrikeOut(true); + + { + QPainter p(&imageDrawText); + p.setFont(font); + p.drawText(QPointF(50, 50), s); + } + + { + QPainter p(&imageDrawStaticText); + QStaticText text = QStaticText(s); + p.setFont(font); + p.drawStaticText(QPointF(50, 50 - QFontMetricsF(p.font()).ascent()), text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("drawStruckOutText_imageDrawText.png"); + imageDrawStaticText.save("drawStruckOutText_imageDrawStaticText.png"); +#endif + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + QCOMPARE(imageDrawText, imageDrawStaticText); +} + +void tst_QStaticText::drawOverlinedText() +{ + QPixmap imageDrawText(1000, 1000); + QPixmap imageDrawStaticText(1000, 1000); + + imageDrawText.fill(Qt::white); + imageDrawStaticText.fill(Qt::white); + + QString s = QString::fromLatin1("Foobar"); + + QFont font; + font.setOverline(true); + + { + QPainter p(&imageDrawText); + p.setFont(font); + p.drawText(QPointF(50, 50), s); + } + + { + QPainter p(&imageDrawStaticText); + QStaticText text = QStaticText(s); + p.setFont(font); + p.drawStaticText(QPointF(50, 50 - QFontMetricsF(p.font()).ascent()), text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("drawOverlinedText_imageDrawText.png"); + imageDrawStaticText.save("drawOverlinedText_imageDrawStaticText.png"); +#endif + + QVERIFY(imageDrawText.toImage() != m_whiteSquare); + QCOMPARE(imageDrawText, imageDrawStaticText); +} + +void tst_QStaticText::drawUnderlinedText() +{ + QPixmap imageDrawText(1000, 1000); + QPixmap imageDrawStaticText(1000, 1000); + + imageDrawText.fill(Qt::white); + imageDrawStaticText.fill(Qt::white); + + QString s = QString::fromLatin1("Foobar"); + + QFont font; + font.setUnderline(true); + + { + QPainter p(&imageDrawText); + p.setFont(font); + p.drawText(QPointF(50, 50), s); + } + + { + QPainter p(&imageDrawStaticText); + QStaticText text = QStaticText(s); + p.setFont(font); + p.drawStaticText(QPointF(50, 50 - QFontMetricsF(p.font()).ascent()), text); + } + +#if defined(DEBUG_SAVE_IMAGE) + imageDrawText.save("drawUnderlinedText_imageDrawText.png"); + imageDrawStaticText.save("drawUnderlinedText_imageDrawStaticText.png"); +#endif + + QCOMPARE(imageDrawText, imageDrawStaticText); +} + +void tst_QStaticText::unprintableCharacter_qtbug12614() +{ + QString s(QChar(0x200B)); // U+200B, ZERO WIDTH SPACE + + QStaticText staticText(s); + + QVERIFY(staticText.size().isValid()); // Force layout. Should not crash. +} + +void tst_QStaticText::underlinedColor_qtbug20159() +{ + QString multiScriptText; + multiScriptText += QChar(0x0410); // Cyrillic 'A' + multiScriptText += QLatin1Char('A'); + + QStaticText staticText(multiScriptText); + + QFont font; + font.setUnderline(true); + + staticText.prepare(QTransform(), font); + + QStaticTextPrivate *d = QStaticTextPrivate::get(&staticText); + QCOMPARE(d->itemCount, 2); + + // The pen should not be marked as dirty when drawing the underline + QVERIFY(!d->items[0].color.isValid()); + QVERIFY(!d->items[1].color.isValid()); +} + +void tst_QStaticText::textDocumentColor() +{ + QStaticText staticText("A<font color=\"red\">B</font>"); + staticText.setTextFormat(Qt::RichText); + staticText.prepare(); + + QStaticTextPrivate *d = QStaticTextPrivate::get(&staticText); + QCOMPARE(d->itemCount, 2); + + // The pen should not be marked as dirty when drawing the underline + QVERIFY(!d->items[0].color.isValid()); + QVERIFY(d->items[1].color.isValid()); + + QCOMPARE(d->items[1].color, QColor(Qt::red)); +} + +QTEST_MAIN(tst_QStaticText) +#include "tst_qstatictext.moc" diff --git a/tests/auto/gui/text/qsyntaxhighlighter/.gitignore b/tests/auto/gui/text/qsyntaxhighlighter/.gitignore new file mode 100644 index 0000000000..3efe6efbe5 --- /dev/null +++ b/tests/auto/gui/text/qsyntaxhighlighter/.gitignore @@ -0,0 +1 @@ +tst_qsyntaxhighlighter diff --git a/tests/auto/gui/text/qsyntaxhighlighter/qsyntaxhighlighter.pro b/tests/auto/gui/text/qsyntaxhighlighter/qsyntaxhighlighter.pro new file mode 100644 index 0000000000..30fb3a5283 --- /dev/null +++ b/tests/auto/gui/text/qsyntaxhighlighter/qsyntaxhighlighter.pro @@ -0,0 +1,4 @@ +load(qttest_p4) +SOURCES += tst_qsyntaxhighlighter.cpp + + diff --git a/tests/auto/gui/text/qsyntaxhighlighter/tst_qsyntaxhighlighter.cpp b/tests/auto/gui/text/qsyntaxhighlighter/tst_qsyntaxhighlighter.cpp new file mode 100644 index 0000000000..24ade2dc91 --- /dev/null +++ b/tests/auto/gui/text/qsyntaxhighlighter/tst_qsyntaxhighlighter.cpp @@ -0,0 +1,549 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> +#include <QTextDocument> +#include <QTextLayout> +#include <QDebug> +#include <QAbstractTextDocumentLayout> +#include <QSyntaxHighlighter> + +//TESTED_CLASS= +//TESTED_FILES= +// +class QTestDocumentLayout : public QAbstractTextDocumentLayout +{ + Q_OBJECT +public: + inline QTestDocumentLayout(QTextDocument *doc) + : QAbstractTextDocumentLayout(doc), documentChangedCalled(false) {} + + virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &) {} + + virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const { return 0; } + + virtual void documentChanged(int, int, int) { documentChangedCalled = true; } + + virtual int pageCount() const { return 1; } + + virtual QSizeF documentSize() const { return QSize(); } + + virtual QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); } + virtual QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); } + + bool documentChangedCalled; +}; + +class tst_QSyntaxHighlighter : public QObject +{ + Q_OBJECT +public: + inline tst_QSyntaxHighlighter() {} + +public slots: + void init(); + void cleanup(); + +private slots: + void basic(); + void basicTwo(); + void removeFormatsOnDelete(); + void emptyBlocks(); + void setCharFormat(); + void highlightOnInit(); + void stopHighlightingWhenStateDoesNotChange(); + void unindent(); + void highlightToEndOfDocument(); + void highlightToEndOfDocument2(); + void preservePreeditArea(); + void task108530(); + void avoidUnnecessaryRehighlight(); + void noContentsChangedDuringHighlight(); + void rehighlight(); + void rehighlightBlock(); + +private: + QTextDocument *doc; + QTestDocumentLayout *lout; + QTextCursor cursor; +}; + +void tst_QSyntaxHighlighter::init() +{ + doc = new QTextDocument; + lout = new QTestDocumentLayout(doc); + doc->setDocumentLayout(lout); + cursor = QTextCursor(doc); +} + +void tst_QSyntaxHighlighter::cleanup() +{ + delete doc; + doc = 0; +} + +class TestHighlighter : public QSyntaxHighlighter +{ +public: + inline TestHighlighter(const QList<QTextLayout::FormatRange> &fmts, QTextDocument *parent) + : QSyntaxHighlighter(parent), formats(fmts), highlighted(false), callCount(0) {} + inline TestHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent), highlighted(false), callCount(0) {} + + virtual void highlightBlock(const QString &text) + { + for (int i = 0; i < formats.count(); ++i) { + const QTextLayout::FormatRange &range = formats.at(i); + setFormat(range.start, range.length, range.format); + } + highlighted = true; + highlightedText += text; + ++callCount; + } + + QList<QTextLayout::FormatRange> formats; + bool highlighted; + int callCount; + QString highlightedText; +}; + +QT_BEGIN_NAMESPACE +bool operator==(const QTextLayout::FormatRange &lhs, const QTextLayout::FormatRange &rhs) +{ + return lhs.start == rhs.start + && lhs.length == rhs.length + && lhs.format == rhs.format; +} +QT_END_NAMESPACE + +void tst_QSyntaxHighlighter::basic() +{ + QList<QTextLayout::FormatRange> formats; + QTextLayout::FormatRange range; + range.start = 0; + range.length = 2; + range.format.setForeground(Qt::blue); + formats.append(range); + + range.start = 4; + range.length = 2; + range.format.setFontItalic(true); + formats.append(range); + + range.start = 9; + range.length = 2; + range.format.setFontUnderline(true); + formats.append(range); + + TestHighlighter *hl = new TestHighlighter(formats, doc); + + lout->documentChangedCalled = false; + doc->setPlainText("Hello World"); + QVERIFY(hl->highlighted); + QVERIFY(lout->documentChangedCalled); + + QVERIFY(doc->begin().layout()->additionalFormats() == formats); +} + +class CommentTestHighlighter : public QSyntaxHighlighter +{ +public: + inline CommentTestHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent), highlighted(false) {} + + inline void reset() + { + highlighted = false; + } + + virtual void highlightBlock(const QString &text) + { + QTextCharFormat commentFormat; + commentFormat.setForeground(Qt::darkGreen); + commentFormat.setFontWeight(QFont::StyleItalic); + commentFormat.setFontFixedPitch(true); + int textLength = text.length(); + + if (text.startsWith(QLatin1Char(';'))){ + // The entire line is a comment + setFormat(0, textLength, commentFormat); + highlighted = true; + } + } + bool highlighted; +}; + + +void tst_QSyntaxHighlighter::basicTwo() +{ + // Done for task 104409 + CommentTestHighlighter *hl = new CommentTestHighlighter(doc); + doc->setPlainText("; a test"); + QVERIFY(hl->highlighted); + QVERIFY(lout->documentChangedCalled); +} + +void tst_QSyntaxHighlighter::removeFormatsOnDelete() +{ + QList<QTextLayout::FormatRange> formats; + QTextLayout::FormatRange range; + range.start = 0; + range.length = 9; + range.format.setForeground(Qt::blue); + formats.append(range); + + TestHighlighter *hl = new TestHighlighter(formats, doc); + + lout->documentChangedCalled = false; + doc->setPlainText("Hello World"); + QVERIFY(hl->highlighted); + QVERIFY(lout->documentChangedCalled); + + lout->documentChangedCalled = false; + QVERIFY(!doc->begin().layout()->additionalFormats().isEmpty()); + delete hl; + QVERIFY(doc->begin().layout()->additionalFormats().isEmpty()); + QVERIFY(lout->documentChangedCalled); +} + +void tst_QSyntaxHighlighter::emptyBlocks() +{ + TestHighlighter *hl = new TestHighlighter(doc); + + cursor.insertText("Foo"); + cursor.insertBlock(); + cursor.insertBlock(); + hl->highlighted = false; + cursor.insertBlock(); + QVERIFY(hl->highlighted); +} + +void tst_QSyntaxHighlighter::setCharFormat() +{ + TestHighlighter *hl = new TestHighlighter(doc); + + cursor.insertText("FooBar"); + cursor.insertBlock(); + cursor.insertText("Blah"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + QTextCharFormat fmt; + fmt.setFontItalic(true); + hl->highlighted = false; + hl->callCount = 0; + cursor.mergeCharFormat(fmt); + QVERIFY(hl->highlighted); + QCOMPARE(hl->callCount, 2); +} + +void tst_QSyntaxHighlighter::highlightOnInit() +{ + cursor.insertText("Hello"); + cursor.insertBlock(); + cursor.insertText("World"); + + TestHighlighter *hl = new TestHighlighter(doc); + QTest::qWait(100); + QVERIFY(hl->highlighted); +} + +class StateTestHighlighter : public QSyntaxHighlighter +{ +public: + inline StateTestHighlighter(QTextDocument *parent) + : QSyntaxHighlighter(parent), state(0), highlighted(false) {} + + inline void reset() + { + highlighted = false; + state = 0; + } + + virtual void highlightBlock(const QString &text) + { + highlighted = true; + if (text == QLatin1String("changestate")) + setCurrentBlockState(state++); + } + + int state; + bool highlighted; +}; + +void tst_QSyntaxHighlighter::stopHighlightingWhenStateDoesNotChange() +{ + cursor.insertText("state"); + cursor.insertBlock(); + cursor.insertText("changestate"); + cursor.insertBlock(); + cursor.insertText("keepstate"); + cursor.insertBlock(); + cursor.insertText("changestate"); + cursor.insertBlock(); + cursor.insertText("changestate"); + + StateTestHighlighter *hl = new StateTestHighlighter(doc); + QTest::qWait(100); + QVERIFY(hl->highlighted); + + hl->reset(); + + // turn the text of the first block into 'changestate' + cursor.movePosition(QTextCursor::Start); + cursor.insertText("change"); + + // verify that we highlighted only to the 'keepstate' block, + // not beyond + QCOMPARE(hl->state, 2); +} + +void tst_QSyntaxHighlighter::unindent() +{ + const QString spaces(" "); + const QString text("Foobar"); + QString plainText; + for (int i = 0; i < 5; ++i) { + cursor.insertText(spaces + text); + cursor.insertBlock(); + + plainText += spaces; + plainText += text; + plainText += QLatin1Char('\n'); + } + QCOMPARE(doc->toPlainText(), plainText); + + TestHighlighter *hl = new TestHighlighter(doc); + hl->callCount = 0; + + cursor.movePosition(QTextCursor::Start); + cursor.beginEditBlock(); + + plainText.clear(); + for (int i = 0; i < 5; ++i) { + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4); + cursor.removeSelectedText(); + cursor.movePosition(QTextCursor::NextBlock); + + plainText += text; + plainText += QLatin1Char('\n'); + } + + cursor.endEditBlock(); + QCOMPARE(doc->toPlainText(), plainText); + QCOMPARE(hl->callCount, 5); +} + +void tst_QSyntaxHighlighter::highlightToEndOfDocument() +{ + TestHighlighter *hl = new TestHighlighter(doc); + hl->callCount = 0; + + cursor.movePosition(QTextCursor::Start); + cursor.beginEditBlock(); + + cursor.insertText("Hello"); + cursor.insertBlock(); + cursor.insertBlock(); + cursor.insertText("World"); + cursor.insertBlock(); + + cursor.endEditBlock(); + + QCOMPARE(hl->callCount, 4); +} + +void tst_QSyntaxHighlighter::highlightToEndOfDocument2() +{ + TestHighlighter *hl = new TestHighlighter(doc); + hl->callCount = 0; + + cursor.movePosition(QTextCursor::End); + cursor.beginEditBlock(); + QTextBlockFormat fmt; + fmt.setAlignment(Qt::AlignLeft); + cursor.setBlockFormat(fmt); + cursor.insertText("Three\nLines\nHere"); + cursor.endEditBlock(); + + QCOMPARE(hl->callCount, 3); +} + +void tst_QSyntaxHighlighter::preservePreeditArea() +{ + QList<QTextLayout::FormatRange> formats; + QTextLayout::FormatRange range; + range.start = 0; + range.length = 8; + range.format.setForeground(Qt::blue); + formats << range; + range.start = 9; + range.length = 1; + range.format.setForeground(Qt::red); + formats << range; + TestHighlighter *hl = new TestHighlighter(formats, doc); + + doc->setPlainText("Hello World"); + cursor.movePosition(QTextCursor::Start); + + QTextLayout *layout = cursor.block().layout(); + + layout->setPreeditArea(5, QString("foo")); + range.start = 5; + range.length = 3; + range.format.setFontUnderline(true); + formats.clear(); + formats << range; + + hl->callCount = 0; + + cursor.beginEditBlock(); + layout->setAdditionalFormats(formats); + cursor.endEditBlock(); + + QCOMPARE(hl->callCount, 1); + + formats = layout->additionalFormats(); + QCOMPARE(formats.count(), 3); + + range = formats.at(0); + + QCOMPARE(range.start, 5); + QCOMPARE(range.length, 3); + QVERIFY(range.format.fontUnderline()); + + range = formats.at(1); + QCOMPARE(range.start, 0); + QCOMPARE(range.length, 8 + 3); + + range = formats.at(2); + QCOMPARE(range.start, 9 + 3); + QCOMPARE(range.length, 1); +} + +void tst_QSyntaxHighlighter::task108530() +{ + TestHighlighter *hl = new TestHighlighter(doc); + + cursor.insertText("test"); + hl->callCount = 0; + hl->highlightedText.clear(); + cursor.movePosition(QTextCursor::Start); + cursor.insertBlock(); + + QCOMPARE(hl->highlightedText, QString("test")); + QCOMPARE(hl->callCount, 2); +} + +void tst_QSyntaxHighlighter::avoidUnnecessaryRehighlight() +{ + TestHighlighter *hl = new TestHighlighter(doc); + QVERIFY(!hl->highlighted); + + doc->setPlainText("Hello World"); + QVERIFY(hl->highlighted); + + hl->highlighted = false; + QTest::qWait(100); + QVERIFY(!hl->highlighted); +} + +void tst_QSyntaxHighlighter::noContentsChangedDuringHighlight() +{ + QList<QTextLayout::FormatRange> formats; + QTextLayout::FormatRange range; + range.start = 0; + range.length = 10; + range.format.setForeground(Qt::blue); + formats.append(range); + + TestHighlighter *hl = new TestHighlighter(formats, doc); + + lout->documentChangedCalled = false; + QTextCursor cursor(doc); + + QSignalSpy contentsChangedSpy(doc, SIGNAL(contentsChanged())); + cursor.insertText("Hello World"); + + QCOMPARE(contentsChangedSpy.count(), 1); + QVERIFY(hl->highlighted); + QVERIFY(lout->documentChangedCalled); +} + +void tst_QSyntaxHighlighter::rehighlight() +{ + TestHighlighter *hl = new TestHighlighter(doc); + hl->callCount = 0; + doc->setPlainText("Hello"); + hl->callCount = 0; + hl->rehighlight(); + QCOMPARE(hl->callCount, 1); +} + +void tst_QSyntaxHighlighter::rehighlightBlock() +{ + TestHighlighter *hl = new TestHighlighter(doc); + + cursor.movePosition(QTextCursor::Start); + cursor.beginEditBlock(); + cursor.insertText("Hello"); + cursor.insertBlock(); + cursor.insertText("World"); + cursor.endEditBlock(); + + hl->callCount = 0; + hl->highlightedText.clear(); + QTextBlock block = doc->begin(); + hl->rehighlightBlock(block); + + QCOMPARE(hl->highlightedText, QString("Hello")); + QCOMPARE(hl->callCount, 1); + + hl->callCount = 0; + hl->highlightedText.clear(); + hl->rehighlightBlock(block.next()); + + QCOMPARE(hl->highlightedText, QString("World")); + QCOMPARE(hl->callCount, 1); +} + +QTEST_MAIN(tst_QSyntaxHighlighter) +#include "tst_qsyntaxhighlighter.moc" diff --git a/tests/auto/gui/text/qtextblock/.gitignore b/tests/auto/gui/text/qtextblock/.gitignore new file mode 100644 index 0000000000..648a522140 --- /dev/null +++ b/tests/auto/gui/text/qtextblock/.gitignore @@ -0,0 +1 @@ +tst_qtextblock diff --git a/tests/auto/gui/text/qtextblock/qtextblock.pro b/tests/auto/gui/text/qtextblock/qtextblock.pro new file mode 100644 index 0000000000..d50ef5ca78 --- /dev/null +++ b/tests/auto/gui/text/qtextblock/qtextblock.pro @@ -0,0 +1,9 @@ +load(qttest_p4) + +QT += widgets widgets-private +QT += core-private gui-private + +SOURCES += tst_qtextblock.cpp + + + diff --git a/tests/auto/gui/text/qtextblock/tst_qtextblock.cpp b/tests/auto/gui/text/qtextblock/tst_qtextblock.cpp new file mode 100644 index 0000000000..b04a6f5559 --- /dev/null +++ b/tests/auto/gui/text/qtextblock/tst_qtextblock.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + + +#define protected public +#include <qtextdocument.h> +#undef protected +#include <qdebug.h> +#ifndef Q_WS_WIN +#include <private/qtextdocument_p.h> +#endif + + + +#include <qtextobject.h> +#include <qtextcursor.h> + + +//TESTED_FILES= + +QT_FORWARD_DECLARE_CLASS(QTextDocument) + +class tst_QTextBlock : public QObject +{ + Q_OBJECT + +public: + tst_QTextBlock(); + + +public slots: + void init(); + void cleanup(); +private slots: + void fragmentOverBlockBoundaries(); + void excludeParagraphSeparatorFragment(); + void backwardsBlockIterator(); + void previousBlock_qtbug18026(); + void removedBlock_qtbug18500(); + +private: + QTextDocument *doc; + QTextCursor cursor; +}; + +tst_QTextBlock::tst_QTextBlock() +{} + +void tst_QTextBlock::init() +{ + doc = new QTextDocument; + cursor = QTextCursor(doc); +} + +void tst_QTextBlock::cleanup() +{ + cursor = QTextCursor(); + delete doc; + doc = 0; +} + +void tst_QTextBlock::fragmentOverBlockBoundaries() +{ + /* this creates two fragments in the piecetable: + * 1) 'hello<parag separator here>world' + * 2) '<parag separator>' + * (they are not united because the former was interested after the latter, + * hence their position in the pt buffer is the other way around) + */ + cursor.insertText("Hello"); + cursor.insertBlock(); + cursor.insertText("World"); + + cursor.movePosition(QTextCursor::Start); + + const QTextDocument *doc = cursor.block().document(); + QVERIFY(doc); + // Block separators are always a fragment of their self. Thus: + // |Hello|\b|World|\b| +#if !defined(Q_WS_WIN) && !defined(Q_WS_S60) + QVERIFY(doc->docHandle()->fragmentMap().numNodes() == 4); +#endif + QCOMPARE(cursor.block().text(), QString("Hello")); + cursor.movePosition(QTextCursor::NextBlock); + QCOMPARE(cursor.block().text(), QString("World")); +} + +void tst_QTextBlock::excludeParagraphSeparatorFragment() +{ + QTextCharFormat fmt; + fmt.setForeground(Qt::blue); + cursor.insertText("Hello", fmt); + + QTextBlock block = doc->begin(); + QVERIFY(block.isValid()); + + QTextBlock::Iterator it = block.begin(); + + QTextFragment fragment = it.fragment(); + QVERIFY(fragment.isValid()); + QCOMPARE(fragment.text(), QString("Hello")); + + ++it; + QVERIFY(it.atEnd()); + QVERIFY(it == block.end()); +} + +void tst_QTextBlock::backwardsBlockIterator() +{ + QTextCharFormat fmt; + + fmt.setForeground(Qt::magenta); + cursor.insertText("A", fmt); + + fmt.setForeground(Qt::red); + cursor.insertText("A", fmt); + + fmt.setForeground(Qt::magenta); + cursor.insertText("A", fmt); + + QTextBlock block = doc->begin(); + QVERIFY(block.isValid()); + + QTextBlock::Iterator it = block.begin(); + QCOMPARE(it.fragment().position(), 0); + ++it; + QCOMPARE(it.fragment().position(), 1); + ++it; + + QCOMPARE(it.fragment().position(), 2); + + --it; + QCOMPARE(it.fragment().position(), 1); + --it; + QCOMPARE(it.fragment().position(), 0); +} + +void tst_QTextBlock::previousBlock_qtbug18026() +{ + QTextBlock last = doc->end().previous(); + QVERIFY(last.isValid()); +} + +void tst_QTextBlock::removedBlock_qtbug18500() +{ + cursor.insertText("line 1\nline 2\nline 3 \nline 4\n"); + cursor.setPosition(7); + QTextBlock block = cursor.block(); + cursor.setPosition(21, QTextCursor::KeepAnchor); + + cursor.removeSelectedText(); + QVERIFY(!block.isValid()); +} + +QTEST_MAIN(tst_QTextBlock) +#include "tst_qtextblock.moc" diff --git a/tests/auto/gui/text/qtextcursor/.gitignore b/tests/auto/gui/text/qtextcursor/.gitignore new file mode 100644 index 0000000000..b9b1f8e7d6 --- /dev/null +++ b/tests/auto/gui/text/qtextcursor/.gitignore @@ -0,0 +1 @@ +tst_qtextcursor diff --git a/tests/auto/gui/text/qtextcursor/qtextcursor.pro b/tests/auto/gui/text/qtextcursor/qtextcursor.pro new file mode 100644 index 0000000000..828b90ca16 --- /dev/null +++ b/tests/auto/gui/text/qtextcursor/qtextcursor.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +SOURCES += tst_qtextcursor.cpp + + + diff --git a/tests/auto/gui/text/qtextcursor/tst_qtextcursor.cpp b/tests/auto/gui/text/qtextcursor/tst_qtextcursor.cpp new file mode 100644 index 0000000000..2b0ba422e0 --- /dev/null +++ b/tests/auto/gui/text/qtextcursor/tst_qtextcursor.cpp @@ -0,0 +1,1862 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + + +#include <qtextdocument.h> +#include <qtexttable.h> +#include <qvariant.h> +#include <qtextdocumentfragment.h> +#include <qabstracttextdocumentlayout.h> +#include <qtextlayout.h> +#include <qtextcursor.h> +#include <qdebug.h> + +//TESTED_FILES=gui/text/qtextcursor.cpp gui/text/qtextcursor_p.h + +QT_FORWARD_DECLARE_CLASS(QTextDocument) + +class tst_QTextCursor : public QObject +{ + Q_OBJECT + +public: + tst_QTextCursor(); + + +public slots: + void init(); + void cleanup(); +private slots: + void navigation1(); + void navigation2_data(); + void navigation2(); + void navigation3(); + void navigation4(); + void navigation5(); + void navigation6(); + void navigation7(); + void navigation8(); + void navigation9(); + void navigation10(); + void movePositionEndOfLine(); + void insertBlock(); + void insertWithBlockSeparator1(); + void insertWithBlockSeparator2(); + void insertWithBlockSeparator3(); + void insertWithBlockSeparator4(); + void clearObjectType1(); + void clearObjectType2(); + void clearObjectType3(); + void comparisonOperators1(); + void comparisonOperators2(); + void selection1(); + void dontCopyTableAttributes(); + + void checkFrame1(); + void checkFrame2(); + + void tableMovement(); + void selectionsInTable(); + + void insertBlockToUseCharFormat(); + + void selectedText(); + + void insertBlockShouldRemoveSelection(); + void insertBlockShouldRemoveSelection2(); + void mergeCellShouldUpdateSelection(); + + void joinPreviousEditBlock(); + + void setBlockFormatInTable(); + + void blockCharFormat(); + void blockCharFormat2(); + void blockCharFormat3(); + void blockCharFormatOnSelection(); + + void anchorInitialized1(); + void anchorInitialized2(); + void anchorInitialized3(); + + void selectWord(); + void selectWordWithSeparators_data(); + void selectWordWithSeparators(); + void startOfWord(); + void selectBlock(); + void selectVisually(); + + void insertText(); + + void insertFragmentShouldUseCurrentCharFormat(); + + void endOfLine(); + + void editBlocksDuringRemove(); + void selectAllDuringRemove(); + + void update_data(); + void update(); + + void disallowSettingObjectIndicesOnCharFormats(); + + void blockAndColumnNumber(); + + void clearCells(); + + void task244408_wordUnderCursor_data(); + void task244408_wordUnderCursor(); + + void adjustCursorsOnInsert(); + + void cursorPositionWithBlockUndoAndRedo(); + void cursorPositionWithBlockUndoAndRedo2(); + void cursorPositionWithBlockUndoAndRedo3(); + +private: + int blockCount(); + + QTextDocument *doc; + QTextCursor cursor; +}; + +Q_DECLARE_METATYPE(QList<QVariant>) + +tst_QTextCursor::tst_QTextCursor() +{} + +void tst_QTextCursor::init() +{ + doc = new QTextDocument; + cursor = QTextCursor(doc); +} + +void tst_QTextCursor::cleanup() +{ + cursor = QTextCursor(); + delete doc; + doc = 0; +} + +void tst_QTextCursor::navigation1() +{ + + cursor.insertText("Hello World"); + QVERIFY(doc->toPlainText() == "Hello World"); + + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.position() == 11); + cursor.deletePreviousChar(); + QVERIFY(cursor.position() == 10); + cursor.deletePreviousChar(); + cursor.deletePreviousChar(); + cursor.deletePreviousChar(); + cursor.deletePreviousChar(); + cursor.deletePreviousChar(); + QVERIFY(doc->toPlainText() == "Hello"); + + QTextCursor otherCursor(doc); + otherCursor.movePosition(QTextCursor::Start); + otherCursor.movePosition(QTextCursor::Right); + cursor = otherCursor; + cursor.movePosition(QTextCursor::Right); + QVERIFY(cursor != otherCursor); + otherCursor.insertText("Hey"); + QVERIFY(cursor.position() == 5); + + doc->undo(); + QVERIFY(cursor.position() == 2); + doc->redo(); + QVERIFY(cursor.position() == 5); + + doc->undo(); + + doc->undo(); + QVERIFY(doc->toPlainText() == "Hello World"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 6); + QVERIFY(cursor.position() == 6); + otherCursor = cursor; + otherCursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, 2); + otherCursor.deletePreviousChar(); + otherCursor.deletePreviousChar(); + otherCursor.deletePreviousChar(); + QVERIFY(cursor.position() == 5); + + cursor.movePosition(QTextCursor::End); + cursor.insertBlock(); + { + int oldPos = cursor.position(); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.position() == oldPos); + } + QVERIFY(cursor.atBlockStart()); + QVERIFY(cursor.position() == 9); + + QTextCharFormat fmt; + fmt.setForeground(Qt::blue); + cursor.insertText("Test", fmt); + QVERIFY(fmt == cursor.charFormat()); + QVERIFY(cursor.position() == 13); +} + +void tst_QTextCursor::navigation2_data() +{ + QTest::addColumn<QStringList>("sl"); + QTest::addColumn<QList<QVariant> >("movement"); + QTest::addColumn<int>("finalPos"); + + QTest::newRow("startBlock1") << QStringList("Happy happy happy joy joy joy") + << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock)) << 0; + QTest::newRow("endBlock1") << QStringList("Happy happy happy joy joy joy") + << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock) + << QVariant(QTextCursor::EndOfBlock)) << 29; + QTest::newRow("startBlock2") << QStringList("Happy happy happy joy joy joy") + << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock) + << QVariant(QTextCursor::EndOfBlock) + << QVariant(QTextCursor::StartOfBlock)) << 0; + QTest::newRow("endBlock2") << QStringList("Happy happy happy joy joy joy") + << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock) + << QVariant(QTextCursor::EndOfBlock) + << QVariant(QTextCursor::StartOfBlock) + << QVariant(QTextCursor::EndOfBlock) + ) << 29; + QTest::newRow("multiBlock1") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock)) + << 18; + QTest::newRow("multiBlock2") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock) + << QVariant(QTextCursor::EndOfBlock)) + << 29; + QTest::newRow("multiBlock3") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::StartOfBlock) + << QVariant(QTextCursor::StartOfBlock)) + << 18; + QTest::newRow("multiBlock4") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::Start) + << QVariant(QTextCursor::EndOfBlock)) + << 17; + QTest::newRow("multiBlock5") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::Start) + << QVariant(QTextCursor::EndOfBlock) + << QVariant(QTextCursor::EndOfBlock)) + << 17; + QTest::newRow("multiBlock6") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::End) + << QVariant(QTextCursor::StartOfBlock)) + << 18; + QTest::newRow("multiBlock7") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock)) + << 0; + QTest::newRow("multiBlock8") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock) + << QVariant(QTextCursor::EndOfBlock)) + << 17; + QTest::newRow("multiBlock9") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock) + << QVariant(QTextCursor::NextBlock)) + << 18; + QTest::newRow("multiBlock10") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock) + << QVariant(QTextCursor::NextBlock) + << QVariant(QTextCursor::NextBlock)) + << 18; + QTest::newRow("multiBlock11") << (QStringList() << QString("Happy happy happy") + << QString("Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousBlock) + << QVariant(QTextCursor::NextBlock) + << QVariant(QTextCursor::EndOfBlock)) + << 29; + QTest::newRow("PreviousWord1") << (QStringList() << QString("Happy happy happy Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord)) + << 26; + QTest::newRow("PreviousWord2") << (QStringList() << QString("Happy happy happy Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord) + << QVariant(QTextCursor::PreviousWord)) + << 22; + QTest::newRow("EndWord1") << (QStringList() << QString("Happy happy happy Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord) + << QVariant(QTextCursor::PreviousWord) + << QVariant(QTextCursor::EndOfWord)) + << 25; + QTest::newRow("NextWord1") << (QStringList() << QString("Happy happy happy Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord) + << QVariant(QTextCursor::PreviousWord) + << QVariant(QTextCursor::NextWord)) + << 26; + QTest::newRow("NextWord2") << (QStringList() << QString("Happy happy happy Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::Start) + << QVariant(QTextCursor::NextWord) + << QVariant(QTextCursor::EndOfWord)) + << 11; + QTest::newRow("StartWord1") << (QStringList() << QString("Happy happy happy Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousWord) + << QVariant(QTextCursor::PreviousWord) + << QVariant(QTextCursor::StartOfWord)) + << 22; + QTest::newRow("StartWord3") << (QStringList() << QString("Happy happy happy Joy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::Start) + << QVariant(QTextCursor::NextWord) + << QVariant(QTextCursor::EndOfWord) + << QVariant(QTextCursor::StartOfWord)) + << 6; + + QTest::newRow("PreviousCharacter") << (QStringList() << QString("Happy happy Joy Joy")) + << (QList<QVariant>() << QVariant(QTextCursor::PreviousCharacter) + << QVariant(QTextCursor::PreviousCharacter)) + << 17; +} + +void tst_QTextCursor::navigation2() +{ + QFETCH(QStringList, sl); + QFETCH(QList<QVariant>, movement); + int i; + for (i = 0; i < sl.size(); ++i) { + cursor.insertText(sl.at(i)); + if (i < sl.size() - 1) + cursor.insertBlock(); + } + + for (i = 0; i < movement.size(); ++i) + cursor.movePosition(QTextCursor::MoveOperation(movement.at(i).toInt())); + QTEST(cursor.position(), "finalPos"); +} + +void tst_QTextCursor::navigation3() +{ + cursor.insertText("a"); + cursor.deletePreviousChar(); + QCOMPARE(cursor.position(), 0); + QVERIFY(doc->toPlainText().isEmpty()); +} + +void tst_QTextCursor::navigation4() +{ + cursor.insertText(" Test "); + + cursor.setPosition(4); + cursor.movePosition(QTextCursor::EndOfWord); + QCOMPARE(cursor.position(), 6); +} + +void tst_QTextCursor::navigation5() +{ + cursor.insertText("Test"); + cursor.insertBlock(); + cursor.insertText("Test"); + + cursor.setPosition(0); + cursor.movePosition(QTextCursor::EndOfBlock); + QCOMPARE(cursor.position(), 4); +} + +void tst_QTextCursor::navigation6() +{ + // triger creation of document layout, so that QTextLines are there + doc->documentLayout(); + doc->setTextWidth(1000); + + cursor.insertText("Test "); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::EndOfLine); + QCOMPARE(cursor.position(), 8); +} + +void tst_QTextCursor::navigation7() +{ + QVERIFY(doc->isEmpty()); + for (int i = QTextCursor::Start; i <= QTextCursor::WordRight; ++i) + QVERIFY(!cursor.movePosition(QTextCursor::MoveOperation(i))); + + doc->setPlainText("Hello World"); + cursor.movePosition(QTextCursor::Start); + do { + } while (cursor.movePosition(QTextCursor::NextCharacter)); + QVERIFY(true /*reached*/); +} + +void tst_QTextCursor::navigation8() +{ + cursor.insertList(QTextListFormat::ListDecimal); + QCOMPARE(cursor.position(), 1); + cursor.insertText("foo"); + QCOMPARE(cursor.position(), 4); + + cursor.insertList(QTextListFormat::ListCircle); + QCOMPARE(cursor.position(), 5); + cursor.insertText("something"); + QCOMPARE(cursor.position(), 14); + + cursor.movePosition(QTextCursor::PreviousCharacter); + QCOMPARE(cursor.position(), 13); + + cursor.setPosition(2); + cursor.movePosition(QTextCursor::NextCharacter); + QCOMPARE(cursor.position(), 3); +} + +void tst_QTextCursor::navigation9() +{ + cursor.insertText("Hello &-=+\t World"); + cursor.movePosition(QTextCursor::PreviousWord); + QCOMPARE(cursor.position(), 15); + cursor.movePosition(QTextCursor::PreviousWord); + QCOMPARE(cursor.position(), 7); + cursor.movePosition(QTextCursor::PreviousWord); + QCOMPARE(cursor.position(), 0); + cursor.movePosition(QTextCursor::NextWord); + QCOMPARE(cursor.position(), 7); + cursor.movePosition(QTextCursor::NextWord); + QCOMPARE(cursor.position(), 15); +} + +void tst_QTextCursor::navigation10() +{ + doc->setHtml("<html><p>just a simple paragraph.</p>" + "<table>" + "<tr><td>Cell number 1</td><td>another cell</td><td></td><td>previous</br>is</br>empty</td></tr>" + "<tr><td>row 2</td><td colspan=\"2\">foo bar</td><td>last cell</td></tr>" + "<tr><td colspan=\"3\">row 3</td><td>a</td></tr>" + "</table></html"); + QCOMPARE(cursor.position(), 101); // end of document + cursor.setPosition(0); + QCOMPARE(cursor.position(), 0); + bool ok = cursor.movePosition(QTextCursor::EndOfLine); + QVERIFY(ok); + QCOMPARE(cursor.position(), 24); + ok = cursor.movePosition(QTextCursor::NextBlock); + QCOMPARE(cursor.position(), 25); // cell 1 + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 39); // another.. + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 52); // empty + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 53); // last on row 1 + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 69); // row 2 + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 75); + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 83); + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 93); // row 3 + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 99); + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok == false); + QCOMPARE(cursor.position(), 99); // didn't move. + QVERIFY(cursor.currentTable()); + + // same thing in reverse... + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 93); + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 83); + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 75); + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 69); + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 53); + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 52); + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 39); + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 25); + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(!ok); + QCOMPARE(cursor.position(), 25); // can't leave the table + + ok = cursor.movePosition(QTextCursor::NextRow); + QVERIFY(ok); + QCOMPARE(cursor.position(), 69); + ok = cursor.movePosition(QTextCursor::NextRow); + QVERIFY(ok); + QCOMPARE(cursor.position(), 93); + ok = cursor.movePosition(QTextCursor::NextRow); + QVERIFY(!ok); + QCOMPARE(cursor.position(), 93); // didn't move + + ok = cursor.movePosition(QTextCursor::PreviousRow); + QVERIFY(ok); + QCOMPARE(cursor.position(), 83); // last col in row 2 + ok = cursor.movePosition(QTextCursor::PreviousRow); + QVERIFY(ok); + QCOMPARE(cursor.position(), 53); // last col in row 1 + ok = cursor.movePosition(QTextCursor::PreviousRow); + QVERIFY(!ok); + QCOMPARE(cursor.position(), 53); + + // test usecase of jumping over a cell + doc->clear(); + doc->setHtml("<html><table>tr><td rowspan=\"2\">a</td><td>b</td></tr><tr><td>c</td></tr></table></html>"); + cursor.setPosition(1); // a + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 3); // b + ok = cursor.movePosition(QTextCursor::NextCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 5); // c + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 3); // b + ok = cursor.movePosition(QTextCursor::PreviousCell); + QVERIFY(ok); + QCOMPARE(cursor.position(), 1); // a +} + +void tst_QTextCursor::insertBlock() +{ + QTextBlockFormat fmt; + fmt.setTopMargin(100); + cursor.insertBlock(fmt); + QVERIFY(cursor.position() == 1); + QVERIFY(cursor.blockFormat() == fmt); +} + +void tst_QTextCursor::insertWithBlockSeparator1() +{ + QString text = "Hello" + QString(QChar::ParagraphSeparator) + "World"; + + cursor.insertText(text); + + cursor.movePosition(QTextCursor::PreviousBlock); + QVERIFY(cursor.position() == 0); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.position() == 6); +} + +void tst_QTextCursor::insertWithBlockSeparator2() +{ + cursor.insertText(QString(QChar::ParagraphSeparator)); + QVERIFY(cursor.position() == 1); +} + +void tst_QTextCursor::insertWithBlockSeparator3() +{ + cursor.insertText(QString(QChar::ParagraphSeparator) + "Hi" + QString(QChar::ParagraphSeparator)); + QVERIFY(cursor.position() == 4); +} + +void tst_QTextCursor::insertWithBlockSeparator4() +{ + cursor.insertText(QString(QChar::ParagraphSeparator) + QString(QChar::ParagraphSeparator)); + QVERIFY(cursor.position() == 2); +} + +void tst_QTextCursor::clearObjectType1() +{ + cursor.insertImage("test.png"); + QVERIFY(cursor.charFormat().isValid()); + QVERIFY(cursor.charFormat().isImageFormat()); + cursor.insertText("Hey"); + QVERIFY(cursor.charFormat().isValid()); + QVERIFY(!cursor.charFormat().isImageFormat()); +} + +void tst_QTextCursor::clearObjectType2() +{ + cursor.insertImage("test.png"); + QVERIFY(cursor.charFormat().isValid()); + QVERIFY(cursor.charFormat().isImageFormat()); + cursor.insertBlock(); + QVERIFY(cursor.charFormat().isValid()); + QVERIFY(!cursor.charFormat().isImageFormat()); +} + +void tst_QTextCursor::clearObjectType3() +{ + // like clearObjectType2 but tests different insertBlock overload + cursor.insertImage("test.png"); + QVERIFY(cursor.charFormat().isValid()); + QVERIFY(cursor.charFormat().isImageFormat()); + QTextBlockFormat bfmt; + bfmt.setAlignment(Qt::AlignRight); + cursor.insertBlock(bfmt); + QVERIFY(cursor.charFormat().isValid()); + QVERIFY(!cursor.charFormat().isImageFormat()); +} + +void tst_QTextCursor::comparisonOperators1() +{ + cursor.insertText("Hello World"); + + cursor.movePosition(QTextCursor::PreviousWord); + + QTextCursor startCursor = cursor; + startCursor.movePosition(QTextCursor::Start); + + QVERIFY(startCursor < cursor); + + QTextCursor midCursor = startCursor; + midCursor.movePosition(QTextCursor::NextWord); + + QVERIFY(midCursor <= cursor); + QVERIFY(midCursor == cursor); + QVERIFY(midCursor >= cursor); + + QVERIFY(midCursor > startCursor); + + QVERIFY(midCursor != startCursor); + QVERIFY(!(midCursor == startCursor)); + + QTextCursor nullCursor; + + QVERIFY(!(startCursor < nullCursor)); + QVERIFY(!(nullCursor < nullCursor)); + QVERIFY(nullCursor < startCursor); + + QVERIFY(nullCursor <= startCursor); + QVERIFY(!(startCursor <= nullCursor)); + + QVERIFY(!(nullCursor >= startCursor)); + QVERIFY(startCursor >= nullCursor); + + QVERIFY(!(nullCursor > startCursor)); + QVERIFY(!(nullCursor > nullCursor)); + QVERIFY(startCursor > nullCursor); +} + +void tst_QTextCursor::comparisonOperators2() +{ + QTextDocument doc1; + QTextDocument doc2; + + QTextCursor cursor1(&doc1); + QTextCursor cursor2(&doc2); + + QVERIFY(cursor1 != cursor2); + QVERIFY(cursor1 == QTextCursor(&doc1)); +} + +void tst_QTextCursor::selection1() +{ + cursor.insertText("Hello World"); + + cursor.setPosition(0); + cursor.clearSelection(); + cursor.setPosition(4, QTextCursor::KeepAnchor); + + QCOMPARE(cursor.selectionStart(), 0); + QCOMPARE(cursor.selectionEnd(), 4); +} + +void tst_QTextCursor::dontCopyTableAttributes() +{ + /* when pressing 'enter' inside a cell it shouldn't + * enlarge the table by adding another cell but just + * extend the cell */ + QTextTable *table = cursor.insertTable(2, 2); + QVERIFY(cursor == table->cellAt(0, 0).firstCursorPosition()); + cursor.insertBlock(); + QCOMPARE(table->columns(), 2); +} + +void tst_QTextCursor::checkFrame1() +{ + QVERIFY(cursor.position() == 0); + QPointer<QTextFrame> frame = cursor.insertFrame(QTextFrameFormat()); + QVERIFY(frame != 0); + + QTextFrame *root = frame->parentFrame(); + QVERIFY(root != 0); + + QVERIFY(frame->firstPosition() == 1); + QVERIFY(frame->lastPosition() == 1); + QVERIFY(frame->parentFrame() != 0); + QVERIFY(root->childFrames().size() == 1); + + QVERIFY(cursor.position() == 1); + QVERIFY(cursor.selectionStart() == 1); + QVERIFY(cursor.selectionEnd() == 1); + + doc->undo(); + + QVERIFY(!frame); + QVERIFY(root->childFrames().size() == 0); + + QVERIFY(cursor.position() == 0); + QVERIFY(cursor.selectionStart() == 0); + QVERIFY(cursor.selectionEnd() == 0); + + doc->redo(); + + frame = doc->frameAt(1); + + QVERIFY(frame); + QVERIFY(frame->firstPosition() == 1); + QVERIFY(frame->lastPosition() == 1); + QVERIFY(frame->parentFrame() != 0); + QVERIFY(root->childFrames().size() == 1); + + QVERIFY(cursor.position() == 1); + QVERIFY(cursor.selectionStart() == 1); + QVERIFY(cursor.selectionEnd() == 1); + +// cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); +// QVERIFY(cursor.position() == 2); +// QVERIFY(cursor.selectionStart() == 0); +// QVERIFY(cursor.selectionEnd() == 2); +} + +void tst_QTextCursor::checkFrame2() +{ + QVERIFY(cursor.position() == 0); + cursor.insertText("A"); + QVERIFY(cursor.position() == 1); + cursor.movePosition(QTextCursor::Start, QTextCursor::KeepAnchor); + + QPointer<QTextFrame> frame = cursor.insertFrame(QTextFrameFormat()); + QTextFrame *root = frame->parentFrame(); + + QVERIFY(frame->firstPosition() == 1); + QVERIFY(frame->lastPosition() == 2); + QVERIFY(frame->parentFrame() != 0); + QVERIFY(root->childFrames().size() == 1); + + QVERIFY(cursor.position() == 1); + QVERIFY(cursor.selectionStart() == 1); + QVERIFY(cursor.selectionEnd() == 2); + + doc->undo(); + + QVERIFY(!frame); + QVERIFY(root->childFrames().size() == 0); + + QVERIFY(cursor.position() == 0); + QVERIFY(cursor.selectionStart() == 0); + QVERIFY(cursor.selectionEnd() == 1); + + doc->redo(); + + frame = doc->frameAt(1); + + QVERIFY(frame); + QVERIFY(frame->firstPosition() == 1); + QVERIFY(frame->lastPosition() == 2); + QVERIFY(frame->parentFrame() != 0); + QVERIFY(root->childFrames().size() == 1); + + QVERIFY(cursor.position() == 1); + QVERIFY(cursor.selectionStart() == 1); + QVERIFY(cursor.selectionEnd() == 2); + + cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor); + QVERIFY(cursor.position() == 0); + QVERIFY(cursor.selectionStart() == 0); + QVERIFY(cursor.selectionEnd() == 3); +} + +void tst_QTextCursor::insertBlockToUseCharFormat() +{ + QTextCharFormat fmt; + fmt.setForeground(Qt::blue); + cursor.insertText("Hello", fmt); + QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue)); + + cursor.insertBlock(); + QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::blue)); + + fmt.setForeground(Qt::red); + cursor.insertText("Hello\nWorld", fmt); + cursor.insertText("Blah"); + QCOMPARE(cursor.charFormat().foreground().color(), QColor(Qt::red)); + + // ### we might want a testcase for createTable, too, as it calls insertBlock, too, + // and we might want to have the char format copied (the one that gets inserted + // as table separators, that are undeletable) +} + +void tst_QTextCursor::tableMovement() +{ + QVERIFY(cursor.position() == 0); + cursor.insertText("AA"); + QVERIFY(cursor.position() == 2); + cursor.movePosition(QTextCursor::Left); + + cursor.insertTable(3, 3); + QCOMPARE(cursor.position(), 2); + + cursor.movePosition(QTextCursor::Down); + QCOMPARE(cursor.position(), 5); + + cursor.movePosition(QTextCursor::Right); + QCOMPARE(cursor.position(), 6); + + cursor.movePosition(QTextCursor::Up); + QCOMPARE(cursor.position(), 3); + + cursor.movePosition(QTextCursor::Right); + QCOMPARE(cursor.position(), 4); + + cursor.movePosition(QTextCursor::Right); + QCOMPARE(cursor.position(), 5); + + cursor.movePosition(QTextCursor::Up); + QCOMPARE(cursor.position(), 2); + + cursor.movePosition(QTextCursor::Up); + QCOMPARE(cursor.position(), 0); + +} + +void tst_QTextCursor::selectionsInTable() +{ + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + + cursor = table->cellAt(0, 0).lastCursorPosition(); + QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor)); + QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor) == false); + + cursor = table->cellAt(1, 0).lastCursorPosition(); + QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor)); + QVERIFY(cursor.movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor) == false); + + cursor = table->cellAt(0, 1).firstCursorPosition(); + QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor)); + QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor) == false); + + cursor = table->cellAt(1, 1).firstCursorPosition(); + QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor)); + QVERIFY(cursor.movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor) == false); +} + +void tst_QTextCursor::selectedText() +{ + cursor.insertText("Hello World"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + + QCOMPARE(cursor.selectedText(), QString("Hello World")); +} + +void tst_QTextCursor::insertBlockShouldRemoveSelection() +{ + cursor.insertText("Hello World"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + + QVERIFY(cursor.hasSelection()); + QCOMPARE(cursor.selectedText(), QString("Hello")); + + cursor.insertBlock(); + + QVERIFY(!cursor.hasSelection()); + QVERIFY(doc->toPlainText().indexOf("Hello") == -1); +} + +void tst_QTextCursor::insertBlockShouldRemoveSelection2() +{ + cursor.insertText("Hello World"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor); + + QVERIFY(cursor.hasSelection()); + QCOMPARE(cursor.selectedText(), QString("Hello")); + + QTextBlockFormat fmt = cursor.blockFormat(); + cursor.insertBlock(fmt); + + QVERIFY(!cursor.hasSelection()); + QVERIFY(doc->toPlainText().indexOf("Hello") == -1); +} + +void tst_QTextCursor::mergeCellShouldUpdateSelection() +{ + QTextTable *table = cursor.insertTable(4, 4); + cursor.setPosition(table->cellAt(0, 0).firstPosition()); + cursor.setPosition(table->cellAt(3, 0).firstPosition(), QTextCursor::KeepAnchor); // aka bottom left + int firstRow, numRows, firstColumn, numColumns; + cursor.selectedTableCells(&firstRow, &numRows, &firstColumn, &numColumns); + QCOMPARE(firstRow, 0); + QCOMPARE(numRows, 4); + QCOMPARE(firstColumn, 0); + QCOMPARE(numColumns, 1); + + table->removeColumns(firstColumn, numColumns); + + QCOMPARE(cursor.anchor(), table->cellAt(0, 0).firstPosition()); + QCOMPARE(cursor.position(), table->cellAt(0, 0).firstPosition()); + QCOMPARE(cursor.position(), cursor.anchor()); // empty. I don't really care where it ends up. + + // prepare for another test with multiple cursors. + // note we have a 4 rows, 3 cols table now. + cursor.setPosition(table->cellAt(0, 0).firstPosition()); + cursor.setPosition(table->cellAt(0, 2).firstPosition(), QTextCursor::KeepAnchor); + + // now create a selection of a whole row. + QTextCursor c2 = table->cellAt(2, 0).firstCursorPosition(); + c2.setPosition(table->cellAt(2, 2).firstPosition(), QTextCursor::KeepAnchor); + + // just for good measure, another one for a block of cells. + QTextCursor c3 = table->cellAt(2, 1).firstCursorPosition(); + c3.setPosition(table->cellAt(3, 2).firstPosition(), QTextCursor::KeepAnchor); + + table->removeRows(2, 1); + + QCOMPARE(cursor.anchor(), table->cellAt(0, 0).firstPosition()); + QCOMPARE(cursor.position(), table->cellAt(0, 2).firstPosition()); + + QCOMPARE(c2.position(), c2.anchor()); // empty. I don't really care where it ends up. + + QCOMPARE(c3.anchor(), table->cellAt(2, 1).firstPosition()); + QCOMPARE(c3.position(), table->cellAt(2, 2).firstPosition()); + + + // prepare for another test where we remove a column + // note we have a 3 rows, 3 cols table now. + cursor.setPosition(table->cellAt(0, 0).firstPosition()); + cursor.setPosition(table->cellAt(2, 1).firstPosition(), QTextCursor::KeepAnchor); + + c2.setPosition(table->cellAt(0, 1).firstPosition()); + c2.setPosition(table->cellAt(2, 2).firstPosition(), QTextCursor::KeepAnchor); + + table->removeColumns(1, 1); + + QCOMPARE(cursor.anchor(), table->cellAt(0, 0).firstPosition()); + QCOMPARE(cursor.position(), table->cellAt(2, 0).firstPosition()); + + QCOMPARE(c2.anchor(), table->cellAt(0, 1).firstPosition()); + QCOMPARE(c2.position(), table->cellAt(2, 1).firstPosition()); + + // test for illegal cursor positions. + // note we have a 3 rows, 2 cols table now. + cursor.setPosition(table->cellAt(2, 0).firstPosition()); + cursor.setPosition(table->cellAt(2, 1).firstPosition(), QTextCursor::KeepAnchor); + + c2.setPosition(table->cellAt(0, 0).firstPosition()); + c2.setPosition(table->cellAt(2, 1).firstPosition(), QTextCursor::KeepAnchor); + + c3.setPosition(table->cellAt(2, 1).firstPosition()); + + table->removeRows(2, 1); + + QCOMPARE(cursor.anchor(), table->cellAt(1, 1).lastPosition()+1); + QCOMPARE(cursor.position(), cursor.anchor()); + + QCOMPARE(c2.anchor(), table->cellAt(0, 0).firstPosition()); + QCOMPARE(c2.position(), table->cellAt(1, 1).firstPosition()); + + QCOMPARE(c3.anchor(), table->cellAt(1, 1).firstPosition()); + QCOMPARE(c3.position(), table->cellAt(1, 1).firstPosition()); +} + +void tst_QTextCursor::joinPreviousEditBlock() +{ + cursor.beginEditBlock(); + cursor.insertText("Hello"); + cursor.insertText("World"); + cursor.endEditBlock(); + QVERIFY(doc->toPlainText().startsWith("HelloWorld")); + + cursor.joinPreviousEditBlock(); + cursor.insertText("Hey"); + cursor.endEditBlock(); + QVERIFY(doc->toPlainText().startsWith("HelloWorldHey")); + + doc->undo(); + QVERIFY(!doc->toPlainText().contains("HelloWorldHey")); +} + +void tst_QTextCursor::setBlockFormatInTable() +{ + // someone reported this on qt4-preview-feedback + QTextBlockFormat fmt; + fmt.setBackground(Qt::blue); + cursor.setBlockFormat(fmt); + + QTextTable *table = cursor.insertTable(2, 2); + cursor = table->cellAt(0, 0).firstCursorPosition(); + fmt.setBackground(Qt::red); + cursor.setBlockFormat(fmt); + + cursor.movePosition(QTextCursor::Start); + QVERIFY(cursor.blockFormat().background().color() == Qt::blue); +} + +void tst_QTextCursor::blockCharFormat2() +{ + QTextCharFormat fmt; + fmt.setForeground(Qt::green); + cursor.mergeBlockCharFormat(fmt); + + fmt.setForeground(Qt::red); + + cursor.insertText("Test", fmt); + cursor.movePosition(QTextCursor::Start); + cursor.insertText("Red"); + cursor.movePosition(QTextCursor::PreviousCharacter); + QVERIFY(cursor.charFormat().foreground().color() == Qt::red); +} + +void tst_QTextCursor::blockCharFormat3() +{ + QVERIFY(cursor.atBlockStart()); + QVERIFY(cursor.atBlockEnd()); + QVERIFY(cursor.atStart()); + + QTextCharFormat fmt; + fmt.setForeground(Qt::green); + cursor.setBlockCharFormat(fmt); + cursor.insertText("Test"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground().color() == Qt::green); + + cursor.movePosition(QTextCursor::Start); + QVERIFY(cursor.charFormat().foreground().color() == Qt::green); + + fmt.setForeground(Qt::red); + cursor.setBlockCharFormat(fmt); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::red); + + cursor.movePosition(QTextCursor::End); + cursor.movePosition(QTextCursor::Start); + QVERIFY(cursor.charFormat().foreground().color() == Qt::green); + + cursor.insertText("Test"); + QVERIFY(cursor.charFormat().foreground().color() == Qt::green); + + cursor.select(QTextCursor::Document); + cursor.removeSelectedText(); + QVERIFY(cursor.atBlockStart()); + QVERIFY(cursor.atBlockEnd()); + QVERIFY(cursor.atStart()); + + cursor.insertText("Test"); + QVERIFY(cursor.charFormat().foreground().color() == Qt::red); +} + +void tst_QTextCursor::blockCharFormat() +{ + QTextCharFormat fmt; + fmt.setForeground(Qt::blue); + cursor.insertBlock(QTextBlockFormat(), fmt); + cursor.insertText("Hm"); + + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::blue); + + fmt.setForeground(Qt::red); + + cursor.setBlockCharFormat(fmt); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::red); +} + +void tst_QTextCursor::blockCharFormatOnSelection() +{ + QTextCharFormat fmt; + fmt.setForeground(Qt::blue); + cursor.insertBlock(QTextBlockFormat(), fmt); + + fmt.setForeground(Qt::green); + cursor.insertText("Hm", fmt); + + fmt.setForeground(Qt::red); + cursor.insertBlock(QTextBlockFormat(), fmt); + cursor.insertText("Ah"); + + fmt.setForeground(Qt::white); + cursor.insertBlock(QTextBlockFormat(), fmt); + cursor.insertText("bleh"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::blue); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::red); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::white); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor); + + fmt.setForeground(Qt::cyan); + cursor.setBlockCharFormat(fmt); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::cyan); + + cursor.movePosition(QTextCursor::Right); + cursor.movePosition(QTextCursor::Right); + QVERIFY(cursor.charFormat().foreground().color() == Qt::green); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::cyan); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::white); +} + +void tst_QTextCursor::anchorInitialized1() +{ + cursor.insertBlock(); + cursor = QTextCursor(cursor.block()); + QCOMPARE(cursor.position(), 1); + QCOMPARE(cursor.anchor(), 1); + QCOMPARE(cursor.selectionStart(), 1); + QCOMPARE(cursor.selectionEnd(), 1); +} + +void tst_QTextCursor::anchorInitialized2() +{ + cursor.insertBlock(); + cursor = QTextCursor(cursor.block().docHandle(), 1); + QCOMPARE(cursor.position(), 1); + QCOMPARE(cursor.anchor(), 1); + QCOMPARE(cursor.selectionStart(), 1); + QCOMPARE(cursor.selectionEnd(), 1); +} + +void tst_QTextCursor::anchorInitialized3() +{ + QTextFrame *frame = cursor.insertFrame(QTextFrameFormat()); + cursor = QTextCursor(frame); + QCOMPARE(cursor.position(), 1); + QCOMPARE(cursor.anchor(), 1); + QCOMPARE(cursor.selectionStart(), 1); + QCOMPARE(cursor.selectionEnd(), 1); +} + +void tst_QTextCursor::selectWord() +{ + cursor.insertText("first second third"); + cursor.insertBlock(); + cursor.insertText("words in second paragraph"); + + cursor.setPosition(9); + cursor.select(QTextCursor::WordUnderCursor); + QVERIFY(cursor.hasSelection()); + QCOMPARE(cursor.selectionStart(), 6); + QCOMPARE(cursor.selectionEnd(), 12); + + cursor.setPosition(5); + cursor.select(QTextCursor::WordUnderCursor); + QVERIFY(cursor.hasSelection()); + QCOMPARE(cursor.selectionStart(), 0); + QCOMPARE(cursor.selectionEnd(), 5); + + cursor.setPosition(6); + cursor.select(QTextCursor::WordUnderCursor); + QVERIFY(cursor.hasSelection()); + QCOMPARE(cursor.selectionStart(), 6); + QCOMPARE(cursor.selectionEnd(), 12); + + cursor.setPosition(14); + cursor.select(QTextCursor::WordUnderCursor); + QVERIFY(cursor.hasSelection()); + QCOMPARE(cursor.selectionStart(), 6); + QCOMPARE(cursor.selectionEnd(), 12); + + cursor.movePosition(QTextCursor::Start); + cursor.select(QTextCursor::WordUnderCursor); + QVERIFY(cursor.hasSelection()); + QCOMPARE(cursor.selectionStart(), 0); + QCOMPARE(cursor.selectionEnd(), 5); + + cursor.movePosition(QTextCursor::EndOfBlock); + cursor.select(QTextCursor::WordUnderCursor); + QVERIFY(cursor.hasSelection()); + QCOMPARE(cursor.selectionStart(), 17); + QCOMPARE(cursor.selectionEnd(), 22); +} + +void tst_QTextCursor::selectWordWithSeparators_data() +{ + QTest::addColumn<QString>("text"); + QTest::addColumn<int>("initialPosition"); + QTest::addColumn<QString>("expectedSelectedText"); + + QTest::newRow("dereference") << QString::fromLatin1("foo->bar()") << 1 << QString::fromLatin1("foo"); + QTest::newRow("funcsignature") << QString::fromLatin1("bar(int x);") << 1 << QString::fromLatin1("bar"); + QTest::newRow("def") << QString::fromLatin1("foo *f;") << 1 << QString::fromLatin1("foo"); +} + +void tst_QTextCursor::selectWordWithSeparators() +{ + QFETCH(QString, text); + QFETCH(int, initialPosition); + QFETCH(QString, expectedSelectedText); + + cursor.insertText(text); + cursor.setPosition(initialPosition); + cursor.select(QTextCursor::WordUnderCursor); + + QCOMPARE(cursor.selectedText(), expectedSelectedText); +} + +void tst_QTextCursor::startOfWord() +{ + cursor.insertText("first second"); + cursor.setPosition(7); + cursor.movePosition(QTextCursor::StartOfWord); + QCOMPARE(cursor.position(), 0); +} + +void tst_QTextCursor::selectBlock() +{ + cursor.insertText("foobar"); + QTextBlockFormat blockFmt; + blockFmt.setAlignment(Qt::AlignHCenter); + cursor.insertBlock(blockFmt); + cursor.insertText("blah"); + cursor.insertBlock(QTextBlockFormat()); + + cursor.movePosition(QTextCursor::PreviousBlock); + QCOMPARE(cursor.block().text(), QString("blah")); + + cursor.select(QTextCursor::BlockUnderCursor); + QVERIFY(cursor.hasSelection()); + + QTextDocumentFragment fragment(cursor); + doc->clear(); + cursor.insertFragment(fragment); + QCOMPARE(blockCount(), 2); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.blockFormat().alignment() == Qt::AlignHCenter); + QCOMPARE(cursor.block().text(), QString("blah")); +} + +void tst_QTextCursor::selectVisually() +{ + cursor.insertText("Foo\nlong line which is probably going to be cut in two when shown in a widget\nparagraph 3\n"); + + cursor.setPosition(6); // somewhere in the long paragraph. + cursor.select(QTextCursor::LineUnderCursor); + // since we are not yet laid-out, we expect the whole paragraph to be selected. + QCOMPARE(cursor.position(), 77); + QCOMPARE(cursor.anchor(), 4); +} + +void tst_QTextCursor::insertText() +{ + QString txt = "Foo\nBar\r\nMeep"; + txt += QChar::LineSeparator; + txt += "Baz"; + txt += QChar::ParagraphSeparator; + txt += "yoyodyne"; + cursor.insertText(txt); + QCOMPARE(blockCount(), 4); + cursor.movePosition(QTextCursor::Start); + QCOMPARE(cursor.block().text(), QString("Foo")); + cursor.movePosition(QTextCursor::NextBlock); + QCOMPARE(cursor.block().text(), QString("Bar")); + cursor.movePosition(QTextCursor::NextBlock); + QCOMPARE(cursor.block().text(), QString(QString("Meep") + QChar(QChar::LineSeparator) + QString("Baz"))); + cursor.movePosition(QTextCursor::NextBlock); + QCOMPARE(cursor.block().text(), QString("yoyodyne")); +} + +void tst_QTextCursor::insertFragmentShouldUseCurrentCharFormat() +{ + QTextDocumentFragment fragment = QTextDocumentFragment::fromPlainText("Hello World"); + QTextCharFormat fmt; + fmt.setFontUnderline(true); + + cursor.clearSelection(); + cursor.setCharFormat(fmt); + cursor.insertFragment(fragment); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat() == fmt); +} + +int tst_QTextCursor::blockCount() +{ + int cnt = 0; + for (QTextBlock blk = doc->begin(); blk.isValid(); blk = blk.next()) + ++cnt; + return cnt; +} + +void tst_QTextCursor::endOfLine() +{ + doc->setPageSize(QSizeF(100000, INT_MAX)); + + QString text("First Line \nSecond Line "); + text.replace(QLatin1Char('\n'), QChar(QChar::LineSeparator)); + cursor.insertText(text); + + // ensure layouted + doc->documentLayout()->documentSize(); + + cursor.movePosition(QTextCursor::Start); + + QCOMPARE(cursor.block().layout()->lineCount(), 2); + + cursor.movePosition(QTextCursor::EndOfLine); + QCOMPARE(cursor.position(), 14); + cursor.movePosition(QTextCursor::NextCharacter); + QCOMPARE(cursor.position(), 15); + cursor.movePosition(QTextCursor::EndOfLine); + QCOMPARE(cursor.position(), 28); +} + +class CursorListener : public QObject +{ + Q_OBJECT +public: + CursorListener(QTextCursor *_cursor) : lastRecordedPosition(-1), lastRecordedAnchor(-1), recordingCount(0), cursor(_cursor) {} + + int lastRecordedPosition; + int lastRecordedAnchor; + int recordingCount; + +public slots: + void recordCursorPosition() + { + lastRecordedPosition = cursor->position(); + lastRecordedAnchor = cursor->anchor(); + ++recordingCount; + } + + void selectAllContents() + { + // Only test the first time + if (!recordingCount) { + recordingCount++; + cursor->select(QTextCursor::Document); + lastRecordedPosition = cursor->position(); + lastRecordedAnchor = cursor->anchor(); + } + } + +private: + QTextCursor *cursor; +}; + +void tst_QTextCursor::editBlocksDuringRemove() +{ + CursorListener listener(&cursor); + + cursor.insertText("Hello World"); + cursor.movePosition(QTextCursor::Start, QTextCursor::KeepAnchor); + QCOMPARE(cursor.selectedText(), QString("Hello World")); + + connect(doc, SIGNAL(contentsChanged()), &listener, SLOT(recordCursorPosition())); + listener.recordingCount = 0; + cursor.deleteChar(); + + QCOMPARE(listener.recordingCount, 1); + QCOMPARE(listener.lastRecordedPosition, 0); + QCOMPARE(listener.lastRecordedAnchor, 0); + + QVERIFY(doc->toPlainText().isEmpty()); +} + +void tst_QTextCursor::selectAllDuringRemove() +{ + CursorListener listener(&cursor); + + cursor.insertText("Hello World"); + cursor.movePosition(QTextCursor::End); + + connect(doc, SIGNAL(contentsChanged()), &listener, SLOT(selectAllContents())); + listener.recordingCount = 0; + QTextCursor localCursor = cursor; + localCursor.deletePreviousChar(); + + QCOMPARE(listener.lastRecordedPosition, 10); + QCOMPARE(listener.lastRecordedAnchor, 0); +} + +void tst_QTextCursor::update_data() +{ + QTest::addColumn<QString>("text"); + QTest::addColumn<int>("position"); + QTest::addColumn<int>("anchor"); + QTest::addColumn<int>("modifyPosition"); + QTest::addColumn<int>("modifyAnchor"); + QTest::addColumn<QString>("insertText"); + QTest::addColumn<int>("expectedPosition"); + QTest::addColumn<int>("expectedAnchor"); + + QString text("Hello big world"); + int charsToDelete = 3; + QTest::newRow("removeInsideSelection") + << text + << /*position*/ 0 + << /*anchor*/ text.length() + // delete 'big' + << 6 + << 6 + charsToDelete + << QString() // don't insert anything, just remove + << /*expectedPosition*/ 0 + << /*expectedAnchor*/ text.length() - charsToDelete + ; + + text = "Hello big world"; + charsToDelete = 3; + QTest::newRow("removeInsideSelectionWithSwappedAnchorAndPosition") + << text + << /*position*/ text.length() + << /*anchor*/ 0 + // delete 'big' + << 6 + << 6 + charsToDelete + << QString() // don't insert anything, just remove + << /*expectedPosition*/ text.length() - charsToDelete + << /*expectedAnchor*/ 0 + ; + + + text = "Hello big world"; + charsToDelete = 3; + QString textToInsert("small"); + QTest::newRow("replaceInsideSelection") + << text + << /*position*/ 0 + << /*anchor*/ text.length() + // delete 'big' ... + << 6 + << 6 + charsToDelete + << textToInsert // ... and replace 'big' with 'small' + << /*expectedPosition*/ 0 + << /*expectedAnchor*/ text.length() - charsToDelete + textToInsert.length() + ; + + text = "Hello big world"; + charsToDelete = 3; + textToInsert = "small"; + QTest::newRow("replaceInsideSelectionWithSwappedAnchorAndPosition") + << text + << /*position*/ text.length() + << /*anchor*/ 0 + // delete 'big' ... + << 6 + << 6 + charsToDelete + << textToInsert // ... and replace 'big' with 'small' + << /*expectedPosition*/ text.length() - charsToDelete + textToInsert.length() + << /*expectedAnchor*/ 0 + ; + + + text = "Hello big world"; + charsToDelete = 3; + QTest::newRow("removeBeforeSelection") + << text + << /*position*/ text.length() - 5 + << /*anchor*/ text.length() + // delete 'big' + << 6 + << 6 + charsToDelete + << QString() // don't insert anything, just remove + << /*expectedPosition*/ text.length() - 5 - charsToDelete + << /*expectedAnchor*/ text.length() - charsToDelete + ; + + text = "Hello big world"; + charsToDelete = 3; + QTest::newRow("removeAfterSelection") + << text + << /*position*/ 0 + << /*anchor*/ 4 + // delete 'big' + << 6 + << 6 + charsToDelete + << QString() // don't insert anything, just remove + << /*expectedPosition*/ 0 + << /*expectedAnchor*/ 4 + ; + +} + +void tst_QTextCursor::update() +{ + QFETCH(QString, text); + + doc->setPlainText(text); + + QFETCH(int, position); + QFETCH(int, anchor); + + cursor.setPosition(anchor); + cursor.setPosition(position, QTextCursor::KeepAnchor); + + QCOMPARE(cursor.position(), position); + QCOMPARE(cursor.anchor(), anchor); + + QFETCH(int, modifyPosition); + QFETCH(int, modifyAnchor); + + QTextCursor modifyCursor = cursor; + modifyCursor.setPosition(modifyAnchor); + modifyCursor.setPosition(modifyPosition, QTextCursor::KeepAnchor); + + QCOMPARE(modifyCursor.position(), modifyPosition); + QCOMPARE(modifyCursor.anchor(), modifyAnchor); + + QFETCH(QString, insertText); + modifyCursor.insertText(insertText); + + QFETCH(int, expectedPosition); + QFETCH(int, expectedAnchor); + + QCOMPARE(cursor.position(), expectedPosition); + QCOMPARE(cursor.anchor(), expectedAnchor); +} + +void tst_QTextCursor::disallowSettingObjectIndicesOnCharFormats() +{ + QTextCharFormat fmt; + fmt.setObjectIndex(42); + cursor.insertText("Hey", fmt); + QCOMPARE(cursor.charFormat().objectIndex(), -1); + + cursor.select(QTextCursor::Document); + cursor.mergeCharFormat(fmt); + QCOMPARE(doc->begin().begin().fragment().charFormat().objectIndex(), -1); + + cursor.select(QTextCursor::Document); + cursor.setCharFormat(fmt); + QCOMPARE(doc->begin().begin().fragment().charFormat().objectIndex(), -1); + + cursor.setBlockCharFormat(fmt); + QCOMPARE(cursor.blockCharFormat().objectIndex(), -1); + + cursor.movePosition(QTextCursor::End); + cursor.insertBlock(QTextBlockFormat(), fmt); + QCOMPARE(cursor.blockCharFormat().objectIndex(), -1); + + doc->clear(); + + QTextTable *table = cursor.insertTable(1, 1); + cursor.select(QTextCursor::Document); + cursor.setCharFormat(fmt); + + cursor = table->cellAt(0, 0).firstCursorPosition(); + QVERIFY(!cursor.isNull()); + QCOMPARE(cursor.blockCharFormat().objectIndex(), table->objectIndex()); +} + +void tst_QTextCursor::blockAndColumnNumber() +{ + QCOMPARE(QTextCursor().columnNumber(), 0); + QCOMPARE(QTextCursor().blockNumber(), 0); + + QCOMPARE(cursor.columnNumber(), 0); + QCOMPARE(cursor.blockNumber(), 0); + cursor.insertText("Hello"); + QCOMPARE(cursor.columnNumber(), 5); + QCOMPARE(cursor.blockNumber(), 0); + + cursor.insertBlock(); + QCOMPARE(cursor.columnNumber(), 0); + QCOMPARE(cursor.blockNumber(), 1); + cursor.insertText("Blah"); + QCOMPARE(cursor.blockNumber(), 1); + + // trigger a layout + doc->documentLayout(); + + cursor.insertBlock(); + QCOMPARE(cursor.columnNumber(), 0); + QCOMPARE(cursor.blockNumber(), 2); + cursor.insertText("Test"); + QCOMPARE(cursor.columnNumber(), 4); + QCOMPARE(cursor.blockNumber(), 2); + cursor.insertText(QString(QChar(QChar::LineSeparator))); + QCOMPARE(cursor.columnNumber(), 0); + QCOMPARE(cursor.blockNumber(), 2); + cursor.insertText("A"); + QCOMPARE(cursor.columnNumber(), 1); + QCOMPARE(cursor.blockNumber(), 2); +} + +void tst_QTextCursor::movePositionEndOfLine() +{ + cursor.insertText("blah\nblah\n"); + // Select part of the second line ("la") + cursor.setPosition(6); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2); + QCOMPARE(cursor.selectedText(), QLatin1String("la")); + + // trigger a layout + doc->documentLayout(); + + // Remove "la" and append "something" to the end in one undo operation + cursor.beginEditBlock(); + cursor.removeSelectedText(); + QTextCursor c2(doc); + c2.setPosition(7); + c2.insertText("foo"); // append to doc without touching the cursor. + + QCOMPARE(cursor.position(), 6); + cursor.movePosition(QTextCursor::EndOfLine); // in an edit block visual movement is moved to the end of the paragraph + QCOMPARE(cursor.position(), 10); + cursor.endEditBlock(); +} + +void tst_QTextCursor::clearCells() +{ + QTextTable *table = cursor.insertTable(3, 5); + cursor.setPosition(table->cellAt(0,0).firstPosition()); // select cell 1 and cell 2 + cursor.setPosition(table->cellAt(0,1).firstPosition(), QTextCursor::KeepAnchor); + cursor.deleteChar(); // should clear the cells, and not crash ;) +} + +void tst_QTextCursor::task244408_wordUnderCursor_data() +{ + QTest::addColumn<QString>("input"); + QTest::addColumn<QString>("expected"); + QTest::newRow("trailingSpace") << QString::fromLatin1("foo ") << QString::fromLatin1(""); + QTest::newRow("noTrailingSpace") << QString::fromLatin1("foo") << QString::fromLatin1("foo"); +} + +void tst_QTextCursor::task244408_wordUnderCursor() +{ + QFETCH(QString, input); + QFETCH(QString, expected); + cursor.insertText(input); + cursor.movePosition(QTextCursor::End); + cursor.select(QTextCursor::WordUnderCursor); + QCOMPARE(cursor.selectedText(), expected); +} + +void tst_QTextCursor::adjustCursorsOnInsert() +{ + cursor.insertText("Some text before "); + int posBefore = cursor.position(); + cursor.insertText("selected text"); + int posAfter = cursor.position(); + cursor.insertText(" some text afterwards"); + + QTextCursor selection = cursor; + selection.setPosition(posBefore); + selection.setPosition(posAfter, QTextCursor::KeepAnchor); + + cursor.setPosition(posBefore-1); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.anchor(), posBefore+1); + QCOMPARE(selection.position(), posAfter+1); + doc->undo(); + + cursor.setPosition(posBefore); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.anchor(), posBefore+1); + QCOMPARE(selection.position(), posAfter+1); + doc->undo(); + + cursor.setPosition(posBefore+1); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.anchor(), posBefore); + QCOMPARE(selection.position(), posAfter+1); + doc->undo(); + + cursor.setPosition(posAfter-1); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.anchor(), posBefore); + QCOMPARE(selection.position(), posAfter+1); + doc->undo(); + + selection.setKeepPositionOnInsert(true); + cursor.setPosition(posAfter); + cursor.insertText(QLatin1String("x")); + selection.setKeepPositionOnInsert(false); + QCOMPARE(selection.anchor(), posBefore); + QCOMPARE(selection.position(), posAfter); + doc->undo(); + + cursor.setPosition(posAfter+1); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.anchor(), posBefore); + QCOMPARE(selection.position(), posAfter); + doc->undo(); + + selection.setPosition(posAfter); + selection.setPosition(posBefore, QTextCursor::KeepAnchor); + + cursor.setPosition(posBefore-1); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.position(), posBefore+1); + QCOMPARE(selection.anchor(), posAfter+1); + doc->undo(); + + cursor.setPosition(posBefore); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.position(), posBefore+1); + QCOMPARE(selection.anchor(), posAfter+1); + doc->undo(); + + cursor.setPosition(posBefore+1); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.position(), posBefore); + QCOMPARE(selection.anchor(), posAfter+1); + doc->undo(); + + cursor.setPosition(posAfter-1); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.position(), posBefore); + QCOMPARE(selection.anchor(), posAfter+1); + doc->undo(); + + cursor.setPosition(posAfter); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.position(), posBefore); + QCOMPARE(selection.anchor(), posAfter+1); + doc->undo(); + + cursor.setPosition(posAfter+1); + cursor.insertText(QLatin1String("x")); + QCOMPARE(selection.position(), posBefore); + QCOMPARE(selection.anchor(), posAfter); + doc->undo(); + +} +void tst_QTextCursor::cursorPositionWithBlockUndoAndRedo() +{ + cursor.insertText("AAAABBBBCCCCDDDD"); + cursor.setPosition(12); + int cursorPositionBefore = cursor.position(); + cursor.beginEditBlock(); + cursor.insertText("*"); + cursor.setPosition(8); + cursor.insertText("*"); + cursor.setPosition(4); + cursor.insertText("*"); + cursor.setPosition(0); + cursor.insertText("*"); + int cursorPositionAfter = cursor.position(); + cursor.endEditBlock(); + + QVERIFY(doc->toPlainText() == "*AAAA*BBBB*CCCC*DDDD"); + QCOMPARE(12, cursorPositionBefore); + QCOMPARE(1, cursorPositionAfter); + + doc->undo(&cursor); + QVERIFY(doc->toPlainText() == "AAAABBBBCCCCDDDD"); + QCOMPARE(cursor.position(), cursorPositionBefore); + doc->redo(&cursor); + QVERIFY(doc->toPlainText() == "*AAAA*BBBB*CCCC*DDDD"); + QCOMPARE(cursor.position(), cursorPositionAfter); +} + +void tst_QTextCursor::cursorPositionWithBlockUndoAndRedo2() +{ + cursor.insertText("AAAABBBB"); + int cursorPositionBefore = cursor.position(); + cursor.setPosition(0, QTextCursor::KeepAnchor); + cursor.beginEditBlock(); + cursor.removeSelectedText(); + cursor.insertText("AAAABBBBCCCCDDDD"); + cursor.endEditBlock(); + doc->undo(&cursor); + QVERIFY(doc->toPlainText() == "AAAABBBB"); + QCOMPARE(cursor.position(), cursorPositionBefore); + + cursor.insertText("CCCC"); + QVERIFY(doc->toPlainText() == "AAAABBBBCCCC"); + + cursorPositionBefore = cursor.position(); + cursor.setPosition(0, QTextCursor::KeepAnchor); + cursor.beginEditBlock(); + cursor.removeSelectedText(); + cursor.insertText("AAAABBBBCCCCDDDD"); + cursor.endEditBlock(); + + /* this undo now implicitely reinserts two segments, first "CCCCC", then + "AAAABBBB". The test ensures that the two are combined in order to + reconstruct the correct cursor position */ + doc->undo(&cursor); + + + QVERIFY(doc->toPlainText() == "AAAABBBBCCCC"); + QCOMPARE(cursor.position(), cursorPositionBefore); +} + +void tst_QTextCursor::cursorPositionWithBlockUndoAndRedo3() +{ + // verify that it's the position of the beginEditBlock that counts, and not the last edit position + cursor.insertText("AAAABBBB"); + int cursorPositionBefore = cursor.position(); + cursor.beginEditBlock(); + cursor.setPosition(4); + QVERIFY(cursor.position() != cursorPositionBefore); + cursor.insertText("*"); + cursor.endEditBlock(); + QCOMPARE(cursor.position(), 5); + doc->undo(&cursor); + QCOMPARE(cursor.position(), cursorPositionBefore); +} + +QTEST_MAIN(tst_QTextCursor) +#include "tst_qtextcursor.moc" diff --git a/tests/auto/gui/text/qtextdocument/.gitignore b/tests/auto/gui/text/qtextdocument/.gitignore new file mode 100644 index 0000000000..c14f0e2422 --- /dev/null +++ b/tests/auto/gui/text/qtextdocument/.gitignore @@ -0,0 +1 @@ +tst_qtextdocument diff --git a/tests/auto/gui/text/qtextdocument/common.h b/tests/auto/gui/text/qtextdocument/common.h new file mode 100644 index 0000000000..1eaefdbfbe --- /dev/null +++ b/tests/auto/gui/text/qtextdocument/common.h @@ -0,0 +1,93 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QAbstractTextDocumentLayout> +#include <private/qtextdocument_p.h> + +#ifndef COMMON_H +#define COMMON_H + +class QTestDocumentLayout : public QAbstractTextDocumentLayout +{ + Q_OBJECT +public: + QTestDocumentLayout(QTextDocument *doc) : QAbstractTextDocumentLayout(doc), f(-1), called(false) {} + virtual void draw(QPainter *, const PaintContext &) {} + virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const { return 0; } + + virtual void documentChanged(int from, int oldLength, int length) + { + called = true; + lastDocumentLengths.append(document()->docHandle()->length()); + + if (f < 0) + return; + + if(from != f || + o != oldLength || + l != length) { + qDebug("checkDocumentChanged: got %d %d %d, expected %d %d %d", from, oldLength, length, f, o, l); + error = true; + } + } + + virtual int pageCount() const { return 1; } + virtual QSizeF documentSize() const { return QSizeF(); } + + virtual QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); } + virtual QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); } + + int f; + int o; + int l; + + void expect(int from, int oldLength, int length) { + f = from; + o = oldLength; + l = length; + error = false; + called = false; + } + bool error; + bool called; + QList<int> lastDocumentLengths; +}; + +#endif diff --git a/tests/auto/gui/text/qtextdocument/qtextdocument.pro b/tests/auto/gui/text/qtextdocument/qtextdocument.pro new file mode 100644 index 0000000000..69517589cc --- /dev/null +++ b/tests/auto/gui/text/qtextdocument/qtextdocument.pro @@ -0,0 +1,6 @@ +load(qttest_p4) +QT += core-private gui-private xml +HEADERS += common.h +SOURCES += tst_qtextdocument.cpp + + diff --git a/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp new file mode 100644 index 0000000000..c98a703acc --- /dev/null +++ b/tests/auto/gui/text/qtextdocument/tst_qtextdocument.cpp @@ -0,0 +1,2788 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $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 <qurl.h> +#include <qpainter.h> +#include <qfontmetrics.h> +#include <qimage.h> +#include <qtextlayout.h> +#include <QDomDocument> +#include "common.h" + + +QT_FORWARD_DECLARE_CLASS(QTextDocument) + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QTextDocument : public QObject +{ + Q_OBJECT + +public: + tst_QTextDocument(); + virtual ~tst_QTextDocument(); + +public slots: + void init(); + void cleanup(); +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 toHtmlRootFrameProperties(); + 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 escape_data(); + void escape(); + + void copiedFontSize(); + + void htmlExportImportBlockCount(); + +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() +{ + QFile::remove(QLatin1String("foo.png")); +} + +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::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::fromAscii("Hello") + QString(QChar::ParagraphSeparator) + QString::fromAscii("World") + << "World" << int(QTextDocument::FindCaseSensitively) << 1 << 6 << 11; + + QTest::newRow("3") << QString::fromAscii("Hello") + QString(QChar::ParagraphSeparator) + QString::fromAscii("World") + << "Hello" << int(QTextDocument::FindCaseSensitively | QTextDocument::FindBackward) << 10 << 0 << 5; + QTest::newRow("4wholewords") << QString::fromAscii("Hello Blah World") + << "Blah" << int(QTextDocument::FindWholeWords) << 0 << 6 << 10; + QTest::newRow("5wholewords") << QString::fromAscii("HelloBlahWorld") + << "Blah" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1; + QTest::newRow("6wholewords") << QString::fromAscii("HelloBlahWorld Blah Hah") + << "Blah" << int(QTextDocument::FindWholeWords) << 0 << 15 << 19; + QTest::newRow("7wholewords") << QString::fromAscii("HelloBlahWorld Blah Hah") + << "Blah" << int(QTextDocument::FindWholeWords | QTextDocument::FindBackward) << 23 << 15 << 19; + QTest::newRow("8wholewords") << QString::fromAscii("Hello: World\n") + << "orld" << int(QTextDocument::FindWholeWords) << 0 << -1 << -1; + + QTest::newRow("across-paragraphs") << QString::fromAscii("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 & + QTest::newRow("2") << "His & hers & theirs" << "&(?!amp;)"<< int(QTextDocument::FindCaseSensitively) << 0 << 15 << 16; + //backward search + QTest::newRow("3") << QString::fromAscii("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>&<></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:"Foo's Family";\">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"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(); + + 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.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.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&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='&b="\">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-malign") << 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-malign") << 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::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::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(const QTextCursor &)), + this, SLOT(cursorPositionChanged(const 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 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::escape_data() +{ + QTest::addColumn<QString>("original"); + QTest::addColumn<QString>("expected"); + + QTest::newRow("1") << "Hello World\n" << "Hello World\n"; + QTest::newRow("2") << "#include <QtCore>" << "#include <QtCore>"; + QTest::newRow("3") << "<p class=\"cool\"><a href=\"http://example.com/?foo=bar&bar=foo\">plop --> </a></p>" + << "<p class="cool"><a href="http://example.com/?foo=bar&amp;bar=foo">plop --&gt; </a></p>"; + QTest::newRow("4") << QString::fromUtf8("<\320\222\321\201>") << QString::fromUtf8("<\320\222\321\201>"); +} + +void tst_QTextDocument::escape() +{ + QFETCH(QString, original); + QFETCH(QString, expected); + + QCOMPARE(Qt::escape(original), expected); +} + +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); +} + +QTEST_MAIN(tst_QTextDocument) +#include "tst_qtextdocument.moc" diff --git a/tests/auto/gui/text/qtextdocumentfragment/.gitignore b/tests/auto/gui/text/qtextdocumentfragment/.gitignore new file mode 100644 index 0000000000..5c569834d3 --- /dev/null +++ b/tests/auto/gui/text/qtextdocumentfragment/.gitignore @@ -0,0 +1 @@ +tst_qtextdocumentfragment diff --git a/tests/auto/gui/text/qtextdocumentfragment/qtextdocumentfragment.pro b/tests/auto/gui/text/qtextdocumentfragment/qtextdocumentfragment.pro new file mode 100644 index 0000000000..e6ddd45f85 --- /dev/null +++ b/tests/auto/gui/text/qtextdocumentfragment/qtextdocumentfragment.pro @@ -0,0 +1,8 @@ +load(qttest_p4) + +QT += core-private gui-private + +SOURCES += tst_qtextdocumentfragment.cpp + + + diff --git a/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp b/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp new file mode 100644 index 0000000000..68c7a285ef --- /dev/null +++ b/tests/auto/gui/text/qtextdocumentfragment/tst_qtextdocumentfragment.cpp @@ -0,0 +1,4030 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + + +#include <qtextdocument.h> +#include <qtextdocumentfragment.h> +#include <qtexttable.h> +#include <qtextlist.h> +#include <qdebug.h> +#include <private/qtextdocument_p.h> + + +#include <qtextcursor.h> + +QT_FORWARD_DECLARE_CLASS(QTextDocument) + +//TESTED_CLASS= +//TESTED_FILES=gui/text/qtextdocumentfragment.h gui/text/qtextdocumentfragment.cpp gui/text/qtexthtmlparser.cpp gui/text/qtexthtmlparser_p.h + +class tst_QTextDocumentFragment : public QObject +{ + Q_OBJECT + +public: + tst_QTextDocumentFragment(); + ~tst_QTextDocumentFragment(); + +public slots: + void init(); + void cleanup(); +private slots: + void listCopying(); + void listZeroCopying(); + void listCopying2(); + void tableCopying(); + void tableCopyingWithColSpans(); + void tableColSpanAndWidth(); + void tableImport(); + void tableImport2(); + void tableImport3(); + void tableImport4(); + void tableImport5(); + void textCopy(); + void copyWholeDocument(); + void title(); + void html_listIndents1(); + void html_listIndents2(); + void html_listIndents3(); + void html_listIndents4(); + void html_listIndents5(); + void html_listIndents6(); + void blockCharFormat(); + void blockCharFormatCopied(); + void initialBlock(); + void clone(); + void dontRemoveInitialBlockIfItHoldsObjectIndexedCharFormat(); + void dosLineFeed(); + void unorderedListEnumeration(); + void resetHasBlockAfterClosedBlockTags(); + void ignoreStyleTags(); + void hrefAnchor(); + void namedAnchorFragments(); + void namedAnchorFragments2(); + void namedAnchorFragments3(); + void dontInheritAlignmentInTables(); + void cellBlockCount(); + void cellBlockCount2(); + void emptyTable(); + void emptyTable2(); + void emptyTable3(); + void doubleRowClose(); + void mayNotHaveChildren(); + void inheritAlignment(); + void dontEmitEmptyNodeWhenEmptyTagIsFollowedByCloseTag(); + void toPlainText(); + void copyTableRow(); + void copyTableColumn(); + void copySubTable(); + void html_textDecoration(); + void html_infiniteLoop(); + void html_blockIndent(); + void html_listIndent(); + void html_whitespace(); + void html_whitespace_data(); + void html_qt3Whitespace(); + void html_qt3WhitespaceWithFragments(); + void html_qt3WhitespaceAfterTags(); + void html_listStart1(); + void html_listStart2(); + void html_cssMargin(); + void html_hexEntities(); + void html_decEntities(); + void html_thCentered(); + void orderedListNumbering(); + void html_blockAfterList(); + void html_subAndSuperScript(); + void html_cssColors(); + void obeyFragmentMarkersInImport(); + void whitespaceWithFragmentMarkers(); + void html_emptyParapgraphs1(); + void html_emptyParapgraphs2(); + void html_emptyParagraphs3(); + void html_emptyParagraphs4(); + void html_font(); + void html_fontSize(); + void html_fontSizeAdjustment(); + void html_cssFontSize(); + void html_cssShorthandFont(); + void html_bodyBgColor(); + void html_qtBgColor(); + void html_blockLevelDiv(); + void html_spanNesting(); + void html_nestedLists(); + void noSpecialCharactersInPlainText(); + void html_doNotInheritBackground(); + void html_inheritBackgroundToInlineElements(); + void html_doNotInheritBackgroundFromBlockElements(); + void html_nobr(); + void fromPlainText(); + void fromPlainText2(); + void html_closingImageTag(); + void html_emptyDocument(); + void html_closingTag(); + void html_anchorAroundImage(); + void html_floatBorder(); + void html_frameImport(); + void html_frameImport2(); + void html_dontAddMarginsAcrossTableCells(); + void html_dontMergeCenterBlocks(); + void html_tableCellBgColor(); + void html_tableCellBgColor2(); + void html_cellSkip(); + void nonZeroMarginOnImport(); + void html_charFormatPropertiesUnset(); + void html_headings(); + void html_quotedFontFamily(); + void html_spanBackgroundColor(); + void defaultFont(); + void html_brokenTitle_data(); + void html_brokenTitle(); + void html_blockVsInline(); + void html_tbody(); + void html_nestedTables(); + void html_rowSpans(); + void html_rowSpans2(); + void html_implicitParagraphs(); + void html_missingCloseTag(); + void html_anchorColor(); + void html_lastParagraphClosing(); + void html_tableHeaderBodyFootParent(); + void html_columnWidths(); + void html_bodyBackground(); + void html_tableCellBackground(); + void css_bodyBackground(); + void css_tableCellBackground(); + void css_fontWeight(); + void css_float(); + void css_textIndent(); + void css_inline(); + void css_external(); + void css_import(); + void css_selectors_data(); + void css_selectors(); + void css_nodeNameCaseInsensitivity(); + void css_textUnderlineStyle_data(); + void css_textUnderlineStyle(); + void css_textUnderlineStyleAndDecoration(); + void css_listStyleType(); + void css_linkPseudo(); + void css_pageBreaks(); + void css_cellPaddings(); + void universalSelectors_data(); + void universalSelectors(); + void screenMedia(); + void htmlResourceLoading(); + void someCaseInsensitiveAttributeValues(); + void backgroundImage(); + void dontMergePreAndNonPre(); + void leftMarginInsideHtml(); + void html_margins(); + void newlineInsidePreShouldBecomeNewParagraph(); + void invalidColspan(); + void html_brokenTableWithJustTr(); + void html_brokenTableWithJustTd(); + void html_preNewlineHandling_data(); + void html_preNewlineHandling(); + void html_br(); + void html_dl(); + void html_tableStrangeNewline(); + void html_tableStrangeNewline2(); + void html_tableStrangeNewline3(); + void html_caption(); + void html_windowsEntities(); + void html_eatenText(); + void html_hr(); + void html_hrMargins(); + void html_blockQuoteMargins(); + void html_definitionListMargins(); + void html_listMargins(); + void html_titleAttribute(); + void html_compressDivs(); + void completeToPlainText(); + void copyContents(); + void html_textAfterHr(); + void blockTagClosing(); + void isEmpty(); + void html_alignmentInheritance(); + void html_ignoreEmptyDivs(); + void html_dontInheritAlignmentForFloatingImages(); + void html_verticalImageAlignment(); + void html_verticalCellAlignment(); + void html_borderColor(); + void html_borderStyle(); + void html_borderWidth(); + void html_userState(); + void html_rootFrameProperties(); + void html_alignmentPropertySet(); + void html_appendList(); + void html_appendList2(); + void html_qt3RichtextWhitespaceMode(); + void html_brAfterHr(); + void html_unclosedHead(); + void html_entities(); + void html_entities_data(); + void html_ignore_script(); + void html_directionWithHtml(); + void html_directionWithRichText(); + void html_metaInBody(); + void html_importImageWithoutAspectRatio(); + void html_fromFirefox(); + +private: + inline void setHtml(const QString &html) + // don't take the shortcut in QTextDocument::setHtml + { doc->clear(); QTextCursor(doc).insertFragment(QTextDocumentFragment::fromHtml(html)); } + + inline void appendHtml(const QString &html) + { + QTextCursor cursor(doc); + cursor.movePosition(QTextCursor::End); + cursor.insertHtml(html); + } + + QTextDocument *doc; + QTextCursor cursor; +}; + +tst_QTextDocumentFragment::tst_QTextDocumentFragment() +{ + QImage img(16, 16, QImage::Format_ARGB32_Premultiplied); + img.save("foo.png"); +} + +tst_QTextDocumentFragment::~tst_QTextDocumentFragment() +{ + QFile::remove(QLatin1String("foo.png")); +} + +void tst_QTextDocumentFragment::init() +{ + doc = new QTextDocument; + cursor = QTextCursor(doc); +} + +void tst_QTextDocumentFragment::cleanup() +{ + cursor = QTextCursor(); + delete doc; + doc = 0; +} + +#include <private/qtextdocument_p.h> +#include <qdebug.h> +static void dumpTable(const QTextDocumentPrivate *pt) +{ + qDebug() << "---dump----"; + qDebug() << "all text:" << pt->buffer(); + for (QTextDocumentPrivate::FragmentIterator it = pt->begin(); + !it.atEnd(); ++it) { + qDebug() << "Fragment at text position" << it.position() << "; stringPosition" << it.value()->stringPosition << "; size" << it.value()->size_array[0] << "format :" << it.value()->format << "frag: " << it.n; + qDebug() << " text:" << pt->buffer().mid(it.value()->stringPosition, it.value()->size_array[0]); + } + qDebug() << "----begin block dump----"; + for (QTextBlock it = pt->blocksBegin(); it.isValid(); it = it.next()) + qDebug() << "block at" << it.position() << "with length" << it.length() << "block alignment" << it.blockFormat().alignment(); + qDebug() << "---dump----"; +} +static void dumpTable(QTextDocument *doc) { dumpTable(doc->docHandle()); } + +void tst_QTextDocumentFragment::listCopying() +{ + cursor.insertList(QTextListFormat::ListDecimal); + + QTextFormat originalBlockFormat = cursor.blockFormat(); + QVERIFY(originalBlockFormat.objectIndex() != -1); + int originalListItemIdx = cursor.blockFormat().objectIndex(); + + cursor.insertText("Hello World"); + + QTextDocumentFragment fragment(doc); + + cursor.insertFragment(fragment); + + QVERIFY(cursor.currentList()); + QVERIFY(cursor.blockFormat() != originalBlockFormat); + QVERIFY(cursor.blockFormat().objectIndex() != originalListItemIdx); +} + +void tst_QTextDocumentFragment::listZeroCopying() +{ + // same testcase as above but using the zero-copying + + cursor.insertList(QTextListFormat::ListDecimal); + + QTextFormat originalBlockFormat = cursor.blockFormat(); + int originalListItemIdx = cursor.blockFormat().objectIndex(); + + cursor.insertText("Hello World"); + + QTextDocumentFragment fragment(doc); + cursor.insertFragment(fragment); + + QVERIFY(cursor.currentList()); + QVERIFY(cursor.blockFormat() != originalBlockFormat); + QVERIFY(cursor.blockFormat().objectIndex() != originalListItemIdx); +} + +void tst_QTextDocumentFragment::listCopying2() +{ + cursor.insertList(QTextListFormat::ListDecimal); + cursor.insertText("Hello World"); + + cursor.insertList(QTextListFormat::ListDisc); + cursor.insertText("Hello World"); + + QTextDocumentFragment fragment(doc); + + cursor.insertFragment(fragment); + + cursor.movePosition(QTextCursor::Start); + int listItemCount = 0; + do { + if (cursor.currentList()) + listItemCount++; + } while (cursor.movePosition(QTextCursor::NextBlock)); + + QCOMPARE(listItemCount, 4); + + // we call this here because it used to cause a failing assertion in the + // list manager. + doc->undo(); +} + +void tst_QTextDocumentFragment::tableCopying() +{ + // this tests both, the fragment to use the direction insertion instead of using the + // cursor, which might adjuts its position when inserting a table step by step, as well + // as the pasiveness of the tablemanager. + QTextDocumentFragment fragment; + { + QTextDocument doc; + QTextCursor cursor(&doc); + + QTextTableFormat fmt; + QTextTable *table = cursor.insertTable(2, 2, fmt); + + table->cellAt(0, 0).firstCursorPosition().insertText("First Cell"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second Cell"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third Cell"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth Cell"); + + fragment = QTextDocumentFragment(&doc); + } + { + QTextDocument doc; + QTextCursor cursor(&doc); + + cursor.insertText("FooBar"); + cursor.insertBlock(); + cursor.movePosition(QTextCursor::Left); + + cursor.insertFragment(fragment); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); + } +} + +void tst_QTextDocumentFragment::tableCopyingWithColSpans() +{ + const char html[] = "" +"<table border>" +" <tr>" +" <td>First Cell" +" <td>Second Cell" +" </tr>" +" <tr>" +" <td colspan=\"2\">Third Cell" +" </tr>" +" <tr>" +" <td>Fourth Cell" +" <td>Fifth Cell" +" </tr>" +"</table>"; + setHtml(html); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QVERIFY(table->columns() == 2 && table->rows() == 3); + + cursor = table->cellAt(2, 0).lastCursorPosition(); + cursor.setPosition(table->cellAt(0, 0).firstPosition(), QTextCursor::KeepAnchor); + QVERIFY(cursor.hasComplexSelection()); + + int firstRow = 0, numRows = 0, firstCol = 0, numCols = 0; + cursor.selectedTableCells(&firstRow, &numRows, &firstCol, &numCols); + QCOMPARE(firstRow, 0); + QCOMPARE(numRows, 3); + QCOMPARE(firstCol, 0); + QCOMPARE(numCols, 1); + + QTextDocumentFragment frag = cursor.selection(); + cleanup(); + init(); + cursor.insertFragment(frag); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + table = cursor.currentTable(); + QVERIFY(table); + QVERIFY(table->columns() == 1 && table->rows() == 3); +} + +void tst_QTextDocumentFragment::tableColSpanAndWidth() +{ + const char html[] = "" +"<table border=\"0\">" +" <tr>" +" <td colspan=\"4\" width=\"400\">First Cell</td>" +" </tr>" +"</table>"; + setHtml(html); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QVERIFY(table->columns() == 4 && table->rows() == 1); + // make sure its approx 400 and not a multiple due to the colspan + QVERIFY(doc->size().width()> 398.); + QVERIFY(doc->size().width() < 420.); +} + +void tst_QTextDocumentFragment::tableImport() +{ + // used to cause a failing assertion, as HTMLImporter::closeTag was + // called twice with the last node. + QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(QString::fromLatin1("<table><tr><td>Hey</td><td>Blah")); + QVERIFY(!fragment.isEmpty()); +} + +void tst_QTextDocumentFragment::tableImport2() +{ + { + const char html[] = "" + "<table>" + "<tr><td>First Cell</td><td>Second Cell</td></tr>" + "<tr><td>Third Cell</td><td>Fourth Cell</td></tr>" + "</table>"; + + QTextDocument doc; + QTextCursor cursor(&doc); + cursor.insertFragment(QTextDocumentFragment::fromHtml(QByteArray::fromRawData(html, sizeof(html) / sizeof(html[0])))); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 2); + } + { + const char html[] = "" + "<table>" + "<tr><td>First Cell</td><td>Second Cell</td></tr>" + "<tr><td>Third Cell</td><td>" + " <table>" + " <tr><td>First Nested Cell</td><td>Second Nested Cell</td></tr>" + " <tr><td>Third Nested Cell</td><td>Fourth Nested Cell</td></tr>" + " <tr><td>Fifth Nested Cell</td><td>Sixth Nested Cell</td></tr>" + " </table></td></tr>" + "</table>"; + + QTextDocument doc; + QTextCursor cursor(&doc); + cursor.insertFragment(QTextDocumentFragment::fromHtml(QByteArray::fromRawData(html, sizeof(html) / sizeof(html[0])))); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 2); + + /* + QTextCursor fourthCell = table->cellAt(1, 1).firstCursorPosition(); + fourthCell.movePosition(QTextCursor::NextBlock); + table = fourthCell.currentTable(); + QVERIFY(table); + QVERIFY(table != cursor.currentTable()); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 3); + */ + } + { + const char buggyHtml[] = "" + "<table>" + "<tr><td>First Cell<td>Second Cell" + "<tr><td>Third Cell<td>Fourth Cell" + "</table>"; + + QTextDocument doc; + QTextCursor cursor(&doc); + cursor.insertFragment(QTextDocumentFragment::fromHtml(QByteArray::fromRawData(buggyHtml, sizeof(buggyHtml) / sizeof(buggyHtml[0])))); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 2); + } + { + const char buggyHtml[] = "" + "<table>" + "<tr><th>First Cell<th>Second Cell" + "<tr><td>Third Cell<td>Fourth Cell" + "</table>"; + + QTextDocument doc; + QTextCursor cursor(&doc); + cursor.insertFragment(QTextDocumentFragment::fromHtml(QByteArray::fromRawData(buggyHtml, sizeof(buggyHtml) / sizeof(buggyHtml[0])))); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 2); + } + +} + +void tst_QTextDocumentFragment::tableImport3() +{ + // ### would be better to have tree tests for QTextHtmlParser + // make sure the p is a child of the td. If not the following td + // ends up outside the table, causing an assertion + const char html[] = "<table><tr><td><p></p></td><td></td></tr></table>"; + QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(QString::fromLatin1(html)); + QVERIFY(!fragment.isEmpty()); +} + +void tst_QTextDocumentFragment::tableImport4() +{ + const char html[] = "<table>" + "<tr><td>blah</td></tr>" + "<tr><td>blah</td><td>blah</td></tr>" + "</table>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + QCOMPARE(cursor.currentTable()->columns(), 2); +} + +void tst_QTextDocumentFragment::tableImport5() +{ + const char html[] = "<table>" + "<tr>" + " <td>Foo</td>" + " <td>Bar</td>" + " <td>Bleh</td>" + " <td>Blub</td>" + "</tr>" + "<tr>" + " <td>Ahh</td>" + " <td colspan=5>Gah</td>" + "</tr>" + "</table>"; + + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + QCOMPARE(cursor.currentTable()->rows(), 2); + QCOMPARE(cursor.currentTable()->columns(), 6); +} + +void tst_QTextDocumentFragment::textCopy() +{ + /* this test used to cause failing assertions in QTextDocumentFragment */ + /* copy&paste 'lo\bwor' */ + cursor.insertText("Hello"); + cursor.insertBlock(); + cursor.insertText("World"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, 3); + cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 3); + + QTextDocumentFragment fragment(cursor); + QVERIFY(!fragment.isEmpty()); + cursor.insertFragment(fragment); +} + +void tst_QTextDocumentFragment::copyWholeDocument() +{ + // used to cause the famous currentBlock.position() == pos + 1 failing assertion + cursor.insertText("\nHey\nBlah\n"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setBackground(Qt::blue); + doc->rootFrame()->setFrameFormat(fmt); + + QTextDocumentFragment fragment(cursor); + QVERIFY(true); // good if we reach this point :) + + cleanup(); + init(); + + fmt.setBackground(Qt::red); + doc->rootFrame()->setFrameFormat(fmt); + + cursor.insertFragment(fragment); + + QVERIFY(doc->rootFrame()->frameFormat().background().color() == Qt::red); +} + +void tst_QTextDocumentFragment::title() +{ + doc->setHtml(QString::fromLatin1("<html><head><title>Test</title></head><body>Blah</body></html>")); + QCOMPARE(doc->metaInformation(QTextDocument::DocumentTitle), QString::fromLatin1("Test")); +} + +void tst_QTextDocumentFragment::html_listIndents1() +{ + const char html[] = "<ul><li>Hey</li><li>Hah</li></ul>"; + setHtml(QString::fromLatin1(html)); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextList *list = cursor.currentList(); + QVERIFY(list); + QCOMPARE(list->format().indent(), 1); + QCOMPARE(cursor.block().blockFormat().indent(), 0); +} + +void tst_QTextDocumentFragment::html_listIndents2() +{ + const char html[] = "<ul><li>Hey<p>Hah</ul>"; + setHtml(QString::fromLatin1(html)); + cursor.movePosition(QTextCursor::Start); + QTextList *list = cursor.currentList(); + QVERIFY(list); + QCOMPARE(list->format().indent(), 1); + QCOMPARE(cursor.block().blockFormat().indent(), 0); + + cursor.movePosition(QTextCursor::NextBlock); + QCOMPARE(cursor.block().blockFormat().indent(), 1); +} + +void tst_QTextDocumentFragment::html_listIndents3() +{ + const char html[] = "<ul><li><p>Hah</ul>"; + setHtml(QString::fromLatin1(html)); + cursor.movePosition(QTextCursor::Start); + QTextList *list = cursor.currentList(); + QVERIFY(list); + QCOMPARE(list->format().indent(), 1); + QCOMPARE(cursor.block().blockFormat().indent(), 0); +} + +void tst_QTextDocumentFragment::html_listIndents4() +{ + const char html[] = "<ul><li>Foo</ul><p>This should not have the same indent as Foo"; + setHtml(QString::fromLatin1(html)); + cursor.movePosition(QTextCursor::Start); + QTextList *list = cursor.currentList(); + QVERIFY(list); + QCOMPARE(list->format().indent(), 1); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(!cursor.currentList()); + QCOMPARE(cursor.blockFormat().indent(), 0); +} + +void tst_QTextDocumentFragment::html_listIndents5() +{ + const char html[] = "<ul><li>Foo<p><li>Bar</li></ul>"; + setHtml(QString::fromLatin1(html)); + cursor.movePosition(QTextCursor::Start); + QTextList *list = cursor.currentList(); + QVERIFY(list); + QCOMPARE(list->format().indent(), 1); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentList() == list); + QCOMPARE(cursor.blockFormat().indent(), 0); +} + +void tst_QTextDocumentFragment::html_listIndents6() +{ + const char html[] = "<ul><li>Outer List<div class=\"testclass\"><ul><li>Nested Item 1</li></ul></div></li></ul>"; + setHtml(QString::fromLatin1(html)); + cursor.movePosition(QTextCursor::Start); + QTextList *list = cursor.currentList(); + QVERIFY(list); + QCOMPARE(list->format().indent(), 1); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentList() != list); + list = cursor.currentList(); + QVERIFY(list); + QCOMPARE(list->format().indent(), 2); + + QCOMPARE(cursor.blockFormat().indent(), 0); +} + +void tst_QTextDocumentFragment::blockCharFormat() +{ + const char html[] = "<p style=\"font-style:italic\"><span style=\"font-style:normal\">Test</span></p>"; + setHtml(QString::fromLatin1(html)); + QVERIFY(doc->begin().charFormat().fontItalic()); +} + +void tst_QTextDocumentFragment::blockCharFormatCopied() +{ + QTextCharFormat fmt; + fmt.setForeground(Qt::green); + cursor.setBlockCharFormat(fmt); + cursor.insertText("Test", QTextCharFormat()); + QTextDocumentFragment frag(doc); + cleanup(); + init(); + cursor.insertFragment(frag); + QVERIFY(cursor.blockCharFormat() == fmt); +} + +void tst_QTextDocumentFragment::initialBlock() +{ + const char html[] = "<p>Test</p>"; + setHtml(QString::fromLatin1(html)); + QCOMPARE(doc->blockCount(), 1); +} + +void tst_QTextDocumentFragment::clone() +{ + QTextBlockFormat mod; + mod.setAlignment(Qt::AlignCenter); + cursor.mergeBlockFormat(mod); + cursor.insertText("Blah"); + QVERIFY(cursor.blockFormat().alignment() == Qt::AlignCenter); + QTextDocumentFragment frag(doc); + cleanup(); + init(); + cursor.insertFragment(frag); + cursor.movePosition(QTextCursor::Start); + QVERIFY(cursor.blockFormat().alignment() == Qt::AlignCenter); +} + +void tst_QTextDocumentFragment::dontRemoveInitialBlockIfItHoldsObjectIndexedCharFormat() +{ + const char html[] = "<table><tr><td>cell one<td>cell two</tr><tr><td>cell three<td>cell four</tr></table>"; + QVERIFY(doc->begin().charFormat().objectIndex() == -1); + setHtml(QString::fromLatin1(html)); + int cnt = 0; + + int objectIndexOfLast = -1; + for (QTextBlock blk = doc->begin(); blk.isValid(); blk = blk.next()) { + ++cnt; + objectIndexOfLast = blk.charFormat().objectIndex(); + } + // beginning of frame for first cell + // + beginning of frame for second cell + // + beginning of frame for third cell + // + beginning of frame for fourth cell + // + end of frame + // + initial block + // ==> 6 + QCOMPARE(cnt, 6); + QVERIFY(objectIndexOfLast != -1); + QVERIFY(doc->begin().next().charFormat().objectIndex() != -1); +} + +void tst_QTextDocumentFragment::dosLineFeed() +{ + const char html[] = "<pre>Test\r\n</pre>Bar"; + setHtml(QString::fromLatin1(html)); + QVERIFY(!doc->toPlainText().contains('\r')); + QCOMPARE(doc->toPlainText(), QString("Test\nBar")); +} + +void tst_QTextDocumentFragment::unorderedListEnumeration() +{ + const char html[] = "<ul><ul><ul><li>Blah</li></ul></ul>"; + setHtml(QString::fromLatin1(html)); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListCircle); + + const char html2[] = "<ul><ul><ul type=disc><li>Blah</li></ul></ul>"; + setHtml(QString::fromLatin1(html2)); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListDisc); + +} + +void tst_QTextDocumentFragment::resetHasBlockAfterClosedBlockTags() +{ + // when closing tags we have to make sure hasBlock in import() gets resetted + const char html[] = "<body><table><tr><td><td><p></table><p></body>"; + setHtml(QString::fromLatin1(html)); + QVERIFY(!doc->isEmpty()); +} + +void tst_QTextDocumentFragment::ignoreStyleTags() +{ + const char html[] = "<body><style>Blah</style>Hello</body>"; + setHtml(QString::fromLatin1(html)); + QCOMPARE(doc->toPlainText(), QString("Hello")); +} + +void tst_QTextDocumentFragment::hrefAnchor() +{ + { + const char html[] = "<a href=\"test\">blah</a>"; + setHtml(QString::fromLatin1(html)); + QVERIFY(doc->begin().begin().fragment().charFormat().isAnchor()); + QCOMPARE(doc->begin().begin().fragment().charFormat().anchorHref(), QString::fromAscii("test")); + QVERIFY(doc->begin().begin().fragment().charFormat().fontUnderline() == true); + } + + { + // only hyperlinks should have special formatting + const char html[] = "<a>blah</a>"; + setHtml(QString::fromLatin1(html)); + QVERIFY(doc->begin().begin().fragment().charFormat().isAnchor()); + QVERIFY(doc->begin().begin().fragment().charFormat().fontUnderline() == false); + } +} + +void tst_QTextDocumentFragment::namedAnchorFragments() +{ + // named anchors should be 'invisible', but the fragment right after it should + // hold the attribute + const char html[] = "a<a name=\"test\" />blah"; + setHtml(QString::fromLatin1(html)); + + QTextBlock firstBlock = doc->begin(); + QVERIFY(firstBlock.isValid()); + + QTextBlock::Iterator it = firstBlock.begin(); + QVERIFY(!it.atEnd()); + + // the 'a' + QVERIFY(it.fragment().isValid()); + QCOMPARE(it.fragment().text(), QString::fromAscii("a")); + QVERIFY(it.fragment().charFormat().isAnchor() == false); + + // the 'b' of 'blah' as separate fragment with the anchor attribute + ++it; + QVERIFY(it.fragment().isValid()); + QCOMPARE(it.fragment().text(), QString::fromAscii("b")); + QVERIFY(it.fragment().charFormat().isAnchor()); + + // the 'lah' of 'blah' as remainder + ++it; + QVERIFY(it.fragment().isValid()); + QVERIFY(it.fragment().text().startsWith("lah")); + QVERIFY(it.fragment().charFormat().isAnchor() == false); +} + +void tst_QTextDocumentFragment::namedAnchorFragments2() +{ + const char html[] = "<p> <a name=\"foo\"> Hello"; + setHtml(QString::fromLatin1(html)); + + QCOMPARE(doc->toPlainText(), QString("Hello")); + + QTextBlock::Iterator it = doc->begin().begin(); + QVERIFY(!it.atEnd()); + + QCOMPARE(it.fragment().text(), QString::fromAscii("H")); + QVERIFY(it.fragment().charFormat().isAnchor()); + + ++it; + + QCOMPARE(it.fragment().text(), QString::fromAscii("ello")); + QVERIFY(!it.fragment().charFormat().isAnchor()); +} + +void tst_QTextDocumentFragment::namedAnchorFragments3() +{ + setHtml("<a name=\"target\" /><a name=\"target2\"/><span>Text</span>"); + + QCOMPARE(doc->toPlainText(), QString("Text")); + + QTextBlock::Iterator it = doc->begin().begin(); + QVERIFY(!it.atEnd()); + + QCOMPARE(it.fragment().text(), QString::fromAscii("T")); + QVERIFY(it.fragment().charFormat().isAnchor()); + QCOMPARE(it.fragment().charFormat().anchorName(), QString("target")); + QStringList targets; targets << "target" << "target2"; + QCOMPARE(it.fragment().charFormat().anchorNames(), targets); + + ++it; + + QCOMPARE(it.fragment().text(), QString::fromAscii("ext")); + QVERIFY(!it.fragment().charFormat().isAnchor()); +} + +void tst_QTextDocumentFragment::dontInheritAlignmentInTables() +{ + const char html[] = "<table align=center><tr><td>Hey</td></tr></table>"; + setHtml(QString::fromLatin1(html)); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + QVERIFY(cursor.currentTable()->cellAt(0, 0).isValid()); + QVERIFY(cursor.currentTable()->cellAt(0, 0).firstCursorPosition().block().next().blockFormat().alignment() != Qt::AlignHCenter); +} + +void tst_QTextDocumentFragment::cellBlockCount() +{ + const char html[] = "<table><tr><td>Hey</td></tr></table>"; + setHtml(QString::fromLatin1(html)); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + + QTextTableCell cell = cursor.currentTable()->cellAt(0, 0); + QVERIFY(cell.isValid()); + + int blockCount = 0; + for (QTextFrame::iterator it = cell.begin(); !it.atEnd(); ++it) { + QVERIFY(it.currentFrame() == 0); + QVERIFY(it.currentBlock().isValid()); + ++blockCount; + } + QCOMPARE(blockCount, 1); +} + +void tst_QTextDocumentFragment::cellBlockCount2() +{ + const char html[] = "<table><tr><td><p>Hey</p></td></tr></table>"; + setHtml(QString::fromLatin1(html)); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + + QTextTableCell cell = cursor.currentTable()->cellAt(0, 0); + QVERIFY(cell.isValid()); + + int blockCount = 0; + for (QTextFrame::iterator it = cell.begin(); !it.atEnd(); ++it) { + QVERIFY(it.currentFrame() == 0); + QVERIFY(it.currentBlock().isValid()); + ++blockCount; + } + QCOMPARE(blockCount, 1); +} + +void tst_QTextDocumentFragment::emptyTable() +{ + const char html[] = "<table></table>"; + setHtml(QString::fromLatin1(html)); + QVERIFY(true); // don't crash with a failing assertion +} + +void tst_QTextDocumentFragment::emptyTable2() +{ + const char html[] = "<table></td></tr></table><p>blah</p>"; + setHtml(QString::fromLatin1(html)); + QVERIFY(true); // don't crash with a failing assertion +} + +void tst_QTextDocumentFragment::emptyTable3() +{ + const char html[] = "<table><tr><td><table></table></td><td>Foobar</td></tr></table>"; + setHtml(QString::fromLatin1(html)); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 2); + QTextTableCell cell = table->cellAt(0, 0); + QVERIFY(cell.isValid()); + QVERIFY(cell.firstPosition() == cell.lastPosition()); + cell = table->cellAt(0, 1); + QTextCursor cursor = cell.firstCursorPosition(); + cursor.setPosition(cell.lastPosition(), QTextCursor::KeepAnchor); + QCOMPARE(cursor.selectedText(), QString("Foobar")); +} + +void tst_QTextDocumentFragment::doubleRowClose() +{ + const char html[] = "<table><tr><td>Blah</td></tr></tr><tr><td>Hm</td></tr></table>"; + setHtml(QString::fromLatin1(html)); + QVERIFY(true); // don't crash with a failing assertion +} + +void tst_QTextDocumentFragment::mayNotHaveChildren() +{ + // make sure the Hey does not end up as tag text for the img tag + const char html[] = "<img />Hey"; + setHtml(QString::fromLatin1(html)); + QCOMPARE(doc->toPlainText().mid(1), QString::fromAscii("Hey")); +} + +void tst_QTextDocumentFragment::inheritAlignment() +{ + // make sure attributes from the body tag get inherited + const char html[] = "<body align=right><p>Hey"; + setHtml(QString::fromLatin1(html)); + // html alignment is absolute + QVERIFY(doc->begin().blockFormat().alignment() == Qt::Alignment(Qt::AlignRight|Qt::AlignAbsolute)); +} + +void tst_QTextDocumentFragment::dontEmitEmptyNodeWhenEmptyTagIsFollowedByCloseTag() +{ + // make sure the Hey does not end up as tag text for the img tag + const char html[] = "<body align=right><p align=left>Blah<img></img><p>Hey"; + setHtml(QString::fromLatin1(html)); + QVERIFY(doc->begin().blockFormat().alignment() == Qt::Alignment(Qt::AlignLeft|Qt::AlignAbsolute)); + QVERIFY(doc->begin().next().blockFormat().alignment() == Qt::Alignment(Qt::AlignRight|Qt::AlignAbsolute)); +} + +void tst_QTextDocumentFragment::toPlainText() +{ + QString input = "Hello\nWorld"; + input += QChar::ParagraphSeparator; + input += "Blah"; + doc->setPlainText(input); + QCOMPARE(doc->blockCount(), 3); +} + +void tst_QTextDocumentFragment::copyTableRow() +{ + QTextDocumentFragment frag; + { + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("Blah"); + table->cellAt(0, 1).firstCursorPosition().insertText("Foo"); + table->cellAt(1, 0).firstCursorPosition().insertText("Bar"); + table->cellAt(1, 1).firstCursorPosition().insertText("Hah"); + + // select second row + cursor = table->cellAt(1, 1).firstCursorPosition(); + cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::KeepAnchor); + + QCOMPARE(table->cellAt(cursor.position()).row(), 1); + QCOMPARE(table->cellAt(cursor.position()).column(), 0); + QCOMPARE(table->cellAt(cursor.anchor()).row(), 1); + QCOMPARE(table->cellAt(cursor.anchor()).column(), 1); + + frag = QTextDocumentFragment(cursor); + } + { + QTextDocument doc2; + cursor = QTextCursor(&doc2); + cursor.insertFragment(frag); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + + QVERIFY(table); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 1); + + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("Bar")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Hah")); + } +} + +void tst_QTextDocumentFragment::copyTableColumn() +{ + QTextDocumentFragment frag; + { + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("Blah"); + table->cellAt(0, 1).firstCursorPosition().insertText("Foo"); + table->cellAt(1, 0).firstCursorPosition().insertText("Bar"); + table->cellAt(1, 1).firstCursorPosition().insertText("Hah"); + + // select second column + cursor = table->cellAt(0, 1).firstCursorPosition(); + cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); + + QCOMPARE(table->cellAt(cursor.anchor()).row(), 0); + QCOMPARE(table->cellAt(cursor.anchor()).column(), 1); + QCOMPARE(table->cellAt(cursor.position()).row(), 1); + QCOMPARE(table->cellAt(cursor.position()).column(), 1); + + frag = QTextDocumentFragment(cursor); + } + { + QTextDocument doc2; + cursor = QTextCursor(&doc2); + cursor.insertFragment(frag); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + + QVERIFY(table); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->rows(), 2); + + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("Foo")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Hah")); + } +} + +void tst_QTextDocumentFragment::copySubTable() +{ + QTextDocumentFragment frag; + { + QTextTableFormat fmt; + QVector<QTextLength> constraints; + constraints << QTextLength(QTextLength::PercentageLength, 16); + constraints << QTextLength(QTextLength::PercentageLength, 28); + constraints << QTextLength(QTextLength::PercentageLength, 28); + constraints << QTextLength(QTextLength::PercentageLength, 28); + fmt.setColumnWidthConstraints(constraints); + + QTextTable *table = cursor.insertTable(4, 4, fmt); + for (int row = 0; row < 4; ++row) + for (int col = 0; col < 4; ++col) + table->cellAt(row, col).firstCursorPosition().insertText(QString("%1/%2").arg(row).arg(col)); + + QCOMPARE(table->format().columnWidthConstraints().count(), table->columns()); + + // select 2x2 subtable + cursor = table->cellAt(1, 1).firstCursorPosition(); + cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor); + cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor); + + QCOMPARE(table->cellAt(cursor.anchor()).row(), 1); + QCOMPARE(table->cellAt(cursor.anchor()).column(), 1); + QCOMPARE(table->cellAt(cursor.position()).row(), 2); + QCOMPARE(table->cellAt(cursor.position()).column(), 2); + + frag = QTextDocumentFragment(cursor); + } + { + QTextDocument doc2; + cursor = QTextCursor(&doc2); + cursor.insertFragment(frag); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + + QVERIFY(table); + QVERIFY(table->format().columnWidthConstraints().isEmpty()); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 2); + + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("1/1")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("1/2")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("2/1")); + QCOMPARE(table->cellAt(1, 1).firstCursorPosition().block().text(), QString("2/2")); + } +} + +void tst_QTextDocumentFragment::html_textDecoration() +{ + const char html[] = "<span style='text-decoration: overline line-through underline'>Blah</span>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QByteArray::fromRawData(html, sizeof(html) / sizeof(html[0])))); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().fontUnderline()); + QVERIFY(cursor.charFormat().fontOverline()); + QVERIFY(cursor.charFormat().fontStrikeOut()); +} + +void tst_QTextDocumentFragment::html_infiniteLoop() +{ + { + // used to cause an infinite loop due to the lack of a space after the + // tag name + const char html[] = "<ahref=\"argl\">Link</a>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + QVERIFY(true); + } + + { + const char html[] = "<a href=\"\"a<"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + QVERIFY(true); + } +} + +void tst_QTextDocumentFragment::html_blockIndent() +{ + const char html[] = "<p style=\"-qt-block-indent:3;\">Test</p>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + QCOMPARE(cursor.blockFormat().indent(), 3); +} + +void tst_QTextDocumentFragment::html_listIndent() +{ + const char html[] = "<ul style=\"-qt-list-indent:4;\"><li>Blah</ul>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + QVERIFY(cursor.currentList()); + QCOMPARE(cursor.currentList()->format().indent(), 4); +} + +void tst_QTextDocumentFragment::html_whitespace_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<QString>("expectedPlainText"); + + QTest::newRow("1") << QString("<span>This is some test</span><span> with spaces between words</span>") + << QString("This is some test with spaces between words"); + + QTest::newRow("2") << QString("<span> </span><span>nowhitespacehereplease</span>") + << QString::fromLatin1("nowhitespacehereplease"); + + QTest::newRow("3") << QString("<span style=\"white-space: pre;\"> white space here </span>") + << QString::fromLatin1(" white space here "); + + QTest::newRow("4") << QString("<span style=\"white-space: pre-wrap;\"> white space here </span>") + << QString::fromLatin1(" white space here "); + + QTest::newRow("5") << QString("<a href=\"One.html\">One</a> <a href=\"Two.html\">Two</a> <b>Three</b>\n" + "<b>Four</b>") + << QString::fromLatin1("One Two Three Four"); + + QTest::newRow("6") << QString("<p>Testing: <b><i><u>BoldItalic</u></i></b> <i>Italic</i></p>") + << QString("Testing: BoldItalic Italic"); + + QTest::newRow("7") << QString("<table><tr><td>Blah</td></tr></table> <table border><tr><td>Foo</td></tr></table>") + << QString("\nBlah\n\nFoo\n"); + + QTest::newRow("8") << QString("<table><tr><td><i>Blah</i></td></tr></table> <i>Blub</i>") + << QString("\nBlah\nBlub"); + + QTest::newRow("task116492") << QString("<p>a<font=\"Times\"> b </font>c</p>") + << QString("a b c"); + + QTest::newRow("task121653") << QString("abc<b> def</b>") + << QString("abc def"); + + QTest::newRow("task122650") << QString("<p>Foo</p> Bar") + << QString("Foo\nBar"); + + QTest::newRow("task122650-2") << QString("<p>Foo</p> <p> Bar") + << QString("Foo \nBar"); + + QTest::newRow("task122650-3") << QString("<html>Before<pre>\nTest</pre>") + << QString("Before\nTest"); + + QTest::newRow("br-with-whitespace") << QString("Foo<br>\nBlah") + << QString("Foo\nBlah"); + + QTest::newRow("collapse-p-with-newline") << QString("Foo<p>\n<p>\n<p>\n<p>\n<p>\n<p>\nBar") + << QString("Foo\nBar"); + + QTest::newRow("table") << QString("<table><tr><td>Blah</td></tr></table>\nTest") + << QString("\nBlah\nTest"); + + QTest::newRow("table2") << QString("<table><tr><td><pre>\nTest\n</pre></td>\n </tr></table>") + << QString("\nTest\n"); + + QTest::newRow("table3") << QString("<table><tr><td><pre>\nTest\n</pre> \n \n </td></tr></table>") + << QString("\nTest \n"); +} + +void tst_QTextDocumentFragment::html_whitespace() +{ + QFETCH(QString, html); + QFETCH(QString, expectedPlainText); + + setHtml(html); + + QCOMPARE(doc->toPlainText(), expectedPlainText); +} + +void tst_QTextDocumentFragment::html_qt3Whitespace() +{ + QString text = "This text has some whitespace" + "\n and \nnewlines that \n should be ignored\n\n"; + const QString html = QString("<html><head><meta name=\"qrichtext\" content=\"1\" /></head><body>") + + text + + QString("</body></html>"); + + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + + text.remove(QChar::fromLatin1('\n')); + + QCOMPARE(doc->toPlainText(), text); +} + +void tst_QTextDocumentFragment::html_qt3WhitespaceWithFragments() +{ + QString text = "This text has some whitespace" + "\n and \nnewlines that \n should be ignored\n\n"; + const QString html = QString("<html><head><meta name=\"qrichtext\" content=\"1\" /></head><body>" + "blah blah<!--StartFragment--><span>") + + text + + QString("</span><!--EndFragment--></body></html>"); + + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + + text.remove(QChar::fromLatin1('\n')); + + QCOMPARE(doc->toPlainText(), text); +} + +void tst_QTextDocumentFragment::html_qt3WhitespaceAfterTags() +{ + QString text = " This text has some whitespace "; + const QString html = QString("<html><head><meta name=\"qrichtext\" content=\"1\" /></head><body><span>") + + text + + QString("</span></body></html>"); + + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + + QCOMPARE(doc->toPlainText(), text); +} + +void tst_QTextDocumentFragment::html_listStart1() +{ + // don't create a block for the <ul> element, even if there's some whitespace between + // it and the <li> + const char html[] = "<ul> <li>list item</li><ul>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QByteArray::fromRawData(html, sizeof(html) / sizeof(html[0])))); + + QCOMPARE(doc->blockCount(), 1); +} + +void tst_QTextDocumentFragment::html_listStart2() +{ + // unlike with html_listStart1 we want a block showing the 'buggy' text here + const char html[] = "<ul>buggy, but text should appear<li>list item</li><ul>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QByteArray::fromRawData(html, sizeof(html) / sizeof(html[0])))); + + QCOMPARE(doc->blockCount(), 2); +} + +void tst_QTextDocumentFragment::html_cssMargin() +{ + const char html[] = "<p style=\"margin-top: 1px; margin-bottom: 2px; margin-left: 3px; margin-right: 4px\">Test</p>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + const QTextBlockFormat fmt = cursor.blockFormat(); + QCOMPARE(fmt.topMargin(), qreal(1)); + QCOMPARE(fmt.bottomMargin(), qreal(2)); + QCOMPARE(fmt.leftMargin(), qreal(3)); + QCOMPARE(fmt.rightMargin(), qreal(4)); +} + +void tst_QTextDocumentFragment::html_hexEntities() +{ + const char html[] = "@"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + QCOMPARE(doc->begin().begin().fragment().text(), QString("@")); +} + +void tst_QTextDocumentFragment::html_decEntities() +{ + const char html[] = "@"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + QCOMPARE(doc->begin().begin().fragment().text(), QString("@")); +} + +void tst_QTextDocumentFragment::html_thCentered() +{ + const char html[] = "<table><tr><th>This should be centered</th></tr></table>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + + cursor.movePosition(QTextCursor::PreviousBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + + QVERIFY(table->cellAt(0, 0).begin().currentBlock().blockFormat().alignment() == Qt::AlignCenter); +} + +void tst_QTextDocumentFragment::orderedListNumbering() +{ + // Supporter issue 45941 - make sure _two_ separate lists + // are imported, which have their own numbering + const char html[] = "<html><body>" + "<ol><li>elem 1</li></ol>" + "<ol><li>elem 1</li></ol>" + "</body></html>"; + + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + + int numberOfLists = 0; + + cursor.movePosition(QTextCursor::Start); + QTextList *lastList = 0; + do { + QTextList *list = cursor.currentList(); + if (list && list != lastList) { + lastList = list; + ++numberOfLists; + } + } while (cursor.movePosition(QTextCursor::NextBlock)); + + QCOMPARE(numberOfLists, 2); +} + +void tst_QTextDocumentFragment::html_blockAfterList() +{ + const char html[] = "<ul><li>Foo</ul>This should be a separate paragraph and not be indented at the same level as Foo"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(html)); + + cursor.movePosition(QTextCursor::Start); + + QVERIFY(cursor.currentList()); + QCOMPARE(cursor.currentList()->format().indent(), 1); + + QVERIFY(cursor.movePosition(QTextCursor::NextBlock)); + QVERIFY(!cursor.currentList()); + QCOMPARE(cursor.blockFormat().indent(), 0); +} + +void tst_QTextDocumentFragment::html_subAndSuperScript() +{ + const char subHtml[] = "<sub>Subby</sub>"; + const char superHtml[] = "<sup>Super</sup>"; + const char subHtmlCss[] = "<span style=\"vertical-align: sub\">Subby</span>"; + const char superHtmlCss[] = "<span style=\"vertical-align: super\">Super</span>"; + const char alignmentInherited[] = "<sub><font face=\"Verdana\">Subby</font></sub>"; + + setHtml(subHtml); + QVERIFY(cursor.charFormat().verticalAlignment() == QTextCharFormat::AlignSubScript); + + setHtml(subHtmlCss); + QVERIFY(cursor.charFormat().verticalAlignment() == QTextCharFormat::AlignSubScript); + + setHtml(superHtml); + QVERIFY(cursor.charFormat().verticalAlignment() == QTextCharFormat::AlignSuperScript); + + setHtml(superHtmlCss); + QVERIFY(cursor.charFormat().verticalAlignment() == QTextCharFormat::AlignSuperScript); + + setHtml(alignmentInherited); + QVERIFY(cursor.charFormat().verticalAlignment() == QTextCharFormat::AlignSubScript); +} + +void tst_QTextDocumentFragment::html_cssColors() +{ + const char color[] = "<span style=\"color:red\"><span style=\"color:blue\">Blue</span></span>"; + setHtml(color); + QVERIFY(cursor.charFormat().foreground().color() == Qt::blue); + + const char rgbColor[] = "<span style=\"color:red\"><span style=\"color:rgb(0, 0, 255)\">Blue</span></span>"; + setHtml(rgbColor); + QVERIFY(cursor.charFormat().foreground().color() == Qt::blue); +} + +void tst_QTextDocumentFragment::obeyFragmentMarkersInImport() +{ + const char html[] = "This leading text should not appear<!--StartFragment--><span>Text</span><!--EndFragment-->This text at the end should not appear"; + setHtml(html); + + QCOMPARE(doc->toPlainText(), QString("Text")); +} + +void tst_QTextDocumentFragment::whitespaceWithFragmentMarkers() +{ + QString text(" text with leading and trailing whitespace "); + const char html[] = "This leading text should not appear<!--StartFragment-->%1<!--EndFragment-->This text at the end should not appear"; + setHtml(QString::fromLatin1(html).arg(text)); + + QString expected("text with leading and trailing whitespace "); + QCOMPARE(doc->toPlainText(), expected); +} + +void tst_QTextDocumentFragment::html_emptyParapgraphs1() +{ + const char html[] = "<p style=\"-qt-paragraph-type:empty;\"> </p><p>Two paragraphs</p>"; + setHtml(html); + + QCOMPARE(doc->blockCount(), 2); + QVERIFY(doc->begin().text().isEmpty()); + QCOMPARE(doc->begin().next().text(), QString("Two paragraphs")); +} + +void tst_QTextDocumentFragment::html_emptyParapgraphs2() +{ + const char html[] = "<p style=\"margin-left:80px\"></p><p>One paragraph</p>"; + setHtml(html); + + QCOMPARE(doc->blockCount(), 1); + QCOMPARE(cursor.blockFormat().leftMargin(), qreal(0)); + + const char html2[] = "<p style=\"margin-left:80px\"></p>One paragraph"; + setHtml(html2); + QCOMPARE(doc->blockCount(), 1); + QCOMPARE(cursor.blockFormat().leftMargin(), qreal(0)); + + const char html3[] = "<p style=\"margin-left:80px\">Foo</p><p></p>Two paragraphs"; + setHtml(html3); + QCOMPARE(doc->blockCount(), 2); + cursor = QTextCursor(doc); + QCOMPARE(cursor.blockFormat().leftMargin(), qreal(80)); + QCOMPARE(cursor.block().next().blockFormat().leftMargin(), qreal(0)); +} + +void tst_QTextDocumentFragment::html_emptyParagraphs3() +{ + const char html[] = "<ul><p>Foo</p><p></p></ul><h4>Bar</h4>"; + + setHtml(html); + + QCOMPARE(doc->blockCount(), 2); + + cursor = QTextCursor(doc); + QCOMPARE(cursor.block().next().blockFormat().indent(), 0); +} + +void tst_QTextDocumentFragment::html_emptyParagraphs4() +{ + const char html[] = "<p>foo</p><p style=\"page-break-before: always\"></p><p>bar</p>"; + setHtml(html); + + QTextBlock block = doc->begin(); + QVERIFY(block.isValid()); + QCOMPARE(block.text(), QString("foo")); + block = block.next(); + QVERIFY(block.isValid()); + QTextBlockFormat bf = block.blockFormat(); + QVERIFY(bf.hasProperty(QTextFormat::PageBreakPolicy)); + QCOMPARE(bf.pageBreakPolicy(), QTextFormat::PageBreak_AlwaysBefore); + QCOMPARE(block.text(), QString("bar")); + + const char html2[] = "<p>foo</p><p style=\"page-break-after: always\"></p><p>bar</p>"; + setHtml(html2); + + block = doc->begin(); + QVERIFY(block.isValid()); + QCOMPARE(block.text(), QString("foo")); + block = block.next(); + QVERIFY(block.isValid()); + bf = block.blockFormat(); + QVERIFY(bf.hasProperty(QTextFormat::PageBreakPolicy)); + QCOMPARE(bf.pageBreakPolicy(), QTextFormat::PageBreak_AlwaysBefore); // after the empty line means it should appear for 'bar' + QCOMPARE(block.text(), QString("bar")); +} + +void tst_QTextDocumentFragment::html_font() +{ + const char html[] = "<font color=\"blue\"><p>Hah</p></font>"; + setHtml(html); + + QVERIFY(cursor.charFormat().foreground().color() == Qt::blue); + QVERIFY(cursor.blockCharFormat().foreground().color() == Qt::blue); +} + +void tst_QTextDocumentFragment::html_fontSize() +{ + const char html[] = "<font size=\"2\">Hah</font>"; + setHtml(html); + + QCOMPARE(cursor.charFormat().property(QTextFormat::FontSizeAdjustment).toInt(), -1); +} + +void tst_QTextDocumentFragment::html_fontSizeAdjustment() +{ + const char html[] = "<font size=\"7\"><b>Hah</b></font>"; + setHtml(html); + + QCOMPARE(cursor.charFormat().property(QTextFormat::FontSizeAdjustment).toInt(), 4); + QCOMPARE(cursor.charFormat().fontWeight(), int(QFont::Bold)); +} + +void tst_QTextDocumentFragment::html_cssFontSize() +{ + const char html[] = "<span style=\"font-size: 50pt\">Foo</span>"; + setHtml(html); + + QCOMPARE(cursor.charFormat().property(QTextFormat::FontPointSize).toInt(), 50); + + const char html2[] = "<span style=\"font-size: 50px\">Foo</span>"; + setHtml(html2); + + QCOMPARE(cursor.charFormat().property(QTextFormat::FontPixelSize).toInt(), 50); + + const char html3[] = "<span style=\"font-size: large\">Foo</span>"; + setHtml(html3); + + QCOMPARE(cursor.charFormat().property(QTextFormat::FontSizeAdjustment).toInt(), 1); +} + +void tst_QTextDocumentFragment::html_cssShorthandFont() +{ + { + const char html[] = "<span style=\"font: 50px sans-serif\">Foo</span>"; + setHtml(html); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontPixelSize).toInt(), 50); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontFamily).toString(), QString("sans-serif")); + } + { + const char html[] = "<span style=\"font: 50pt sans-serif\">Foo</span>"; + setHtml(html); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontPointSize).toInt(), 50); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontFamily).toString(), QString("sans-serif")); + } + { + const char html[] = "<span style='font:7.0pt \"Times New Roman\"'>Foo</span>"; + setHtml(html); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontPointSize).toInt(), 7); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontFamily).toString(), QString("Times New Roman")); + } + { + const char html[] = "<span style='font:bold 7.0pt'>Foo</span>"; + setHtml(html); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontWeight).toInt(), int(QFont::Bold)); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontPointSize).toInt(), 7); + } + { + const char html[] = "<span style='font:bold italic 7.0pt'>Foo</span>"; + setHtml(html); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontWeight).toInt(), int(QFont::Bold)); + QCOMPARE(cursor.charFormat().property(QTextFormat::FontItalic).toBool(), true); + } +} + +void tst_QTextDocumentFragment::html_bodyBgColor() +{ + const char html[] = "<body bgcolor=\"blue\">Foo</body>"; + doc->setHtml(html); + + QVERIFY(doc->rootFrame()->frameFormat().background().color() == Qt::blue); +} + +void tst_QTextDocumentFragment::html_qtBgColor() +{ + const char html[] = "<qt bgcolor=\"blue\">Foo</qt>"; + doc->setHtml(html); + + QVERIFY(doc->rootFrame()->frameFormat().background().color() == Qt::blue); +} + +void tst_QTextDocumentFragment::html_bodyBackground() +{ + const char html[] = "<body background=\"foo.png\">Foo</body>"; + doc->setHtml(html); + + QVERIFY(doc->rootFrame()->frameFormat().background().style() == Qt::TexturePattern); +} + +void tst_QTextDocumentFragment::html_tableCellBackground() +{ + const char html[] = "<body><table><tr><td background=\"foo.png\">Foo</td></tr></table></body>"; + doc->setHtml(html); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + + QTextTableCell cell = table->cellAt(0, 0); + QVERIFY(cell.format().background().style() == Qt::TexturePattern); +} + +void tst_QTextDocumentFragment::css_bodyBackground() +{ + const char html[] = "<body style=\"background-image:url('foo.png')\">Foo</body>"; + doc->setHtml(html); + + QVERIFY(doc->rootFrame()->frameFormat().background().style() == Qt::TexturePattern); +} + +void tst_QTextDocumentFragment::css_tableCellBackground() +{ + const char html[] = "<body><table><tr><td style=\"background-image:url('foo.png')\">Foo</td></tr></table></body>"; + doc->setHtml(html); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + + QTextTableCell cell = table->cellAt(0, 0); + QVERIFY(cell.format().background().style() == Qt::TexturePattern); +} + +void tst_QTextDocumentFragment::css_cellPaddings() +{ + const char html[] = "<body><table><tr><td style=\"padding-left:1\">Foo</td>" + "<td style=\"padding-right:1\"></td><td style=\"padding-top:10\"></td>" + "<td style=\"padding-bottom:5\"></td><td style=\"padding:15\"></td></tr></table></body>"; + doc->setHtml(html); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + + QTextTableCell cell = table->cellAt(0, 0); + QCOMPARE(cell.format().toTableCellFormat().leftPadding(), qreal(1)); + cell = table->cellAt(0, 1); + QCOMPARE(cell.format().toTableCellFormat().rightPadding(), qreal(1)); + cell = table->cellAt(0, 2); + QCOMPARE(cell.format().toTableCellFormat().topPadding(), qreal(10)); + cell = table->cellAt(0, 3); + QCOMPARE(cell.format().toTableCellFormat().bottomPadding(), qreal(5)); + cell = table->cellAt(0, 4); + QCOMPARE(cell.format().toTableCellFormat().leftPadding(), qreal(15)); + QCOMPARE(cell.format().toTableCellFormat().rightPadding(), qreal(15)); + QCOMPARE(cell.format().toTableCellFormat().topPadding(), qreal(15)); + QCOMPARE(cell.format().toTableCellFormat().bottomPadding(), qreal(15)); +} + +void tst_QTextDocumentFragment::html_blockLevelDiv() +{ + const char html[] = "<div align=right><b>Hello World"; + setHtml(html); + + QCOMPARE(doc->begin().blockFormat().alignment(), Qt::AlignRight|Qt::AlignAbsolute); + QVERIFY(doc->begin().next() == doc->end()); +} + +void tst_QTextDocumentFragment::html_spanNesting() +{ + const char html[] = "<span style=\"color:black\">a<span style=\"color:red\">b<span style=\"color:black\">c</span></span>d</span>"; + setHtml(html); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground() == Qt::black); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground() == Qt::red); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground() == Qt::black); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground() == Qt::black); +} + +void tst_QTextDocumentFragment::html_nestedLists() +{ + const char html[] = "<p><ul><li>Foo<ul><li>In nested list</li></ul></li><li>Last item</li></ul></p>"; + setHtml(html); + + cursor.movePosition(QTextCursor::Start); + QTextList *firstList = cursor.currentList(); + QVERIFY(firstList); + QCOMPARE(firstList->format().indent(), 1); + + cursor.movePosition(QTextCursor::NextBlock); + QTextList *secondList = cursor.currentList(); + QVERIFY(secondList); + QVERIFY(secondList != firstList); + QCOMPARE(cursor.currentList()->format().indent(), 2); + + cursor.movePosition(QTextCursor::NextBlock); + QTextList *thirdList = cursor.currentList(); + QVERIFY(thirdList); + QVERIFY(thirdList == firstList); +} + +void tst_QTextDocumentFragment::noSpecialCharactersInPlainText() +{ + cursor.insertTable(2, 2); + cursor.insertBlock(); + cursor.insertText(QString(QChar::LineSeparator)); + cursor.insertText(QString(QChar::Nbsp)); + + QString plain = doc->toPlainText(); + QVERIFY(!plain.contains(QChar::ParagraphSeparator)); + QVERIFY(!plain.contains(QChar::Nbsp)); + QVERIFY(!plain.contains(QTextBeginningOfFrame)); + QVERIFY(!plain.contains(QTextEndOfFrame)); + QVERIFY(!plain.contains(QChar::LineSeparator)); + + plain = QTextDocumentFragment(doc).toPlainText(); + QVERIFY(!plain.contains(QChar::ParagraphSeparator)); + QVERIFY(!plain.contains(QChar::Nbsp)); + QVERIFY(!plain.contains(QTextBeginningOfFrame)); + QVERIFY(!plain.contains(QTextEndOfFrame)); + QVERIFY(!plain.contains(QChar::LineSeparator)); +} + +void tst_QTextDocumentFragment::html_doNotInheritBackground() +{ + const char html[] = "<html><body bgcolor=\"blue\"><p>Blah</p></body></html>"; + doc->setHtml(html); + + for (QTextBlock block = doc->begin(); + block.isValid(); block = block.next()) { + QVERIFY(block.blockFormat().hasProperty(QTextFormat::BackgroundBrush) == false); + } + + QVERIFY(doc->rootFrame()->frameFormat().hasProperty(QTextFormat::BackgroundBrush)); + QVERIFY(doc->rootFrame()->frameFormat().background().color() == Qt::blue); +} + +void tst_QTextDocumentFragment::html_inheritBackgroundToInlineElements() +{ + const char html[] = "<span style=\"background: blue\">Foo<span>Bar</span></span>"; + doc->setHtml(html); + + int fragmentCount = 0; + + QTextBlock block = doc->begin(); + for (QTextBlock::Iterator it = block.begin(); + !it.atEnd(); ++it, ++fragmentCount) { + + const QTextFragment fragment = it.fragment(); + if (fragmentCount == 0) { + QCOMPARE(fragment.text(), QString("FooBar")); + QVERIFY(fragment.charFormat().background().color() == Qt::blue); + } + } + + QCOMPARE(fragmentCount, 1); +} + +void tst_QTextDocumentFragment::html_doNotInheritBackgroundFromBlockElements() +{ + const char html[] = "<p style=\"background: blue\"><span>Foo</span></span>"; + doc->setHtml(html); + + int fragmentCount = 0; + + QTextBlock block = doc->begin(); + for (QTextBlock::Iterator it = block.begin(); + !it.atEnd(); ++it, ++fragmentCount) { + + const QTextFragment fragment = it.fragment(); + if (fragmentCount == 0) { + QCOMPARE(fragment.text(), QString("Foo")); + QVERIFY(!fragment.charFormat().hasProperty(QTextFormat::BackgroundBrush)); + } + } + + QCOMPARE(fragmentCount, 1); +} +void tst_QTextDocumentFragment::html_nobr() +{ + const QString input = "Blah Foo Bar"; + const QString html = QString::fromLatin1("<html><body><p><nobr>") + input + QString::fromLatin1("</nobr></p></body></html>"); + setHtml(html); + + QString text = doc->begin().begin().fragment().text(); + QString expectedText = input; + expectedText.replace(QRegExp("\\s+"), QString(QChar::Nbsp)); + QCOMPARE(text, expectedText); +} + +void tst_QTextDocumentFragment::fromPlainText() +{ + QString plainText; + plainText = "Hello\nWorld\r\nBlub"; + plainText += QChar::ParagraphSeparator; + // TextEdit on OS 10 gives us OS 9 style linefeeds + // when copy & pasteing multi-line plaintext. + plainText += "OS9IsOldSchool\r"; + plainText += "Last Parag"; + + doc->setPlainText(plainText); + + int blockCount = 0; + for (QTextBlock block = doc->begin(); block.isValid(); block = block.next()) { + QVERIFY(!block.text().contains(QLatin1Char('\n'))); + QVERIFY(!block.text().contains(QLatin1Char('\r'))); + QVERIFY(!block.text().contains(QChar::ParagraphSeparator)); + + if (blockCount == 0) + QCOMPARE(block.text(), QString("Hello")); + else if (blockCount == 1) + QCOMPARE(block.text(), QString("World")); + else if (blockCount == 2) + QCOMPARE(block.text(), QString("Blub")); + else if (blockCount == 3) + QCOMPARE(block.text(), QString("OS9IsOldSchool")); + else if (blockCount == 4) + QCOMPARE(block.text(), QString("Last Parag")); + + + ++blockCount; + } + + QCOMPARE(blockCount, 5); +} + +void tst_QTextDocumentFragment::fromPlainText2() +{ + doc->setPlainText("Hello World"); + QCOMPARE(QTextDocumentFragment(doc).toPlainText(), doc->toPlainText()); +} + +void tst_QTextDocumentFragment::html_closingImageTag() +{ + const char html[] = "<span style=\"font-size: 10pt\"><span style=\"font-size: 40pt\">Blah<img src=\"blah\"></img>Foo</span></span>"; + setHtml(html); + + int fragmentCount = 0; + + QTextBlock block = doc->begin(); + for (QTextBlock::Iterator it = block.begin(); + !it.atEnd(); ++it, ++fragmentCount) { + + const QTextFragment fragment = it.fragment(); + if (fragmentCount == 0) { + QCOMPARE(fragment.text(), QString("Blah")); + QCOMPARE(fragment.charFormat().fontPointSize(), qreal(40)); + } else if (fragmentCount == 1) { + QCOMPARE(fragment.text(), QString(QChar::ObjectReplacementCharacter)); + } else if (fragmentCount == 2) { + QCOMPARE(fragment.text(), QString("Foo")); + QCOMPARE(fragment.charFormat().fontPointSize(), qreal(40)); + } + } + + QCOMPARE(fragmentCount, 3); +} + +void tst_QTextDocumentFragment::html_emptyDocument() +{ + const char html[] = "<html><body><p style=\"-qt-paragraph-type:empty;\"></p></body></html>"; + setHtml(html); + QCOMPARE(doc->blockCount(), 1); +} + +void tst_QTextDocumentFragment::html_closingTag() +{ + const char html[] = "<i />text"; + setHtml(html); + + QVERIFY(!cursor.charFormat().fontItalic()); +} + +void tst_QTextDocumentFragment::html_anchorAroundImage() +{ + const char html[] = "<a href=\"http://www.troll.no\"><img src=test.png></a>"; + setHtml(html); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QTextImageFormat fmt = cursor.charFormat().toImageFormat(); + QCOMPARE(fmt.name(), QString("test.png")); + QVERIFY(fmt.isAnchor()); + QCOMPARE(fmt.anchorHref(), QString("http://www.troll.no")); +} + +void tst_QTextDocumentFragment::html_floatBorder() +{ + const char html[] = "<table border=1.2><tr><td>Foo"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + QCOMPARE(cursor.currentTable()->format().border(), qreal(1.2)); +} + +void tst_QTextDocumentFragment::html_frameImport() +{ + 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"); + + QTextDocumentFragment frag(doc); + cleanup(); + init(); + frag = QTextDocumentFragment::fromHtml(frag.toHtml()); + cursor.insertFragment(frag); + + QList<QTextFrame *> childFrames = doc->rootFrame()->childFrames(); + QVERIFY(childFrames.count() == 1); + QTextFrame *frame = childFrames.first(); + QCOMPARE(frame->frameFormat().margin(), ffmt.margin()); + QCOMPARE(frame->frameFormat().border(), ffmt.border()); +} + +void tst_QTextDocumentFragment::html_frameImport2() +{ + QTextFrameFormat ffmt; + ffmt.setBorder(1); + ffmt.setPosition(QTextFrameFormat::FloatRight); + ffmt.setLeftMargin(200); + ffmt.setTopMargin(100); + ffmt.setBottomMargin(50); + ffmt.setRightMargin(250); + ffmt.setWidth(100); + ffmt.setHeight(50); + ffmt.setBackground(QColor("#00ff00")); + cursor.insertFrame(ffmt); + cursor.insertText("Hello World"); + + QTextDocumentFragment frag(doc); + cleanup(); + init(); + frag = QTextDocumentFragment::fromHtml(frag.toHtml()); + cursor.insertFragment(frag); + + QList<QTextFrame *> childFrames = doc->rootFrame()->childFrames(); + QVERIFY(childFrames.count() == 1); + QTextFrame *frame = childFrames.first(); + QCOMPARE(frame->frameFormat().topMargin(), ffmt.topMargin()); + QCOMPARE(frame->frameFormat().bottomMargin(), ffmt.bottomMargin()); + QCOMPARE(frame->frameFormat().leftMargin(), ffmt.leftMargin()); + QCOMPARE(frame->frameFormat().rightMargin(), ffmt.rightMargin()); + QCOMPARE(frame->frameFormat().border(), ffmt.border()); +} + +void tst_QTextDocumentFragment::html_dontAddMarginsAcrossTableCells() +{ + const char html[] = "<table style=\"margin-left: 100px;\"><tr><td><p style=\"margin-left:50px;\">Foo</p></td></tr></table>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + + QList<QTextFrame *> childFrames = doc->rootFrame()->childFrames(); + QVERIFY(childFrames.count() == 1); + QTextFrame *frame = childFrames.first(); + cursor = frame->firstCursorPosition(); + QCOMPARE(cursor.blockFormat().leftMargin(), qreal(50.0)); +} + +void tst_QTextDocumentFragment::html_dontMergeCenterBlocks() +{ + const char html[] = "<center>This should be centered</center>And this should not be centered anymore"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + + QCOMPARE(doc->blockCount(), 2); + QTextBlock blk = doc->begin(); + QVERIFY(blk.blockFormat().alignment() == Qt::AlignCenter); + blk = blk.next(); + QVERIFY(blk.blockFormat().alignment() != Qt::AlignCenter); +} + +void tst_QTextDocumentFragment::html_tableCellBgColor() +{ + const char html[] = "<table><tr><td bgcolor=\"blue\">Test<p>Second Parag</p></td></tr></table>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + + QTextTableCell cell = table->cellAt(0, 0); + QVERIFY(cell.format().background().color() == Qt::blue); +} + +void tst_QTextDocumentFragment::html_tableCellBgColor2() +{ + const char html[] = "<table><tr><td bgcolor=\"blue\"><table><tr><td>Blah</td></tr></table></td></tr></table>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + + QTextTableCell cell = table->cellAt(0, 0); + QVERIFY(cell.format().background().color() == Qt::blue); + + QTextFrame::Iterator it = cell.begin(); + QVERIFY(!it.atEnd()); + QVERIFY(it.currentFrame() == 0); + QVERIFY(it.currentBlock().isValid()); + + ++it; + QVERIFY(!it.atEnd()); + QVERIFY(it.currentFrame() != 0); + QVERIFY(!it.currentBlock().isValid()); + + ++it; + QVERIFY(!it.atEnd()); + QVERIFY(it.currentFrame() == 0); + QVERIFY(it.currentBlock().isValid()); + QVERIFY(it.currentBlock().blockFormat().background() == QBrush(Qt::NoBrush)); + + ++it; + QVERIFY(it.atEnd()); +} + +void tst_QTextDocumentFragment::html_cellSkip() +{ + const char html[] = "" +"<table border>" +" <tr>" +" <td>First Cell</td>" +" </tr>" +" <tr>" +" <td>Second Cell</td>" +" <td>Third Cell</td>" +" </tr>" +"</table>"; + + setHtml(html); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QVERIFY(table->columns() == 2 && table->rows() == 2); + + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First Cell")); + QVERIFY(table->cellAt(0, 1).firstCursorPosition().block().text().isEmpty()); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Second Cell")); + QCOMPARE(table->cellAt(1, 1).firstCursorPosition().block().text(), QString("Third Cell")); +} + +void tst_QTextDocumentFragment::nonZeroMarginOnImport() +{ + // specify bgcolor so that the html import creates a root frame format + setHtml("<html><body bgcolor=\"#00ff00\"><b>Hello World</b></body></html>"); + QVERIFY(doc->rootFrame()->frameFormat().margin() > 0.0); +} + +void tst_QTextDocumentFragment::html_charFormatPropertiesUnset() +{ + setHtml("Hello World"); + QVERIFY(doc->begin().begin().fragment().charFormat().properties().isEmpty()); +} + +void tst_QTextDocumentFragment::html_headings() +{ + setHtml("<h1>foo</h1>bar"); + QCOMPARE(doc->blockCount(), 2); +} + +void tst_QTextDocumentFragment::html_quotedFontFamily() +{ + setHtml("<div style=\"font-family: 'Foo Bar';\">Test</div>"); + QCOMPARE(doc->begin().begin().fragment().charFormat().fontFamily(), QString("Foo Bar")); + + setHtml("<div style='font-family: \"Foo Bar\";'>Test</div>"); + QCOMPARE(doc->begin().begin().fragment().charFormat().fontFamily(), QString("Foo Bar")); + + setHtml("<div style='font-family: \"Foo Bar\";'>Test</div>"); + QCOMPARE(doc->begin().begin().fragment().charFormat().fontFamily(), QString("Foo Bar")); + + setHtml("<div style='font-family: Foo\n Bar;'>Test</div>"); + QCOMPARE(doc->begin().begin().fragment().charFormat().fontFamily(), QString("Foo Bar")); + + setHtml("<div style='font-family: Foo\n Bar, serif, \"bar foo\";'>Test</div>"); + QCOMPARE(doc->begin().begin().fragment().charFormat().fontFamily(), QString("Foo Bar,serif,bar foo")); + +} + +void tst_QTextDocumentFragment::defaultFont() +{ + QFont f; + f.setFamily("Courier New"); + f.setBold(true); + f.setItalic(true); + f.setStrikeOut(true); // set here but deliberately ignored for the html export + f.setPointSize(100); + doc->setDefaultFont(f); + doc->setPlainText("Hello World"); + const QString html = doc->toHtml(); + QLatin1String str("<body style=\" font-family:'Courier New'; font-size:100pt; font-weight:600; font-style:italic;\">"); + QVERIFY(html.contains(str)); +} + +void tst_QTextDocumentFragment::html_spanBackgroundColor() +{ + setHtml("<span style=\"background-color: blue\">Foo</span>"); + QVERIFY(doc->begin().begin().fragment().charFormat().background().color() == QColor(Qt::blue)); +} + +void tst_QTextDocumentFragment::html_brokenTitle_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<QString>("expectedBody"); + QTest::addColumn<QString>("expectedTitle"); + + QTest::newRow("brokentitle") << QString("<html><head><title>Foo<b>bar</b></title></head><body>Blah</body></html>") + << QString("Blah") << QString("Foo"); + QTest::newRow("brokentitle2") << QString("<html><head><title>Foo<font color=red>i</font>t<font color=red>i</font>Blub</title></head><body>Blah</body></html>") + << QString("Blah") << QString("Foo"); + QTest::newRow("entities") << QString("<html><head><title>Foo<bar</title></head><body>Blah</body></html>") + << QString("Blah") << QString("Foo<bar"); + QTest::newRow("unclosedtitle") << QString("<html><head><title>Foo</head><body>Blah</body></html>") + << QString("Blah") << QString("Foo"); +} + +void tst_QTextDocumentFragment::html_brokenTitle() +{ + QFETCH(QString, html); + QFETCH(QString, expectedBody); + QFETCH(QString, expectedTitle); + doc->setHtml(html); + QCOMPARE(doc->toPlainText(), expectedBody); + QCOMPARE(doc->metaInformation(QTextDocument::DocumentTitle), expectedTitle); +} + +void tst_QTextDocumentFragment::html_blockVsInline() +{ + { + setHtml("<html><body><div><b>Foo<div>Bar"); + QVERIFY(cursor.charFormat().fontWeight() == QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() == QFont::Bold); + } + { + setHtml("<html><body><p><b>Foo<p>Bar"); + QVERIFY(cursor.charFormat().fontWeight() != QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() != QFont::Bold); + } + { + setHtml("<html><body><b><center>Foo</center></b>"); + QVERIFY(cursor.charFormat().fontWeight() == QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() == QFont::Bold); + } + { + setHtml("<html><body><b><p>Foo"); + QVERIFY(cursor.charFormat().fontWeight() == QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() == QFont::Bold); + } + { + setHtml("<html><body><b><p>Foo<p>Bar"); + QVERIFY(cursor.charFormat().fontWeight() == QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() == QFont::Bold); + } + { + setHtml("<div><b>Foo<div>Bar"); + QVERIFY(cursor.charFormat().fontWeight() == QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() == QFont::Bold); + } + { + setHtml("<p><b>Foo<p>Bar"); + QVERIFY(cursor.charFormat().fontWeight() != QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() != QFont::Bold); + } + { + setHtml("<b><center>Foo</center></b>"); + QVERIFY(cursor.charFormat().fontWeight() == QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() == QFont::Bold); + } + { + setHtml("<b><p>Foo"); + QVERIFY(cursor.charFormat().fontWeight() == QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() == QFont::Bold); + } + { + setHtml("<b><p>Foo<p>Bar"); + QVERIFY(cursor.charFormat().fontWeight() == QFont::Bold); + QVERIFY(cursor.blockCharFormat().fontWeight() == QFont::Bold); + } +} + +void tst_QTextDocumentFragment::html_tbody() +{ + setHtml("<table><thead><tr><td>First Cell</td></tr></thead><tbody><tr><td>Second Cell</td></tr></tbody></table>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->format().headerRowCount(), 1); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First Cell")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Second Cell")); +} + +void tst_QTextDocumentFragment::html_nestedTables() +{ + setHtml("<table>" + " <tr><td>" + "" + " <table>" + " <tr><td>Hello</td></tr>" + " </table>" + "" + " <table>" + " <tr><td>World</td></tr>" + " </table>" + "" + " </td></tr>" + "</table>" + ); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 1); + + cursor = table->cellAt(0, 0).firstCursorPosition(); + cursor.movePosition(QTextCursor::NextBlock); + + QTextTable *firstNestedTable = cursor.currentTable(); + QVERIFY(firstNestedTable); + QVERIFY(firstNestedTable->parentFrame() == table); + QCOMPARE(firstNestedTable->rows(), 1); + QCOMPARE(firstNestedTable->columns(), 1); + QCOMPARE(firstNestedTable->cellAt(0, 0).firstCursorPosition().block().text(), QString("Hello")); + + while (cursor.currentTable() == firstNestedTable + && cursor.movePosition(QTextCursor::NextBlock)) + ; + + QVERIFY(!cursor.isNull()); + QVERIFY(cursor.currentTable() == table); + + cursor.movePosition(QTextCursor::NextBlock); + + QTextTable *secondNestedTable = cursor.currentTable(); + QVERIFY(secondNestedTable); + QVERIFY(secondNestedTable->parentFrame() == table); + QCOMPARE(secondNestedTable->rows(), 1); + QCOMPARE(secondNestedTable->columns(), 1); + QCOMPARE(secondNestedTable->cellAt(0, 0).firstCursorPosition().block().text(), QString("World")); +} + +void tst_QTextDocumentFragment::html_rowSpans() +{ + setHtml("" + "<table border=\"1\" width=\"100%\">" + " <tr>" + " <td rowspan=\"2\">blah</td>" + " <td rowspan=\"2\">foo</td>" + " </tr>" + " <tr></tr>" + " <tr>" + " <td rowspan=\"2\">blubb</td>" + " <td rowspan=\"2\">baz</td>" + " </tr>" + " <tr></tr>" + "</table>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 4); + QCOMPARE(table->columns(), 2); + + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("blah")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("foo")); + + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("blah")); + QCOMPARE(table->cellAt(1, 1).firstCursorPosition().block().text(), QString("foo")); + + QCOMPARE(table->cellAt(2, 0).firstCursorPosition().block().text(), QString("blubb")); + QCOMPARE(table->cellAt(2, 1).firstCursorPosition().block().text(), QString("baz")); + + QCOMPARE(table->cellAt(3, 0).firstCursorPosition().block().text(), QString("blubb")); + QCOMPARE(table->cellAt(3, 1).firstCursorPosition().block().text(), QString("baz")); +} + +void tst_QTextDocumentFragment::html_rowSpans2() +{ + setHtml("" + "<html><body>" + "<table border=\"1\">" + "<tr>" + "<td>Row 1 col 1</td>" + "</tr>" + "<tr>" + "<td rowspan=\"3\">Row 2 col 1, rowspan 3</td>" + "<td>Row 2 col 2</td>" + "</tr>" + "<tr>" + "<td rowspan=\"2\">Row 3 col 2, rowspan 2</td>" + "</tr>" + "<tr>" + "</tr>" + "</table>" + "</body></html>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 4); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 1).rowSpan(), 1); + QCOMPARE(table->cellAt(1, 0).rowSpan(), 3); + QCOMPARE(table->cellAt(2, 1).rowSpan(), 2); +} + +void tst_QTextDocumentFragment::html_implicitParagraphs() +{ + setHtml("<p>foo</p>bar"); + QCOMPARE(doc->blockCount(), 2); +} + +void tst_QTextDocumentFragment::html_missingCloseTag() +{ + setHtml("<font color=\"red\"><span style=\"color:blue\">blue</span></span> red</font>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground().color() == Qt::blue); + cursor.movePosition(QTextCursor::NextWord); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground().color() == Qt::red); +} + +void tst_QTextDocumentFragment::html_anchorColor() +{ + setHtml("<span style=\"color: red;\">Red</span>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground().color() == Qt::red); + + setHtml("<span style=\"color: red;\"><a href=\"http://www.kde.org/\">Blue</a></span>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground().color() == QApplication::palette().link().color()); + + setHtml("<span style=\"color: red;\"><a href=\"http://www.kde.org/\" style=\"color: yellow;\">Green</a></span>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().foreground().color() == Qt::yellow); +} + +void tst_QTextDocumentFragment::html_lastParagraphClosing() +{ + setHtml("<p>Foo<b>Bar</b>Baz"); + QCOMPARE(doc->blockCount(), 1); +} + +void tst_QTextDocumentFragment::html_tableHeaderBodyFootParent() +{ + // don't get confused by strange tags, keep tbody/thead/tfoot children of + // the table tag + setHtml("<table><col><col><col><tbody><tr><td>Hey</td></tr></tbody></table>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("Hey")); + + setHtml("<table><col><col><col><thead><tr><td>Hey</td></tr></thead></table>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("Hey")); + + setHtml("<table><col><col><col><tfoot><tr><td>Hey</td></tr></tfoot></table>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("Hey")); +} + +void tst_QTextDocumentFragment::html_columnWidths() +{ + setHtml("<table>" + " <tr>" + " <td colspan=\"2\">Foo</td>" + " </tr>" + " <tr>" + " <td>Bar</td>" + " <td width=\"1%\">Baz</td>" + " </tr>" + "</table>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 2); + QTextTableFormat fmt = table->format(); + + const QVector<QTextLength> columnWidths = fmt.columnWidthConstraints(); + QCOMPARE(columnWidths.count(), 2); + QVERIFY(columnWidths.at(0).type() == QTextLength::VariableLength); + QVERIFY(columnWidths.at(1).type() == QTextLength::PercentageLength); + QVERIFY(columnWidths.at(1).rawValue() == 1); +} + +void tst_QTextDocumentFragment::css_fontWeight() +{ + setHtml("<p style=\"font-weight:bold\">blah</p>"); + QVERIFY(doc->begin().charFormat().fontWeight() == QFont::Bold); + setHtml("<p style=\"font-weight:600\">blah</p>"); + QVERIFY(doc->begin().charFormat().fontWeight() == QFont::Bold); + +} + +void tst_QTextDocumentFragment::css_float() +{ + setHtml("<img src=\"foo\" style=\"float: right\">"); + QTextCharFormat fmt = doc->begin().begin().fragment().charFormat(); + QVERIFY(fmt.isImageFormat()); + QTextObject *o = doc->objectForFormat(fmt); + QVERIFY(o); + QTextFormat f = o->format(); + QVERIFY(f.isFrameFormat()); + QVERIFY(f.toFrameFormat().position() == QTextFrameFormat::FloatRight); + + setHtml("<img src=\"foo\" align=right>"); + fmt = doc->begin().begin().fragment().charFormat(); + QVERIFY(fmt.isImageFormat()); + o = doc->objectForFormat(fmt); + QVERIFY(o); + f = o->format(); + QVERIFY(f.isFrameFormat()); + QVERIFY(f.toFrameFormat().position() == QTextFrameFormat::FloatRight); + + setHtml("<img src=\"foo\" align=left>"); + fmt = doc->begin().begin().fragment().charFormat(); + QVERIFY(fmt.isImageFormat()); + o = doc->objectForFormat(fmt); + QVERIFY(o); + f = o->format(); + QVERIFY(f.isFrameFormat()); + QVERIFY(f.toFrameFormat().position() == QTextFrameFormat::FloatLeft); +} + +void tst_QTextDocumentFragment::css_textIndent() +{ + setHtml("<p style=\"text-indent: 42px\">foo</p>"); + QTextBlockFormat fmt = doc->begin().blockFormat(); + QCOMPARE(fmt.textIndent(), qreal(42)); +} + +void tst_QTextDocumentFragment::css_inline() +{ + setHtml("" + "<style>" + " p { background-color: green;}" + "</style>" + "<p>test</p>" + ); + QTextBlockFormat fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() == QColor("green")); +} + +void tst_QTextDocumentFragment::css_external() +{ + doc->addResource(QTextDocument::StyleSheetResource, QUrl("test.css"), QString("p { background-color: green; }")); + doc->setHtml("" + "<link href=\"test.css\" type=\"text/css\" />" + "<p>test</p>" + ); + QTextBlockFormat fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() == QColor("green")); +} + +void tst_QTextDocumentFragment::css_import() +{ + doc->addResource(QTextDocument::StyleSheetResource, QUrl("test.css"), QString("@import \"other.css\";")); + doc->addResource(QTextDocument::StyleSheetResource, QUrl("other.css"), QString("@import url(\"other2.css\");")); + doc->addResource(QTextDocument::StyleSheetResource, QUrl("other2.css"), QString("p { background-color: green; }")); + doc->setHtml("" + "<link href=\"test.css\" type=\"text/css\" />" + "<p>test</p>" + ); + QTextBlockFormat fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() == QColor("green")); + + doc->setHtml("" + "<style>@import \"test.css\" screen;</style>" + "<p>test</p>" + ); + fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() == QColor("green")); +} + +void tst_QTextDocumentFragment::css_selectors_data() +{ + QTest::addColumn<bool>("match"); + QTest::addColumn<QString>("selector"); + QTest::addColumn<QString>("attributes"); + + QTest::newRow("plain") << true << QString() << QString(); + + QTest::newRow("class") << true << QString(".foo") << QString("class=foo"); + QTest::newRow("notclass") << false << QString(".foo") << QString("class=bar"); + + QTest::newRow("attrset") << true << QString("[justset]") << QString("justset"); + QTest::newRow("notattrset") << false << QString("[justset]") << QString("otherattribute"); + + QTest::newRow("attrmatch") << true << QString("[foo=bar]") << QString("foo=bar"); + QTest::newRow("noattrmatch") << false << QString("[foo=bar]") << QString("foo=xyz"); + + QTest::newRow("contains") << true << QString("[foo~=bar]") << QString("foo=\"baz bleh bar\""); + QTest::newRow("notcontains") << false << QString("[foo~=bar]") << QString("foo=\"test\""); + + QTest::newRow("beingswith") << true << QString("[foo|=bar]") << QString("foo=\"bar-bleh\""); + QTest::newRow("notbeingswith") << false << QString("[foo|=bar]") << QString("foo=\"bleh-bar\""); + + QTest::newRow("attr2") << true << QString("[bar=foo]") << QString("bleh=bar bar=foo"); +} + +void tst_QTextDocumentFragment::css_selectors() +{ + QFETCH(bool, match); + QFETCH(QString, selector); + QFETCH(QString, attributes); + + QString html = QString("" + "<style>" + " p { background-color: green }" + " p%1 { background-color: red }" + "</style>" + "<p %2>test</p>" + ).arg(selector).arg(attributes); + setHtml(html); + + QTextBlockFormat fmt = doc->begin().blockFormat(); + if (match) + QVERIFY(fmt.background().color() == QColor("red")); + else + QVERIFY(fmt.background().color() == QColor("green")); +} + +void tst_QTextDocumentFragment::css_nodeNameCaseInsensitivity() +{ + setHtml("<style>" + "P { background-color: green }" + "</style>" + "<p>test</p>"); + QTextBlockFormat fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() == QColor("green")); +} + +void tst_QTextDocumentFragment::css_textUnderlineStyle_data() +{ + QTest::addColumn<QString>("styleName"); + QTest::addColumn<int>("expectedStyle"); + + QTest::newRow("none") << QString("none") << int(QTextCharFormat::NoUnderline); + QTest::newRow("solid") << QString("solid") << int(QTextCharFormat::SingleUnderline); + QTest::newRow("dash") << QString("dashed") << int(QTextCharFormat::DashUnderline); + QTest::newRow("dot") << QString("dotted") << int(QTextCharFormat::DotLine); + QTest::newRow("dashdot") << QString("dot-dash") << int(QTextCharFormat::DashDotLine); + QTest::newRow("dashdotdot") << QString("dot-dot-dash") << int(QTextCharFormat::DashDotDotLine); + QTest::newRow("wave") << QString("wave") << int(QTextCharFormat::WaveUnderline); +} + +void tst_QTextDocumentFragment::css_textUnderlineStyle() +{ + QFETCH(QString, styleName); + QFETCH(int, expectedStyle); + + QString html = QString::fromLatin1("<span style=\"text-underline-style: %1\">Blah</span>").arg(styleName); + doc->setHtml(html); + + QTextFragment fragment = doc->begin().begin().fragment(); + QVERIFY(fragment.isValid()); + QCOMPARE(int(fragment.charFormat().underlineStyle()), expectedStyle); +} + +void tst_QTextDocumentFragment::css_textUnderlineStyleAndDecoration() +{ + doc->setHtml("<span style=\"text-decoration: overline; text-underline-style: solid\">Test</span>"); + + QTextFragment fragment = doc->begin().begin().fragment(); + QVERIFY(fragment.isValid()); + QVERIFY(fragment.charFormat().underlineStyle() == QTextCharFormat::SingleUnderline); + QVERIFY(fragment.charFormat().fontOverline()); + + doc->setHtml("<span style=\"text-underline-style: solid; text-decoration: overline\">Test</span>"); + + fragment = doc->begin().begin().fragment(); + QVERIFY(fragment.isValid()); + QVERIFY(fragment.charFormat().underlineStyle() == QTextCharFormat::SingleUnderline); + QVERIFY(fragment.charFormat().fontOverline()); +} + +void tst_QTextDocumentFragment::css_listStyleType() +{ + doc->setHtml("<ol style=\"list-style-type: disc\"><li>Blah</li></ol>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListDisc); + + doc->setHtml("<ul style=\"list-style-type: square\"><li>Blah</li></ul>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListSquare); + + doc->setHtml("<ul style=\"list-style-type: circle\"><li>Blah</li></ul>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListCircle); + + doc->setHtml("<ul style=\"list-style-type: decimal\"><li>Blah</li></ul>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListDecimal); + + doc->setHtml("<ul style=\"list-style-type: lower-alpha\"><li>Blah</li></ul>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListLowerAlpha); + + doc->setHtml("<ul style=\"list-style-type: upper-alpha\"><li>Blah</li></ul>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListUpperAlpha); + + doc->setHtml("<ul style=\"list-style-type: upper-roman\"><li>Blah</li></ul>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListUpperRoman); + + doc->setHtml("<ul style=\"list-style-type: lower-roman\"><li>Blah</li></ul>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListLowerRoman); + + // ignore the unsupported list-style-position inside the list-style shorthand property + doc->setHtml("<ul style=\"list-style: outside decimal\"><li>Blah</li></ul>"); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListDecimal); +} + +void tst_QTextDocumentFragment::css_linkPseudo() +{ + doc->setHtml("<a href=\"foobar\">Blah</a>"); + QVERIFY(doc->begin().begin().fragment().charFormat().fontUnderline()); + + doc->setHtml("<style>a { text-decoration: none; }</style><a href=\"foobar\">Blah</a>"); + QVERIFY(!doc->begin().begin().fragment().charFormat().fontUnderline()); + + doc->setHtml("<style>a:link { text-decoration: none; }</style><a href=\"foobar\">Blah</a>"); + QVERIFY(!doc->begin().begin().fragment().charFormat().fontUnderline()); +} + +void tst_QTextDocumentFragment::css_pageBreaks() +{ + doc->setHtml("<p>Foo</p>"); + QVERIFY(doc->begin().blockFormat().pageBreakPolicy() == QTextFormat::PageBreak_Auto); + + doc->setHtml("<p style=\" page-break-before:always;\">Foo</p>"); + QVERIFY(doc->begin().blockFormat().pageBreakPolicy() == QTextFormat::PageBreak_AlwaysBefore); + + doc->setHtml("<p style=\" page-break-after:always;\">Foo</p>"); + QVERIFY(doc->begin().blockFormat().pageBreakPolicy() == QTextFormat::PageBreak_AlwaysAfter); + + doc->setHtml("<p style=\" page-break-before:always; page-break-after:always;\">Foo</p>"); + QVERIFY(doc->begin().blockFormat().pageBreakPolicy() == (QTextFormat::PageBreak_AlwaysAfter | QTextFormat::PageBreak_AlwaysBefore)); +} + +void tst_QTextDocumentFragment::universalSelectors_data() +{ + QTest::addColumn<bool>("match"); + QTest::addColumn<QString>("selector"); + QTest::addColumn<QString>("attributes"); + + QTest::newRow("1") << true << QString("*") << QString(); + QTest::newRow("2") << false << QString() << QString(); // invalid totally empty selector + + QTest::newRow("3") << false << QString("*[foo=bar]") << QString("foo=bleh"); + QTest::newRow("4") << true << QString("*[foo=bar]") << QString("foo=bar"); + + QTest::newRow("5") << false << QString("[foo=bar]") << QString("foo=bleh"); + QTest::newRow("6") << true << QString("[foo=bar]") << QString("foo=bar"); + + QTest::newRow("7") << true << QString(".charfmt1") << QString("class=charfmt1"); +} + +void tst_QTextDocumentFragment::universalSelectors() +{ + QFETCH(bool, match); + QFETCH(QString, selector); + QFETCH(QString, attributes); + + QString html = QString("" + "<style>" + "%1 { background-color: green }" + "</style>" + "<p %2>test</p>" + ).arg(selector).arg(attributes); + + setHtml(html); + + QTextBlockFormat fmt = doc->begin().blockFormat(); + if (match) + QVERIFY(fmt.background().color() == QColor("green")); + else + QVERIFY(!fmt.hasProperty(QTextFormat::BackgroundBrush)); +} + +void tst_QTextDocumentFragment::screenMedia() +{ + setHtml("<style>" + "@media screen {" + "p { background-color: green }" + "}" + "</style>" + "<p>test</p>" + ""); + QTextBlockFormat fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() == QColor("green")); + + setHtml("<style>" + "@media foobar {" + "p { background-color: green }" + "}" + "</style>" + "<p>test</p>" + ""); + fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() != QColor("green")); + + setHtml("<style>" + "@media sCrEeN {" + "p { background-color: green }" + "}" + "</style>" + "<p>test</p>" + ""); + fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() == QColor("green")); +} + +void tst_QTextDocumentFragment::htmlResourceLoading() +{ + const QString html("<link href=\"test.css\" type=\"text/css\" />" + "<p>test</p>"); + + QTextDocument tmp; + tmp.addResource(QTextDocument::StyleSheetResource, QUrl("test.css"), QString("p { background-color: green; }")); + QTextDocumentFragment frag = QTextDocumentFragment::fromHtml(html, &tmp); + doc->clear(); + QTextCursor(doc).insertFragment(frag); + QTextBlockFormat fmt = doc->begin().blockFormat(); + QVERIFY(fmt.background().color() == QColor("green")); +} + +void tst_QTextDocumentFragment::someCaseInsensitiveAttributeValues() +{ + const char html1[] = "<ul type=sQUarE><li>Blah</li></ul>"; + setHtml(QString::fromLatin1(html1)); + cursor.movePosition(QTextCursor::End); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->format().style() == QTextListFormat::ListSquare); + + const char html2[] = "<div align=ceNTeR><b>Hello World"; + setHtml(html2); + + QCOMPARE(doc->begin().blockFormat().alignment(), Qt::AlignHCenter); + + const char html3[] = "<p dir=rTL><b>Hello World"; + setHtml(html3); + + QCOMPARE(doc->begin().blockFormat().layoutDirection(), Qt::RightToLeft); +} + +class TestDocument : public QTextDocument +{ +public: + inline TestDocument() {} + + QPixmap testPixmap; + + virtual QVariant loadResource(int type, const QUrl &name) { + if (name.toString() == QLatin1String("testPixmap")) { + return testPixmap; + } + return QTextDocument::loadResource(type, name); + } +}; + +void tst_QTextDocumentFragment::backgroundImage() +{ + TestDocument doc; + doc.testPixmap = QPixmap(100, 100); + doc.testPixmap.fill(Qt::blue); + doc.setHtml("<p style=\"background-image: url(testPixmap)\">Hello</p>"); + QBrush bg = doc.begin().blockFormat().background(); + QVERIFY(bg.style() == Qt::TexturePattern); + QVERIFY(bg.texture().serialNumber() == doc.testPixmap.serialNumber()); +} + +void tst_QTextDocumentFragment::dontMergePreAndNonPre() +{ + doc->setHtml("<pre>Pre text</pre>Text that should be wrapped"); + QCOMPARE(doc->blockCount(), 2); + QCOMPARE(doc->begin().text(), QString("Pre text")); + QCOMPARE(doc->begin().next().text(), QString("Text that should be wrapped")); +} + +void tst_QTextDocumentFragment::leftMarginInsideHtml() +{ + doc->setHtml("<html><dl><dd>Blah"); + QCOMPARE(doc->blockCount(), 1); + QVERIFY(doc->begin().blockFormat().leftMargin() > 0); +} + +void tst_QTextDocumentFragment::html_margins() +{ + doc->setHtml("<p style=\"margin-left: 42px\">Test"); + QCOMPARE(doc->blockCount(), 1); + QCOMPARE(doc->begin().blockFormat().topMargin(), 12.); + QCOMPARE(doc->begin().blockFormat().bottomMargin(), 12.); + QCOMPARE(doc->begin().blockFormat().leftMargin(), 42.); +} + +void tst_QTextDocumentFragment::newlineInsidePreShouldBecomeNewParagraph() +{ + // rationale: we used to map newlines inside <pre> to QChar::LineSeparator, but + // if you display a lot of text inside pre it all ended up inside one single paragraph, + // which doesn't scale very well with our text engine. Paragraphs spanning thousands of + // lines are not a common use-case otherwise. + + doc->setHtml("<pre>Foo\nBar</pre>"); + QCOMPARE(doc->blockCount(), 2); + QTextBlock block = doc->begin(); + QCOMPARE(block.blockFormat().topMargin(), qreal(12)); + QVERIFY(qIsNull(block.blockFormat().bottomMargin())); + + block = block.next(); + + QVERIFY(qIsNull(block.blockFormat().topMargin())); + QCOMPARE(block.blockFormat().bottomMargin(), qreal(12)); + + doc->setHtml("<pre style=\"margin-top: 32px; margin-bottom: 45px; margin-left: 50px\">Foo\nBar</pre>"); + QCOMPARE(doc->blockCount(), 2); + block = doc->begin(); + QCOMPARE(block.blockFormat().topMargin(), qreal(32)); + QVERIFY(qIsNull(block.blockFormat().bottomMargin())); + QCOMPARE(block.blockFormat().leftMargin(), qreal(50)); + + block = block.next(); + + QVERIFY(qIsNull(block.blockFormat().topMargin())); + QCOMPARE(block.blockFormat().bottomMargin(), qreal(45)); + QCOMPARE(block.blockFormat().leftMargin(), qreal(50)); + +} + +void tst_QTextDocumentFragment::invalidColspan() +{ + doc->setHtml("<table><tr rowspan=-1><td colspan=-1>Blah</td></tr></table>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->rows(), 1); +} + +void tst_QTextDocumentFragment::html_brokenTableWithJustTr() +{ + doc->setHtml("<tr><td>First Cell</td><tr><td>Second Cell"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First Cell")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Second Cell")); + + doc->setHtml("" + "<col width=286 style='mso-width-source:userset;mso-width-alt:10459;width:215pt'>" + "<col width=64 span=3 style='width:48pt'>" + "<tr height=17 style='height:12.75pt'>" + "<td height=17 width=286 style='height:12.75pt;width:215pt'>1a</td>" + "<td width=64 style='width:48pt'>1b</td>" + "<td width=64 style='width:48pt'>1c</td>" + "<td width=64 style='width:48pt'>1d</td>" + "</tr>" + "<tr height=17 style='height:12.75pt'>" + "<td height=17 style='height:12.75pt'>|2a</td>" + "<td>2b</td>" + "<td>2c</td>" + "<td>2d</td>" + "</tr>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 4); +} + +void tst_QTextDocumentFragment::html_brokenTableWithJustTd() +{ + doc->setHtml("<td>First Cell</td><td>Second Cell"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First Cell")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Second Cell")); + + doc->setHtml("<td height=17 width=286 style='height:12.75pt;width:215pt'>1a</td>" + "<td width=64 style='width:48pt'>1b</td>" + "<td width=64 style='width:48pt'>1c</td>" + "<td width=64 style='width:48pt'>1d</td>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 4); +} + +void tst_QTextDocumentFragment::html_preNewlineHandling_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<QString>("expectedPlainText"); + + QTest::newRow("pre1") << QString("Foo<pre>Bar") + << QString("Foo\nBar"); + QTest::newRow("pre2") << QString("Foo<pre>\nBar") + << QString("Foo\nBar"); + QTest::newRow("pre2") << QString("Foo<pre>\n\nBar") + << QString("Foo\n\nBar"); + QTest::newRow("pre4") << QString("<html>Foo<pre>\nBar") + << QString("Foo\nBar"); + QTest::newRow("pre5") << QString("<pre>Foo\n</pre>\nBar") + << QString("Foo\nBar"); + QTest::newRow("pre6") << QString("<pre>Foo<b>Bar</b>Blah\n</pre>\nMooh") + << QString("FooBarBlah\nMooh"); + QTest::newRow("pre7") << QString("<pre>\nPara1\n</pre>\n<pre>\nPara2\n</pre>") + << QString("Para1\nPara2"); +} + +void tst_QTextDocumentFragment::html_preNewlineHandling() +{ + QFETCH(QString, html); + + doc->setHtml(html); + QTEST(doc->toPlainText(), "expectedPlainText"); +} + +void tst_QTextDocumentFragment::html_br() +{ + doc->setHtml("Foo<br><br><br>Blah"); + QCOMPARE(doc->toPlainText(), QString("Foo\n\n\nBlah")); +} + +void tst_QTextDocumentFragment::html_dl() +{ + doc->setHtml("<dl><dt>term<dd>data</dl>Text afterwards"); + QCOMPARE(doc->toPlainText(), QString("term\ndata\nText afterwards")); +} + +void tst_QTextDocumentFragment::html_tableStrangeNewline() +{ + doc->setHtml("<table><tr><td>Foo</td></tr>\n</table>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 1); + const QTextTableCell cell = table->cellAt(0, 0); + QCOMPARE(cell.firstCursorPosition().block().text(), QString("Foo")); + QVERIFY(cell.firstCursorPosition().block() == cell.lastCursorPosition().block()); +} + +void tst_QTextDocumentFragment::html_tableStrangeNewline2() +{ + doc->setHtml("<table><tr><td>Foo</td></tr><tr>\n<td/></tr></table>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 1); + const QTextTableCell cell = table->cellAt(0, 0); + QCOMPARE(cell.firstCursorPosition().block().text(), QString("Foo")); + QVERIFY(cell.firstCursorPosition().block() == cell.lastCursorPosition().block()); +} + +void tst_QTextDocumentFragment::html_tableStrangeNewline3() +{ + doc->setHtml("<table border>" + "<tr>" + "<td>" + "<ul>" + "<li>Meh</li>" + "</ul>" + "</td>" + "<td>\n" + "<ul>" + "<li>Foo</li>" + "</ul>" + "</td>" + "</tr>" + "</table>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 2); + + QTextTableCell cell = table->cellAt(0, 0); + QCOMPARE(cell.firstCursorPosition().block().text(), QString("Meh")); + QVERIFY(cell.firstCursorPosition().block() == cell.lastCursorPosition().block()); + + cell = table->cellAt(0, 1); + QCOMPARE(cell.firstCursorPosition().block().text(), QString("Foo")); + QVERIFY(cell.firstCursorPosition().block() == cell.lastCursorPosition().block()); +} + +void tst_QTextDocumentFragment::html_caption() +{ + doc->setHtml("<table border align=center>" + "<caption>This <b> is a</b> Caption!</caption>" + "<tr><td>Blah</td></tr>" + "</table>"); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + + QCOMPARE(cursor.block().text(), QString("This is a Caption!")); + QVERIFY(cursor.blockFormat().alignment() == Qt::AlignHCenter); + + cursor.movePosition(QTextCursor::NextBlock); + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 1); + + QTextTableCell cell = table->cellAt(0, 0); + QCOMPARE(cell.firstCursorPosition().block().text(), QString("Blah")); +} + +static const uint windowsLatin1ExtendedCharacters[0xA0 - 0x80] = { + 0x20ac, // 0x80 + 0x0081, // 0x81 direct mapping + 0x201a, // 0x82 + 0x0192, // 0x83 + 0x201e, // 0x84 + 0x2026, // 0x85 + 0x2020, // 0x86 + 0x2021, // 0x87 + 0x02C6, // 0x88 + 0x2030, // 0x89 + 0x0160, // 0x8A + 0x2039, // 0x8B + 0x0152, // 0x8C + 0x008D, // 0x8D direct mapping + 0x017D, // 0x8E + 0x008F, // 0x8F directmapping + 0x0090, // 0x90 directmapping + 0x2018, // 0x91 + 0x2019, // 0x92 + 0x201C, // 0x93 + 0X201D, // 0x94 + 0x2022, // 0x95 + 0x2013, // 0x96 + 0x2014, // 0x97 + 0x02DC, // 0x98 + 0x2122, // 0x99 + 0x0161, // 0x9A + 0x203A, // 0x9B + 0x0153, // 0x9C + 0x009D, // 0x9D direct mapping + 0x017E, // 0x9E + 0x0178 // 0x9F +}; + +void tst_QTextDocumentFragment::html_windowsEntities() +{ + for (uint i = 0; i < sizeof(windowsLatin1ExtendedCharacters)/sizeof(windowsLatin1ExtendedCharacters[0]); ++i) { + QString html = QString::number(i + 0x80); + html.prepend("<p>&#"); + html.append(";"); + doc->setHtml(html); + QCOMPARE(doc->toPlainText(), QString(QChar(windowsLatin1ExtendedCharacters[i]))); + } +} + +void tst_QTextDocumentFragment::html_eatenText() +{ + doc->setHtml("<h1>Test1</h1>\nTest2<h1>Test3</h1>"); + cursor.movePosition(QTextCursor::Start); + QCOMPARE(cursor.block().text(), QString("Test1")); + cursor.movePosition(QTextCursor::NextBlock); + QCOMPARE(cursor.block().text(), QString("Test2")); + cursor.movePosition(QTextCursor::NextBlock); + QCOMPARE(cursor.block().text(), QString("Test3")); +} + +void tst_QTextDocumentFragment::html_hr() +{ + doc->setHtml("<hr />"); + QCOMPARE(doc->blockCount(), 1); + QVERIFY(doc->begin().blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)); +} + +void tst_QTextDocumentFragment::html_hrMargins() +{ + doc->setHtml("<p>Test<hr/>Blah"); + QCOMPARE(doc->blockCount(), 3); + + cursor.movePosition(QTextCursor::Start); + QTextBlock block = cursor.block(); + QCOMPARE(block.text(), QString("Test")); + QVERIFY(block.blockFormat().bottomMargin() <= qreal(12.)); + QTextBlock first = block; + + cursor.movePosition(QTextCursor::NextBlock); + block = cursor.block(); + QTextBlock hr = block; + QVERIFY(qMax(first.blockFormat().bottomMargin(), block.blockFormat().topMargin()) > 0); + + cursor.movePosition(QTextCursor::NextBlock); + block = cursor.block(); + + QCOMPARE(block.text(), QString("Blah")); +} + +void tst_QTextDocumentFragment::html_blockQuoteMargins() +{ + doc->setHtml("<blockquote>Bar</blockquote>"); + QCOMPARE(doc->blockCount(), 1); + cursor.movePosition(QTextCursor::Start); + QTextBlock block = cursor.block(); + QCOMPARE(block.text(), QString("Bar")); + QCOMPARE(block.blockFormat().leftMargin(), qreal(40.)); + QCOMPARE(block.blockFormat().rightMargin(), qreal(40.)); + QCOMPARE(block.blockFormat().topMargin(), qreal(12.)); + QCOMPARE(block.blockFormat().bottomMargin(), qreal(12.)); +} + +void tst_QTextDocumentFragment::html_definitionListMargins() +{ + doc->setHtml("Foo<dl><dt>tag<dd>data</dl>Bar"); + QCOMPARE(doc->blockCount(), 4); + + cursor.movePosition(QTextCursor::Start); + QTextBlock block = cursor.block(); + QCOMPARE(block.text(), QString("Foo")); + + block = block.next(); + QCOMPARE(block.text(), QString("tag")); + QCOMPARE(block.blockFormat().topMargin(), qreal(8.)); + + block = block.next(); + QCOMPARE(block.text(), QString("data")); + QCOMPARE(block.blockFormat().bottomMargin(), qreal(8.)); + + block = block.next(); + QCOMPARE(block.text(), QString("Bar")); +} + +void tst_QTextDocumentFragment::html_listMargins() +{ + doc->setHtml("Foo<ol><li>First<li>Second</ol>Bar"); + QCOMPARE(doc->blockCount(), 4); + + cursor.movePosition(QTextCursor::Start); + QTextBlock block = cursor.block(); + QCOMPARE(block.text(), QString("Foo")); + + block = block.next(); + QCOMPARE(block.text(), QString("First")); + QCOMPARE(block.blockFormat().topMargin(), qreal(12.)); + + block = block.next(); + QCOMPARE(block.text(), QString("Second")); + QCOMPARE(block.blockFormat().bottomMargin(), qreal(12.)); + + block = block.next(); + QCOMPARE(block.text(), QString("Bar")); +} + +void tst_QTextDocumentFragment::html_titleAttribute() +{ + doc->setHtml("<span title=\"this is my title\">Test</span>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QCOMPARE(cursor.charFormat().toolTip(), QString("this is my title")); +} + +void tst_QTextDocumentFragment::html_compressDivs() +{ + doc->setHtml("<p/><div/><div/><div/><div/>Test"); + QCOMPARE(doc->blockCount(), 1); + QCOMPARE(doc->begin().text(), QString("Test")); +} + +void tst_QTextDocumentFragment::completeToPlainText() +{ + doc->setPlainText("Hello\nWorld"); + QCOMPARE(doc->toPlainText(), QString("Hello\nWorld")); + QTextDocumentFragment fragment(doc); + QCOMPARE(fragment.toPlainText(), QString("Hello\nWorld")); +} + +void tst_QTextDocumentFragment::copyContents() +{ + doc->setPlainText("Hello"); + QFont f; + doc->setDefaultFont(f); + QTextFragment fragment = doc->begin().begin().fragment(); + QCOMPARE(fragment.text(), QString("Hello")); + QCOMPARE(fragment.charFormat().font().pointSize(), f.pointSize()); + + QTextDocumentFragment frag(doc); + doc->clear(); + f.setPointSize(48); + doc->setDefaultFont(f); + QTextCursor(doc).insertFragment(QTextDocumentFragment::fromHtml(frag.toHtml())); + fragment = doc->begin().begin().fragment(); + QCOMPARE(fragment.text(), QString("Hello")); + QCOMPARE(fragment.charFormat().font().pointSize(), f.pointSize()); +} + +void tst_QTextDocumentFragment::html_textAfterHr() +{ + doc->setHtml("<hr><nobr><b>After the centered text</b></nobr>"); + QCOMPARE(doc->blockCount(), 2); + QTextBlock block = doc->begin(); + QVERIFY(block.text().isEmpty()); + QVERIFY(block.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)); + block = block.next(); + + QString txt("After the centered text"); + txt.replace(QLatin1Char(' '), QChar::Nbsp); + QCOMPARE(block.text(), txt); + QVERIFY(!block.blockFormat().hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)); +} + +void tst_QTextDocumentFragment::blockTagClosing() +{ + doc->setHtml("<p>foo<p>bar<span>baz</span>"); + QCOMPARE(doc->blockCount(), 2); + QTextBlock block = doc->begin(); + QCOMPARE(block.text(), QString("foo")); + block = block.next(); + QCOMPARE(block.text(), QString("barbaz")); +} + +void tst_QTextDocumentFragment::isEmpty() +{ + QTextDocumentFragment frag; + QVERIFY(frag.isEmpty()); + frag = QTextDocumentFragment::fromHtml("test"); + QVERIFY(!frag.isEmpty()); + frag = QTextDocumentFragment::fromHtml("<hr />"); + QVERIFY(!frag.isEmpty()); +} + +void tst_QTextDocumentFragment::html_alignmentInheritance() +{ + doc->setHtml("<center>Centered text<hr></center><b>After the centered text</b>"); + QCOMPARE(doc->blockCount(), 3); + QTextBlock block = doc->begin(); + QVERIFY(block.blockFormat().alignment() & Qt::AlignHCenter); + block = block.next(); + QVERIFY(block.blockFormat().alignment() & Qt::AlignHCenter); + block = block.next(); + QVERIFY(!(block.blockFormat().alignment() & Qt::AlignHCenter)); +} + +void tst_QTextDocumentFragment::html_ignoreEmptyDivs() +{ + doc->setHtml("<p><div/><b>Foo</b>"); + QCOMPARE(doc->blockCount(), 1); + QCOMPARE(doc->begin().text(), QString("Foo")); +} + +void tst_QTextDocumentFragment::html_dontInheritAlignmentForFloatingImages() +{ + doc->setHtml("<p align=right><img align=unknownignored src=\"foo\" /></p>"); + QTextCharFormat fmt = doc->begin().begin().fragment().charFormat(); + QVERIFY(fmt.isImageFormat()); + QTextObject *o = doc->objectForFormat(fmt); + QVERIFY(o); + QTextFormat f = o->format(); + QVERIFY(f.isFrameFormat()); + QVERIFY(f.toFrameFormat().position() == QTextFrameFormat::InFlow); +} + +void tst_QTextDocumentFragment::html_verticalImageAlignment() +{ + doc->setHtml("<img src=\"foo\"/>"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().isImageFormat()); + QTextImageFormat fmt = cursor.charFormat().toImageFormat(); + QVERIFY(fmt.verticalAlignment() == QTextCharFormat::AlignNormal); + + doc->setHtml("<img src=\"foo\" align=middle />"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().isImageFormat()); + fmt = cursor.charFormat().toImageFormat(); + QVERIFY(fmt.verticalAlignment() == QTextCharFormat::AlignMiddle); + + doc->setHtml("<img src=\"foo\" style=\"vertical-align: middle\" />"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().isImageFormat()); + fmt = cursor.charFormat().toImageFormat(); + QVERIFY(fmt.verticalAlignment() == QTextCharFormat::AlignMiddle); + + doc->setHtml("<img src=\"foo\" align=top />"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().isImageFormat()); + fmt = cursor.charFormat().toImageFormat(); + QVERIFY(fmt.verticalAlignment() == QTextCharFormat::AlignTop); + + doc->setHtml("<img src=\"foo\" style=\"vertical-align: top\" />"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().isImageFormat()); + fmt = cursor.charFormat().toImageFormat(); + QVERIFY(fmt.verticalAlignment() == QTextCharFormat::AlignTop); +} + +void tst_QTextDocumentFragment::html_verticalCellAlignment() +{ + const char *alt[] = + { + // vertical-align property + "<table>" + "<tr>" + "<td style=\"vertical-align: middle\"></td>" + "<td style=\"vertical-align: top\"></td>" + "<td style=\"vertical-align: bottom\"></td>" + "</tr>" + "</table>", + // valign property + "<table>" + "<tr>" + "<td valign=\"middle\"></td>" + "<td valign=\"top\"></td>" + "<td valign=\"bottom\"></td>" + "</tr>" + "</table>", + // test td override of tr property + "<table>" + "<tr valign=\"bottom\">" + "<td valign=\"middle\"></td>" + "<td valign=\"top\"></td>" + "<td></td>" + "</tr>" + "</table>" + }; + + const int numTestCases = sizeof(alt) / sizeof(*alt); + for (int i = 0; i < numTestCases; ++i) { + doc->setHtml(alt[i]); + + QTextTable *table = qobject_cast<QTextTable *>(doc->rootFrame()->childFrames().at(0)); + QVERIFY(table); + + QCOMPARE(table->cellAt(0, 0).format().verticalAlignment(), QTextCharFormat::AlignMiddle); + QCOMPARE(table->cellAt(0, 1).format().verticalAlignment(), QTextCharFormat::AlignTop); + QCOMPARE(table->cellAt(0, 2).format().verticalAlignment(), QTextCharFormat::AlignBottom); + } +} + +void tst_QTextDocumentFragment::html_borderColor() +{ + const char html[] = "<table border=1 style=\"border-color:#0000ff;\"><tr><td>Foo</td></tr></table>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + QCOMPARE(cursor.currentTable()->format().borderStyle(), QTextFrameFormat::BorderStyle_Outset); + QCOMPARE(cursor.currentTable()->format().borderBrush(), QBrush(QColor("#0000ff"))); +} + +void tst_QTextDocumentFragment::html_borderStyle() +{ + const char html[] = "<table border=1 style=\"border-style:solid;\"><tr><td>Foo</td></tr></table>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + QCOMPARE(cursor.currentTable()->format().borderStyle(), QTextFrameFormat::BorderStyle_Solid); + QCOMPARE(cursor.currentTable()->format().borderBrush(), QBrush(Qt::darkGray)); +} + +void tst_QTextDocumentFragment::html_borderWidth() +{ + const char *html[2] = { "<table style=\"border-width:2;\"><tr><td>Foo</td></tr></table>", + "<table style=\"border-width:2px;\"><tr><td>Foo</td></tr></table>" }; + + for (int i = 0; i < 2; ++i) { + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html[i]))); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentTable()); + QCOMPARE(cursor.currentTable()->format().border(), qreal(2)); + } +} + +void tst_QTextDocumentFragment::html_userState() +{ + const char html[] = "<p style=\"-qt-user-state:42;\">A</p><p style=\"-qt-user-state:0;\">B</p><p>C</p>"; + cursor.insertFragment(QTextDocumentFragment::fromHtml(QString::fromLatin1(html))); + QTextBlock block = doc->begin(); + QCOMPARE(block.userState(), 42); + QCOMPARE(block.next().userState(), 0); + QCOMPARE(block.next().next().userState(), -1); +} + +void tst_QTextDocumentFragment::html_rootFrameProperties() +{ + const char html[] = "<table border=1 style=\"-qt-table-type:root; margin-top:10px;\"><tr><td>Foo</tr></td>"; + doc->setHtml(html); + + QCOMPARE(doc->rootFrame()->childFrames().size(), 0); + + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + QCOMPARE(fmt.topMargin(), qreal(10)); + QCOMPARE(fmt.bottomMargin(), qreal(0)); + QCOMPARE(fmt.leftMargin(), qreal(0)); + QCOMPARE(fmt.rightMargin(), qreal(0)); + QCOMPARE(fmt.border(), qreal(1)); + + QString normalFrameHtml = QLatin1String(html); + normalFrameHtml.replace(QLatin1String("root"), QLatin1String("frame")); + + doc->setHtml(normalFrameHtml); + QCOMPARE(doc->rootFrame()->childFrames().size(), 1); +} + +void tst_QTextDocumentFragment::html_appendList() +{ + appendHtml("<p>foo</p>"); + appendHtml("<ul><li>Line 1</li><li>Line 2</li></ul>"); + + QCOMPARE(doc->blockCount(), 3); + QVERIFY(doc->begin().next().textList() != 0); +} + +void tst_QTextDocumentFragment::html_appendList2() +{ + appendHtml("1"); + appendHtml("<ul><li><img src=\"/foo/bar\" /></li></ul>"); + + QCOMPARE(doc->blockCount(), 2); + QVERIFY(doc->begin().next().textList() != 0); +} + +void tst_QTextDocumentFragment::html_alignmentPropertySet() +{ + const char html[] = "<p>Test</p>"; + setHtml(QString::fromLatin1(html)); + QVERIFY(!doc->begin().blockFormat().hasProperty(QTextFormat::BlockAlignment)); +} + +void tst_QTextDocumentFragment::html_qt3RichtextWhitespaceMode() +{ + setHtml(QString::fromLatin1("<html><head><meta name=\"qrichtext\" content=\"1\" /></head><p> line with whitespace</p><p> another line with whitespace</p></body></html>")); + QCOMPARE(doc->blockCount(), 2); + + QTextBlock block = doc->begin(); + QVERIFY(block.text().startsWith(" ")); + + block = block.next(); + QVERIFY(block.text().startsWith(" ")); +} + +void tst_QTextDocumentFragment::html_brAfterHr() +{ + setHtml(QString::fromLatin1("Text A<br><hr><br>Text B<hr>")); + + QCOMPARE(doc->blockCount(), 4); + + QTextBlock block = doc->begin(); + QCOMPARE(block.text(), QString("Text A") + QChar(QChar::LineSeparator)); + + block = block.next(); + QVERIFY(block.text().isEmpty()); + + block = block.next(); + QCOMPARE(block.text(), QChar(QChar::LineSeparator) + QString("Text B")); + + block = block.next(); + QVERIFY(block.text().isEmpty()); +} + +void tst_QTextDocumentFragment::html_unclosedHead() +{ + doc->setHtml(QString::fromLatin1("<html><head><title>Test</title><body>Blah</body></html>")); + QCOMPARE(doc->metaInformation(QTextDocument::DocumentTitle), QString::fromLatin1("Test")); + QCOMPARE(doc->toPlainText(), QString::fromLatin1("Blah")); +} + +// duplicated from qtexthtmlparser.cpp +#define MAX_ENTITY 258 +static const struct { const char *name; quint16 code; } entities[MAX_ENTITY]= { + { "AElig", 0x00c6 }, + { "Aacute", 0x00c1 }, + { "Acirc", 0x00c2 }, + { "Agrave", 0x00c0 }, + { "Alpha", 0x0391 }, + { "AMP", 38 }, + { "Aring", 0x00c5 }, + { "Atilde", 0x00c3 }, + { "Auml", 0x00c4 }, + { "Beta", 0x0392 }, + { "Ccedil", 0x00c7 }, + { "Chi", 0x03a7 }, + { "Dagger", 0x2021 }, + { "Delta", 0x0394 }, + { "ETH", 0x00d0 }, + { "Eacute", 0x00c9 }, + { "Ecirc", 0x00ca }, + { "Egrave", 0x00c8 }, + { "Epsilon", 0x0395 }, + { "Eta", 0x0397 }, + { "Euml", 0x00cb }, + { "Gamma", 0x0393 }, + { "GT", 62 }, + { "Iacute", 0x00cd }, + { "Icirc", 0x00ce }, + { "Igrave", 0x00cc }, + { "Iota", 0x0399 }, + { "Iuml", 0x00cf }, + { "Kappa", 0x039a }, + { "Lambda", 0x039b }, + { "LT", 60 }, + { "Mu", 0x039c }, + { "Ntilde", 0x00d1 }, + { "Nu", 0x039d }, + { "OElig", 0x0152 }, + { "Oacute", 0x00d3 }, + { "Ocirc", 0x00d4 }, + { "Ograve", 0x00d2 }, + { "Omega", 0x03a9 }, + { "Omicron", 0x039f }, + { "Oslash", 0x00d8 }, + { "Otilde", 0x00d5 }, + { "Ouml", 0x00d6 }, + { "Phi", 0x03a6 }, + { "Pi", 0x03a0 }, + { "Prime", 0x2033 }, + { "Psi", 0x03a8 }, + { "QUOT", 34 }, + { "Rho", 0x03a1 }, + { "Scaron", 0x0160 }, + { "Sigma", 0x03a3 }, + { "THORN", 0x00de }, + { "Tau", 0x03a4 }, + { "Theta", 0x0398 }, + { "Uacute", 0x00da }, + { "Ucirc", 0x00db }, + { "Ugrave", 0x00d9 }, + { "Upsilon", 0x03a5 }, + { "Uuml", 0x00dc }, + { "Xi", 0x039e }, + { "Yacute", 0x00dd }, + { "Yuml", 0x0178 }, + { "Zeta", 0x0396 }, + { "aacute", 0x00e1 }, + { "acirc", 0x00e2 }, + { "acute", 0x00b4 }, + { "aelig", 0x00e6 }, + { "agrave", 0x00e0 }, + { "alefsym", 0x2135 }, + { "alpha", 0x03b1 }, + { "amp", 38 }, + { "and", 0x22a5 }, + { "ang", 0x2220 }, + { "apos", 0x0027 }, + { "aring", 0x00e5 }, + { "asymp", 0x2248 }, + { "atilde", 0x00e3 }, + { "auml", 0x00e4 }, + { "bdquo", 0x201e }, + { "beta", 0x03b2 }, + { "brvbar", 0x00a6 }, + { "bull", 0x2022 }, + { "cap", 0x2229 }, + { "ccedil", 0x00e7 }, + { "cedil", 0x00b8 }, + { "cent", 0x00a2 }, + { "chi", 0x03c7 }, + { "circ", 0x02c6 }, + { "clubs", 0x2663 }, + { "cong", 0x2245 }, + { "copy", 0x00a9 }, + { "crarr", 0x21b5 }, + { "cup", 0x222a }, + { "curren", 0x00a4 }, + { "dArr", 0x21d3 }, + { "dagger", 0x2020 }, + { "darr", 0x2193 }, + { "deg", 0x00b0 }, + { "delta", 0x03b4 }, + { "diams", 0x2666 }, + { "divide", 0x00f7 }, + { "eacute", 0x00e9 }, + { "ecirc", 0x00ea }, + { "egrave", 0x00e8 }, + { "empty", 0x2205 }, + { "emsp", 0x2003 }, + { "ensp", 0x2002 }, + { "epsilon", 0x03b5 }, + { "equiv", 0x2261 }, + { "eta", 0x03b7 }, + { "eth", 0x00f0 }, + { "euml", 0x00eb }, + { "euro", 0x20ac }, + { "exist", 0x2203 }, + { "fnof", 0x0192 }, + { "forall", 0x2200 }, + { "frac12", 0x00bd }, + { "frac14", 0x00bc }, + { "frac34", 0x00be }, + { "frasl", 0x2044 }, + { "gamma", 0x03b3 }, + { "ge", 0x2265 }, + { "gt", 62 }, + { "hArr", 0x21d4 }, + { "harr", 0x2194 }, + { "hearts", 0x2665 }, + { "hellip", 0x2026 }, + { "iacute", 0x00ed }, + { "icirc", 0x00ee }, + { "iexcl", 0x00a1 }, + { "igrave", 0x00ec }, + { "image", 0x2111 }, + { "infin", 0x221e }, + { "int", 0x222b }, + { "iota", 0x03b9 }, + { "iquest", 0x00bf }, + { "isin", 0x2208 }, + { "iuml", 0x00ef }, + { "kappa", 0x03ba }, + { "lArr", 0x21d0 }, + { "lambda", 0x03bb }, + { "lang", 0x2329 }, + { "laquo", 0x00ab }, + { "larr", 0x2190 }, + { "lceil", 0x2308 }, + { "ldquo", 0x201c }, + { "le", 0x2264 }, + { "lfloor", 0x230a }, + { "lowast", 0x2217 }, + { "loz", 0x25ca }, + { "lrm", 0x200e }, + { "lsaquo", 0x2039 }, + { "lsquo", 0x2018 }, + { "lt", 60 }, + { "macr", 0x00af }, + { "mdash", 0x2014 }, + { "micro", 0x00b5 }, + { "middot", 0x00b7 }, + { "minus", 0x2212 }, + { "mu", 0x03bc }, + { "nabla", 0x2207 }, + { "nbsp", 0x00a0 }, + { "ndash", 0x2013 }, + { "ne", 0x2260 }, + { "ni", 0x220b }, + { "not", 0x00ac }, + { "notin", 0x2209 }, + { "nsub", 0x2284 }, + { "ntilde", 0x00f1 }, + { "nu", 0x03bd }, + { "oacute", 0x00f3 }, + { "ocirc", 0x00f4 }, + { "oelig", 0x0153 }, + { "ograve", 0x00f2 }, + { "oline", 0x203e }, + { "omega", 0x03c9 }, + { "omicron", 0x03bf }, + { "oplus", 0x2295 }, + { "or", 0x22a6 }, + { "ordf", 0x00aa }, + { "ordm", 0x00ba }, + { "oslash", 0x00f8 }, + { "otilde", 0x00f5 }, + { "otimes", 0x2297 }, + { "ouml", 0x00f6 }, + { "para", 0x00b6 }, + { "part", 0x2202 }, + { "percnt", 0x0025 }, + { "permil", 0x2030 }, + { "perp", 0x22a5 }, + { "phi", 0x03c6 }, + { "pi", 0x03c0 }, + { "piv", 0x03d6 }, + { "plusmn", 0x00b1 }, + { "pound", 0x00a3 }, + { "prime", 0x2032 }, + { "prod", 0x220f }, + { "prop", 0x221d }, + { "psi", 0x03c8 }, + { "quot", 34 }, + { "rArr", 0x21d2 }, + { "radic", 0x221a }, + { "rang", 0x232a }, + { "raquo", 0x00bb }, + { "rarr", 0x2192 }, + { "rceil", 0x2309 }, + { "rdquo", 0x201d }, + { "real", 0x211c }, + { "reg", 0x00ae }, + { "rfloor", 0x230b }, + { "rho", 0x03c1 }, + { "rlm", 0x200f }, + { "rsaquo", 0x203a }, + { "rsquo", 0x2019 }, + { "sbquo", 0x201a }, + { "scaron", 0x0161 }, + { "sdot", 0x22c5 }, + { "sect", 0x00a7 }, + { "shy", 0x00ad }, + { "sigma", 0x03c3 }, + { "sigmaf", 0x03c2 }, + { "sim", 0x223c }, + { "spades", 0x2660 }, + { "sub", 0x2282 }, + { "sube", 0x2286 }, + { "sum", 0x2211 }, + { "sup1", 0x00b9 }, + { "sup2", 0x00b2 }, + { "sup3", 0x00b3 }, + { "sup", 0x2283 }, + { "supe", 0x2287 }, + { "szlig", 0x00df }, + { "tau", 0x03c4 }, + { "there4", 0x2234 }, + { "theta", 0x03b8 }, + { "thetasym", 0x03d1 }, + { "thinsp", 0x2009 }, + { "thorn", 0x00fe }, + { "tilde", 0x02dc }, + { "times", 0x00d7 }, + { "trade", 0x2122 }, + { "uArr", 0x21d1 }, + { "uacute", 0x00fa }, + { "uarr", 0x2191 }, + { "ucirc", 0x00fb }, + { "ugrave", 0x00f9 }, + { "uml", 0x00a8 }, + { "upsih", 0x03d2 }, + { "upsilon", 0x03c5 }, + { "uuml", 0x00fc }, + { "weierp", 0x2118 }, + { "xi", 0x03be }, + { "yacute", 0x00fd }, + { "yen", 0x00a5 }, + { "yuml", 0x00ff }, + { "zeta", 0x03b6 }, + { "zwj", 0x200d }, + { "zwnj", 0x200c } +}; + +void tst_QTextDocumentFragment::html_entities_data() +{ + QTest::addColumn<QString>("html"); + QTest::addColumn<quint16>("code"); + + for (int i = 0; i < MAX_ENTITY; ++i) { + QTest::newRow(entities[i].name) << QString("<pre>&") + QString::fromLatin1(entities[i].name) + QString(";</pre>") + << entities[i].code; + } +} + +void tst_QTextDocumentFragment::html_entities() +{ + QFETCH(QString, html); + QFETCH(quint16, code); + + setHtml(html); + QCOMPARE(doc->blockCount(), 1); + QString txt = doc->begin().text(); + QCOMPARE(txt.length(), 1); + QCOMPARE(txt.at(0).unicode(), code); +} + +void tst_QTextDocumentFragment::html_ignore_script() +{ + doc->setHtml(QString::fromLatin1("<html><script>Test</script><body>Blah</body></html>")); + QCOMPARE(doc->toPlainText(), QString("Blah")); +} + +void tst_QTextDocumentFragment::html_directionWithHtml() +{ + doc->setHtml(QString::fromLatin1("<html><body><p>Test<p dir=rtl>RTL<p dir=ltr>LTR")); + QCOMPARE(doc->blockCount(), 3); + + QTextBlock block = doc->firstBlock(); + QVERIFY(block.blockFormat().hasProperty(QTextFormat::LayoutDirection)); + QVERIFY(block.blockFormat().layoutDirection() == Qt::LeftToRight); // HTML default + + block = block.next(); + QVERIFY(block.blockFormat().hasProperty(QTextFormat::LayoutDirection)); + QVERIFY(block.blockFormat().layoutDirection() == Qt::RightToLeft); + + block = block.next(); + QVERIFY(block.blockFormat().hasProperty(QTextFormat::LayoutDirection)); + QVERIFY(block.blockFormat().layoutDirection() == Qt::LeftToRight); +} + +void tst_QTextDocumentFragment::html_directionWithRichText() +{ + doc->setHtml(QString::fromLatin1("<p>Test<p dir=rtl>RTL<p dir=ltr>LTR")); + QCOMPARE(doc->blockCount(), 3); + + QTextBlock block = doc->firstBlock(); + QVERIFY(!block.blockFormat().hasProperty(QTextFormat::LayoutDirection)); + + block = block.next(); + QVERIFY(block.blockFormat().hasProperty(QTextFormat::LayoutDirection)); + QVERIFY(block.blockFormat().layoutDirection() == Qt::RightToLeft); + + block = block.next(); + QVERIFY(block.blockFormat().hasProperty(QTextFormat::LayoutDirection)); + QVERIFY(block.blockFormat().layoutDirection() == Qt::LeftToRight); +} + +void tst_QTextDocumentFragment::html_metaInBody() +{ + setHtml("<body>Hello<meta>World</body>"); + QCOMPARE(doc->toPlainText(), QString("HelloWorld")); +} + +void tst_QTextDocumentFragment::html_importImageWithoutAspectRatio() +{ + doc->setHtml("<img src=\"foo\" width=\"100%\" height=\"43\">"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().isImageFormat()); + QTextImageFormat fmt = cursor.charFormat().toImageFormat(); + // qDebug() << fmt.width() << fmt.height(); + QVERIFY (fmt.hasProperty(QTextFormat::ImageWidth)); + QCOMPARE (fmt.height(), 43.); + + doc->setHtml("<img src=\"foo\" height=\"43\">"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().isImageFormat()); + fmt = cursor.charFormat().toImageFormat(); + QVERIFY (! fmt.hasProperty(QTextFormat::ImageWidth)); + QCOMPARE (fmt.height(), 43.); + + doc->setHtml("<img src=\"foo\" width=\"200\">"); + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextCharacter); + QVERIFY(cursor.charFormat().isImageFormat()); + fmt = cursor.charFormat().toImageFormat(); + QVERIFY (! fmt.hasProperty(QTextFormat::ImageHeight)); + QCOMPARE (fmt.width(), 200.); +} + +void tst_QTextDocumentFragment::html_fromFirefox() +{ + // if you have a html loaded in firefox like <html>Test\nText</html> then selecting all and copying will + // result in the following text on the clipboard (for text/html) + doc->setHtml(QString::fromLatin1("<!--StartFragment-->Test\nText\n\n<!--EndFragment-->")); + QCOMPARE(doc->toPlainText(), QString::fromLatin1("Test Text ")); +} + +QTEST_MAIN(tst_QTextDocumentFragment) +#include "tst_qtextdocumentfragment.moc" diff --git a/tests/auto/gui/text/qtextdocumentlayout/.gitignore b/tests/auto/gui/text/qtextdocumentlayout/.gitignore new file mode 100644 index 0000000000..80671c70d0 --- /dev/null +++ b/tests/auto/gui/text/qtextdocumentlayout/.gitignore @@ -0,0 +1 @@ +tst_qtextdocumentlayout diff --git a/tests/auto/gui/text/qtextdocumentlayout/qtextdocumentlayout.pro b/tests/auto/gui/text/qtextdocumentlayout/qtextdocumentlayout.pro new file mode 100644 index 0000000000..32b05ce061 --- /dev/null +++ b/tests/auto/gui/text/qtextdocumentlayout/qtextdocumentlayout.pro @@ -0,0 +1,5 @@ +load(qttest_p4) +QT += widgets +SOURCES += tst_qtextdocumentlayout.cpp + + diff --git a/tests/auto/gui/text/qtextdocumentlayout/tst_qtextdocumentlayout.cpp b/tests/auto/gui/text/qtextdocumentlayout/tst_qtextdocumentlayout.cpp new file mode 100644 index 0000000000..f5c72cd4d0 --- /dev/null +++ b/tests/auto/gui/text/qtextdocumentlayout/tst_qtextdocumentlayout.cpp @@ -0,0 +1,276 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <qtextdocument.h> +#include <qabstracttextdocumentlayout.h> +#include <qdebug.h> +#include <qpainter.h> +#include <qtexttable.h> +#include <qtextedit.h> +#include <qscrollbar.h> + +//TESTED_CLASS= +//TESTED_FILES=gui/text/qtextdocumentlayout_p.h gui/text/qtextdocumentlayout.cpp + +class tst_QTextDocumentLayout : public QObject +{ + Q_OBJECT +public: + inline tst_QTextDocumentLayout() {} + virtual ~tst_QTextDocumentLayout() {} + +public slots: + void init(); + void cleanup(); + +private slots: + void defaultPageSizeHandling(); + void idealWidth(); + void lineSeparatorFollowingTable(); + void wrapAtWordBoundaryOrAnywhere(); + void inlineImage(); + void clippedTableCell(); + void floatingTablePageBreak(); + +private: + QTextDocument *doc; +}; + +void tst_QTextDocumentLayout::init() +{ + doc = new QTextDocument; +} + +void tst_QTextDocumentLayout::cleanup() +{ + delete doc; + doc = 0; +} + +void tst_QTextDocumentLayout::defaultPageSizeHandling() +{ + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QVERIFY(layout); + + QVERIFY(!doc->pageSize().isValid()); + QSizeF docSize = layout->documentSize(); + QVERIFY(docSize.width() > 0 && docSize.width() < 1000); + QVERIFY(docSize.height() > 0 && docSize.height() < 1000); + + doc->setPlainText("Some text\nwith a few lines\nand not real information\nor anything otherwise useful"); + + docSize = layout->documentSize(); + QVERIFY(docSize.isValid()); + QVERIFY(docSize.width() != INT_MAX); + QVERIFY(docSize.height() != INT_MAX); +} + +void tst_QTextDocumentLayout::idealWidth() +{ + doc->setPlainText("Some text\nwith a few lines\nand not real information\nor anything otherwise useful"); + doc->setTextWidth(1000); + QCOMPARE(doc->textWidth(), qreal(1000)); + QCOMPARE(doc->size().width(), doc->textWidth()); + QVERIFY(doc->idealWidth() < doc->textWidth()); + QVERIFY(doc->idealWidth() > 0); + + QTextBlockFormat fmt; + fmt.setAlignment(Qt::AlignRight | Qt::AlignAbsolute); + QTextCursor cursor(doc); + cursor.select(QTextCursor::Document); + cursor.mergeBlockFormat(fmt); + + QCOMPARE(doc->textWidth(), qreal(1000)); + QCOMPARE(doc->size().width(), doc->textWidth()); + QVERIFY(doc->idealWidth() < doc->textWidth()); + QVERIFY(doc->idealWidth() > 0); +} + +// none of the QTextLine items in the document should intersect with the margin rect +void tst_QTextDocumentLayout::lineSeparatorFollowingTable() +{ + QString html_begin("<html><table border=1><tr><th>Column 1</th></tr><tr><td>Data</td></tr></table><br>"); + QString html_text("bla bla bla bla bla bla bla bla<br>"); + QString html_end("<table border=1><tr><th>Column 1</th></tr><tr><td>Data</td></tr></table></html>"); + + QString html = html_begin; + + for (int i = 0; i < 80; ++i) + html += html_text; + + html += html_end; + + doc->setHtml(html); + + QTextCursor cursor(doc); + cursor.movePosition(QTextCursor::Start); + + const int margin = 87; + const int pageWidth = 873; + const int pageHeight = 1358; + + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setMargin(margin); + doc->rootFrame()->setFrameFormat(fmt); + + QFont font(doc->defaultFont()); + font.setPointSize(10); + doc->setDefaultFont(font); + doc->setPageSize(QSizeF(pageWidth, pageHeight)); + + QRectF marginRect(QPointF(0, pageHeight - margin), QSizeF(pageWidth, 2 * margin)); + + // force layouting + doc->pageCount(); + + for (QTextBlock block = doc->begin(); block != doc->end(); block = block.next()) { + QTextLayout *layout = block.layout(); + for (int i = 0; i < layout->lineCount(); ++i) { + QTextLine line = layout->lineAt(i); + QRectF rect = line.rect().translated(layout->position()); + QVERIFY(!rect.intersects(marginRect)); + } + } +} + +void tst_QTextDocumentLayout::wrapAtWordBoundaryOrAnywhere() +{ + //task 150562 + QTextEdit edit; + edit.setText("<table><tr><td>hello hello hello" + "thisisabigwordthisisabigwordthisisabigwordthisisabigwordthisisabigword" + "hello hello hello</td></tr></table>"); + edit.setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + edit.resize(100, 100); + edit.show(); + QVERIFY(!edit.horizontalScrollBar()->isVisible()); +} + +void tst_QTextDocumentLayout::inlineImage() +{ + doc->setPageSize(QSizeF(800, 500)); + + QImage img(400, 400, QImage::Format_RGB32); + QLatin1String name("bigImage"); + + doc->addResource(QTextDocument::ImageResource, QUrl(name), img); + + QTextImageFormat imgFormat; + imgFormat.setName(name); + imgFormat.setWidth(img.width()); + + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + qreal height = doc->pageSize().height() - fmt.topMargin() - fmt.bottomMargin(); + imgFormat.setHeight(height); + + QTextCursor cursor(doc); + cursor.insertImage(imgFormat); + + QCOMPARE(doc->pageCount(), 1); +} + +void tst_QTextDocumentLayout::clippedTableCell() +{ + const char *html = + "<table style=\" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;\"" + "border=\"0\" margin=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tr><td></td></tr></table>"; + + doc->setHtml(html); + doc->pageSize(); + + QTextCursor cursor(doc); + cursor.movePosition(QTextCursor::Right); + + QTextTable *table = cursor.currentTable(); + QVERIFY(table); + + QTextCursor cellCursor = table->cellAt(0, 0).firstCursorPosition(); + QImage src(16, 16, QImage::Format_ARGB32_Premultiplied); + src.fill(0xffff0000); + cellCursor.insertImage(src); + + QTextBlock block = cellCursor.block(); + QRectF r = doc->documentLayout()->blockBoundingRect(block); + + QRectF rect(0, 0, r.left() + 1, 64); + + QImage img(64, 64, QImage::Format_ARGB32_Premultiplied); + img.fill(0x0); + QImage expected = img; + QPainter p(&img); + doc->drawContents(&p, rect); + p.end(); + p.begin(&expected); + r.setWidth(1); + p.fillRect(r, Qt::red); + p.end(); + + img.save("img.png"); + expected.save("expected.png"); + QCOMPARE(img, expected); +} + +void tst_QTextDocumentLayout::floatingTablePageBreak() +{ + doc->clear(); + + QTextCursor cursor(doc); + + QTextTableFormat tableFormat; + tableFormat.setPosition(QTextFrameFormat::FloatLeft); + QTextTable *table = cursor.insertTable(50, 1, tableFormat); + Q_UNUSED(table); + + // Make height of document 2/3 of the table, fitting the table into two pages + QSizeF documentSize = doc->size(); + documentSize.rheight() *= 2.0 / 3.0; + + doc->setPageSize(documentSize); + + QCOMPARE(doc->pageCount(), 2); +} + + +QTEST_MAIN(tst_QTextDocumentLayout) +#include "tst_qtextdocumentlayout.moc" diff --git a/tests/auto/gui/text/qtextformat/.gitignore b/tests/auto/gui/text/qtextformat/.gitignore new file mode 100644 index 0000000000..3e3be422fe --- /dev/null +++ b/tests/auto/gui/text/qtextformat/.gitignore @@ -0,0 +1 @@ +tst_qtextformat diff --git a/tests/auto/gui/text/qtextformat/qtextformat.pro b/tests/auto/gui/text/qtextformat/qtextformat.pro new file mode 100644 index 0000000000..30f6e50c61 --- /dev/null +++ b/tests/auto/gui/text/qtextformat/qtextformat.pro @@ -0,0 +1,9 @@ +############################################################ +# Project file for autotest for file qtextformat.h +############################################################ + +load(qttest_p4) + +SOURCES += tst_qtextformat.cpp + + diff --git a/tests/auto/gui/text/qtextformat/tst_qtextformat.cpp b/tests/auto/gui/text/qtextformat/tst_qtextformat.cpp new file mode 100644 index 0000000000..b235b112b6 --- /dev/null +++ b/tests/auto/gui/text/qtextformat/tst_qtextformat.cpp @@ -0,0 +1,374 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <qcoreapplication.h> +#include <qdebug.h> +#include <qsettings.h> +#include <qtextformat.h> +#include <qtextdocument.h> +#include <qtextcursor.h> +#include <qtextobject.h> +#include <qtextlayout.h> +#include <qabstracttextdocumentlayout.h> + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QTextFormat : public QObject +{ +Q_OBJECT + +private slots: + void getSetCheck(); + void defaultAlignment(); + void testQTextCharFormat() const; + void testUnderlinePropertyPrecedence(); + void toFormat(); + void resolveFont(); + void getSetTabs(); + void testTabsUsed(); + void testFontStyleSetters(); +}; + +/*! \internal + This (used to) trigger a crash in: + + QDataStream &operator>>(QDataStream &stream, QTextFormat &fmt) + + which is most easily produced through QSettings. + */ +void tst_QTextFormat::testQTextCharFormat() const +{ + QSettings settings("test", "test"); + QTextCharFormat test; + + settings.value("test", test); +} + +// Testing get/set functions +void tst_QTextFormat::getSetCheck() +{ + QTextFormat obj1; + // int QTextFormat::objectIndex() + // void QTextFormat::setObjectIndex(int) + obj1.setObjectIndex(0); + QCOMPARE(0, obj1.objectIndex()); + obj1.setObjectIndex(INT_MIN); + QCOMPARE(INT_MIN, obj1.objectIndex()); + obj1.setObjectIndex(INT_MAX); + QCOMPARE(INT_MAX, obj1.objectIndex()); +} + +void tst_QTextFormat::defaultAlignment() +{ + QTextBlockFormat fmt; + QVERIFY(!fmt.hasProperty(QTextFormat::BlockAlignment)); + QCOMPARE(fmt.intProperty(QTextFormat::BlockAlignment), 0); + QVERIFY(fmt.alignment() == Qt::AlignLeft); +} + +void tst_QTextFormat::testUnderlinePropertyPrecedence() +{ + QTextCharFormat format; + // use normal accessors and check internal state + format.setUnderlineStyle(QTextCharFormat::NoUnderline); + QCOMPARE(format.property(QTextFormat::FontUnderline).isNull(), false); + QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).isNull(), false); + QCOMPARE(format.property(QTextFormat::FontUnderline).toBool(), false); + QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).toInt(), 0); + + format = QTextCharFormat(); + format.setUnderlineStyle(QTextCharFormat::SingleUnderline); + QCOMPARE(format.property(QTextFormat::FontUnderline).isNull(), false); + QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).isNull(), false); + QCOMPARE(format.property(QTextFormat::FontUnderline).toBool(), true); + QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).toInt(), 1); + + format = QTextCharFormat(); + format.setUnderlineStyle(QTextCharFormat::DotLine); + QCOMPARE(format.property(QTextFormat::FontUnderline).isNull(), false); + QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).isNull(), false); + QCOMPARE(format.property(QTextFormat::FontUnderline).toBool(), false); + QVERIFY(format.property(QTextFormat::TextUnderlineStyle).toInt() > 0); + + // override accessors and use setProperty to create a false state. + // then check font() + format = QTextCharFormat(); + format.setProperty(QTextCharFormat::FontUnderline, true); + QCOMPARE(format.property(QTextFormat::FontUnderline).isNull(), false); + QCOMPARE(format.property(QTextFormat::TextUnderlineStyle).isNull(), true); + QCOMPARE(format.fontUnderline(), true); + QCOMPARE(format.font().underline(), true); + + format = QTextCharFormat(); + // create conflict. Should use the new property + format.setProperty(QTextCharFormat::TextUnderlineStyle, QTextCharFormat::SingleUnderline); + format.setProperty(QTextCharFormat::FontUnderline, false); + QCOMPARE(format.fontUnderline(), true); + QCOMPARE(format.font().underline(), true); + + format = QTextCharFormat(); + // create conflict. Should use the new property + format.setProperty(QTextCharFormat::TextUnderlineStyle, QTextCharFormat::NoUnderline); + format.setProperty(QTextCharFormat::FontUnderline, true); + QCOMPARE(format.fontUnderline(), false); + QCOMPARE(format.font().underline(), false); + + // do it again, but reverse the ordering (we use a QVector internally, so test a LOT ;) + // create conflict. Should use the new property + format.setProperty(QTextCharFormat::FontUnderline, false); + format.setProperty(QTextCharFormat::TextUnderlineStyle, QTextCharFormat::SingleUnderline); + QCOMPARE(format.fontUnderline(), true); + QCOMPARE(format.font().underline(), true); + + format = QTextCharFormat(); + // create conflict. Should use the new property + format.setProperty(QTextCharFormat::FontUnderline, true); + format.setProperty(QTextCharFormat::TextUnderlineStyle, QTextCharFormat::NoUnderline); + QCOMPARE(format.fontUnderline(), false); + QCOMPARE(format.font().underline(), false); +} + +void tst_QTextFormat::toFormat() +{ + { + QTextFormat fmt = QTextFrameFormat(); + QCOMPARE(fmt.toFrameFormat().type(), int(QTextFormat::FrameFormat)); + } + + { + QTextFormat fmt = QTextTableFormat(); + QCOMPARE(fmt.toTableFormat().type(), int(QTextFormat::FrameFormat)); + QCOMPARE(fmt.toTableFormat().objectType(), int(QTextFormat::TableObject)); + } + + { + QTextFormat fmt = QTextBlockFormat(); + QCOMPARE(fmt.toBlockFormat().type(), int(QTextFormat::BlockFormat)); + } + + { + QTextFormat fmt = QTextCharFormat(); + QCOMPARE(fmt.toCharFormat().type(), int(QTextFormat::CharFormat)); + } + + { + QTextFormat fmt = QTextListFormat(); + QCOMPARE(fmt.toListFormat().type(), int(QTextFormat::ListFormat)); + } +} + +void tst_QTextFormat::resolveFont() +{ + QTextDocument doc; + + QTextCharFormat fmt; + fmt.setProperty(QTextFormat::ForegroundBrush, Qt::blue); + fmt.setProperty(QTextFormat::FontItalic, true); + QTextCursor(&doc).insertText("Test", fmt); + + QVector<QTextFormat> formats = doc.allFormats(); + QCOMPARE(formats.count(), 3); + + QVERIFY(formats.at(2).type() == QTextFormat::CharFormat); + fmt = formats.at(2).toCharFormat(); + + QVERIFY(!fmt.font().underline()); + QVERIFY(fmt.hasProperty(QTextFormat::ForegroundBrush)); + + QFont f; + f.setUnderline(true); + doc.setDefaultFont(f); + formats = doc.allFormats(); + fmt = formats.at(2).toCharFormat(); + + QVERIFY(fmt.font().underline()); + QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline)); + + // verify that deleting a non-existent property does not affect the font resolving + + QVERIFY(!fmt.hasProperty(QTextFormat::BackgroundBrush)); + fmt.clearProperty(QTextFormat::BackgroundBrush); + QVERIFY(!fmt.hasProperty(QTextFormat::BackgroundBrush)); + + QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline)); + QVERIFY(fmt.font().underline()); + + // verify that deleting an existent but font _unrelated_ property does not affect the font resolving + + QVERIFY(fmt.hasProperty(QTextFormat::ForegroundBrush)); + fmt.clearProperty(QTextFormat::ForegroundBrush); + QVERIFY(!fmt.hasProperty(QTextFormat::ForegroundBrush)); + + QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline)); + QVERIFY(fmt.font().underline()); + + // verify that removing a font property _does_ clear the resolving + + QVERIFY(fmt.hasProperty(QTextFormat::FontItalic)); + fmt.clearProperty(QTextFormat::FontItalic); + QVERIFY(!fmt.hasProperty(QTextFormat::FontItalic)); + + QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline)); + QVERIFY(!fmt.font().underline()); + QVERIFY(!fmt.font().italic()); + + // reset + fmt = formats.at(2).toCharFormat(); + + QVERIFY(fmt.font().underline()); + QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline)); + + // verify that _setting_ an unrelated property does _not_ affect the resolving + + QVERIFY(!fmt.hasProperty(QTextFormat::IsAnchor)); + fmt.setProperty(QTextFormat::IsAnchor, true); + QVERIFY(fmt.hasProperty(QTextFormat::IsAnchor)); + + QVERIFY(fmt.font().underline()); + QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline)); + + // verify that setting a _related_ font property does affect the resolving + // + QVERIFY(!fmt.hasProperty(QTextFormat::FontStrikeOut)); + fmt.setProperty(QTextFormat::FontStrikeOut, true); + QVERIFY(fmt.hasProperty(QTextFormat::FontStrikeOut)); + + QVERIFY(!fmt.font().underline()); + QVERIFY(!fmt.hasProperty(QTextFormat::FontUnderline)); + QVERIFY(fmt.font().strikeOut()); +} + +void tst_QTextFormat::getSetTabs() +{ + class Comparator { + public: + Comparator(const QList<QTextOption::Tab> &tabs, const QList<QTextOption::Tab> &tabs2) + { + QCOMPARE(tabs.count(), tabs2.count()); + for(int i=0; i < tabs.count(); i++) { + QTextOption::Tab t1 = tabs[i]; + QTextOption::Tab t2 = tabs2[i]; + QCOMPARE(t1.position, t2.position); + QCOMPARE(t1.type, t2.type); + QCOMPARE(t1.delimiter, t2.delimiter); + } + } + }; + + QList<QTextOption::Tab> tabs; + QTextBlockFormat format; + format.setTabPositions(tabs); + Comparator c1(tabs, format.tabPositions()); + + QTextOption::Tab tab1; + tab1.position = 1234; + tabs.append(tab1); + format.setTabPositions(tabs); + Comparator c2(tabs, format.tabPositions()); + + QTextOption::Tab tab2(3456, QTextOption::RightTab, QChar('x')); + tabs.append(tab2); + format.setTabPositions(tabs); + Comparator c3(tabs, format.tabPositions()); +} + +void tst_QTextFormat::testTabsUsed() +{ + QTextDocument doc; + QTextCursor cursor(&doc); + + QList<QTextOption::Tab> tabs; + QTextBlockFormat format; + QTextOption::Tab tab; + tab.position = 100; + tabs.append(tab); + format.setTabPositions(tabs); + cursor.mergeBlockFormat(format); + cursor.insertText("foo\tbar"); + //doc.setPageSize(QSizeF(200, 200)); + doc.documentLayout()->pageCount(); // force layout; + + QTextBlock block = doc.begin(); + QTextLayout *layout = block.layout(); + QVERIFY(layout); + QCOMPARE(layout->lineCount(), 1); + QTextLine line = layout->lineAt(0); + QCOMPARE(line.cursorToX(4), 100.); + + QTextOption option = layout->textOption(); + QCOMPARE(option.tabs().count(), tabs.count()); + +} + +void tst_QTextFormat::testFontStyleSetters() +{ + QTextCharFormat format; + + // test the setters + format.setFontStyleHint(QFont::Serif); + QCOMPARE(format.font().styleHint(), QFont::Serif); + QCOMPARE(format.font().styleStrategy(), QFont::PreferDefault); + format.setFontStyleStrategy(QFont::PreferOutline); + QCOMPARE(format.font().styleStrategy(), QFont::PreferOutline); + + // test setting properties through setFont() + QFont font; + font.setStyleHint(QFont::SansSerif, QFont::PreferAntialias); + format.setFont(font); + QCOMPARE(format.font().styleHint(), QFont::SansSerif); + QCOMPARE(format.font().styleStrategy(), QFont::PreferAntialias); + + // test kerning + format.setFontKerning(false); + QCOMPARE(format.font().kerning(), false); + format.setFontKerning(true); + QCOMPARE(format.font().kerning(), true); + font.setKerning(false); + format.setFont(font); + QCOMPARE(format.font().kerning(), false); +} + +QTEST_MAIN(tst_QTextFormat) +#include "tst_qtextformat.moc" diff --git a/tests/auto/gui/text/qtextlayout/.gitignore b/tests/auto/gui/text/qtextlayout/.gitignore new file mode 100644 index 0000000000..11758969db --- /dev/null +++ b/tests/auto/gui/text/qtextlayout/.gitignore @@ -0,0 +1 @@ +tst_qtextlayout diff --git a/tests/auto/gui/text/qtextlayout/qtextlayout.pro b/tests/auto/gui/text/qtextlayout/qtextlayout.pro new file mode 100644 index 0000000000..6bf0065e4f --- /dev/null +++ b/tests/auto/gui/text/qtextlayout/qtextlayout.pro @@ -0,0 +1,12 @@ +load(qttest_p4) +QT += core-private gui-private +HEADERS += +SOURCES += tst_qtextlayout.cpp +DEFINES += QT_COMPILES_IN_HARFBUZZ +INCLUDEPATH += $$QT_SOURCE_TREE/src/3rdparty/harfbuzz/src + +symbian { + TARGET.EPOCHEAPSIZE = 100000 20000000 +} + +qpa:contains(QT_CONFIG,qpa):CONFIG+=insignificant_test # QTBUG-20979 diff --git a/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp new file mode 100644 index 0000000000..2414ab3e37 --- /dev/null +++ b/tests/auto/gui/text/qtextlayout/tst_qtextlayout.cpp @@ -0,0 +1,1506 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/* + !!!!!! Warning !!!!! + Please don't save this file in emacs. It contains utf8 text sequences emacs will + silently convert to a series of question marks. + */ +#include <QtTest/QtTest> + + + +#include <private/qtextengine_p.h> +#include <qtextlayout.h> + +#include <qdebug.h> + + +#define TESTFONT_SIZE 12 + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QTextLayout : public QObject +{ + Q_OBJECT + +public: + tst_QTextLayout(); + virtual ~tst_QTextLayout(); + + +public slots: + void init(); + void cleanup(); +private slots: + void getSetCheck(); + void lineBreaking(); + void simpleBoundingRect(); + void threeLineBoundingRect(); + void boundingRectWithLongLineAndNoWrap(); + void forcedBreaks(); + void breakAny(); + void noWrap(); + void cursorToXForInlineObjects(); + void cursorToXForSetColumns(); + void defaultWordSeparators_data(); + void defaultWordSeparators(); + void cursorMovementFromInvalidPositions(); + void cursorMovementInsideSpaces(); + void charWordStopOnLineSeparator(); + void xToCursorAtEndOfLine(); + void boundingRectTopLeft(); + void charStopForSurrogatePairs(); + void tabStops(); + void integerOverflow(); + void testDefaultTabs(); + void testTabs(); + void testMultilineTab(); + void testRightTab(); + void testTabsInAlignedParag(); + void testCenteredTab(); + void testDelimiterTab(); + void testMultiTab(); + void testTabDPIScale(); + void tabsForRtl(); + void tabHeight(); + void capitalization_allUpperCase(); + void capitalization_allLowerCase(); + void capitalization_smallCaps(); + void capitalization_capitalize(); + void longText(); + void widthOfTabs(); + void columnWrapWithTabs(); + void boundingRectForUnsetLineWidth(); + void boundingRectForSetLineWidth(); + void glyphLessItems(); + + // QTextLine stuff + void setNumColumnsWrapAtWordBoundaryOrAnywhere(); + void setNumColumnsWordWrap(); + void smallTextLengthNoWrap(); + void smallTextLengthWordWrap(); + void smallTextLengthWrapAtWordBoundaryOrAnywhere(); + void testLineBreakingAllSpaces(); + void lineWidthFromBOM(); + void textWidthVsWIdth(); + void textWithSurrogates_qtbug15679(); + void textWidthWithStackedTextEngine(); + void textWidthWithLineSeparator(); + void cursorInLigatureWithMultipleLines(); + void xToCursorForLigatures(); + +private: + QFont testFont; +}; + +// Testing get/set functions +void tst_QTextLayout::getSetCheck() +{ + QString str("Bogus text"); + QTextLayout layout(str, testFont); + layout.beginLayout(); + QTextEngine *engine = layout.engine(); + QTextInlineObject obj1(0, engine); + // qreal QTextInlineObject::width() + // void QTextInlineObject::setWidth(qreal) + obj1.setWidth(0.0); + QCOMPARE(0.0, obj1.width()); + obj1.setWidth(1.2); + QVERIFY(1.0 < obj1.width()); + + // qreal QTextInlineObject::ascent() + // void QTextInlineObject::setAscent(qreal) + obj1.setAscent(0.0); + QCOMPARE(0.0, obj1.ascent()); + obj1.setAscent(1.2); + QVERIFY(1.0 < obj1.ascent()); + + // qreal QTextInlineObject::descent() + // void QTextInlineObject::setDescent(qreal) + obj1.setDescent(0.0); + QCOMPARE(0.0, obj1.descent()); + obj1.setDescent(1.2); + QVERIFY(1.0 < obj1.descent()); + + QTextLayout obj2; + // bool QTextLayout::cacheEnabled() + // void QTextLayout::setCacheEnabled(bool) + obj2.setCacheEnabled(false); + QCOMPARE(false, obj2.cacheEnabled()); + obj2.setCacheEnabled(true); + QCOMPARE(true, obj2.cacheEnabled()); +} + +QT_BEGIN_NAMESPACE +extern void qt_setQtEnableTestFont(bool value); +QT_END_NAMESPACE + +tst_QTextLayout::tst_QTextLayout() +{ + qt_setQtEnableTestFont(true); +} + +tst_QTextLayout::~tst_QTextLayout() +{ +} + +void tst_QTextLayout::init() +{ + testFont = QFont(); + testFont.setFamily("__Qt__Box__Engine__"); + testFont.setPixelSize(TESTFONT_SIZE); + testFont.setWeight(QFont::Normal); + QCOMPARE(QFontMetrics(testFont).width('a'), testFont.pixelSize()); +} + +void tst_QTextLayout::cleanup() +{ + testFont = QFont(); +} + + +void tst_QTextLayout::lineBreaking() +{ +#if defined(Q_WS_X11) + struct Breaks { + const char *utf8; + uchar breaks[32]; + }; + Breaks brks[] = { + { "11", { false, 0xff } }, + { "aa", { false, 0xff } }, + { "++", { false, 0xff } }, + { "--", { false, 0xff } }, + { "((", { false, 0xff } }, + { "))", { false, 0xff } }, + { "..", { false, 0xff } }, + { "\"\"", { false, 0xff } }, + { "$$", { false, 0xff } }, + { "!!", { false, 0xff } }, + { "??", { false, 0xff } }, + { ",,", { false, 0xff } }, + + { ")()", { true, false, 0xff } }, + { "?!?", { false, false, 0xff } }, + { ".,.", { false, false, 0xff } }, + { "+-+", { false, false, 0xff } }, + { "+=+", { false, false, 0xff } }, + { "+(+", { false, false, 0xff } }, + { "+)+", { false, false, 0xff } }, + + { "a b", { false, true, 0xff } }, + { "a(b", { false, false, 0xff } }, + { "a)b", { false, false, 0xff } }, + { "a-b", { false, true, 0xff } }, + { "a.b", { false, false, 0xff } }, + { "a+b", { false, false, 0xff } }, + { "a?b", { false, false, 0xff } }, + { "a!b", { false, false, 0xff } }, + { "a$b", { false, false, 0xff } }, + { "a,b", { false, false, 0xff } }, + { "a/b", { false, false, 0xff } }, + { "1/2", { false, false, 0xff } }, + { "./.", { false, false, 0xff } }, + { ",/,", { false, false, 0xff } }, + { "!/!", { false, false, 0xff } }, + { "\\/\\", { false, false, 0xff } }, + { "1 2", { false, true, 0xff } }, + { "1(2", { false, false, 0xff } }, + { "1)2", { false, false, 0xff } }, + { "1-2", { false, false, 0xff } }, + { "1.2", { false, false, 0xff } }, + { "1+2", { false, false, 0xff } }, + { "1?2", { false, true, 0xff } }, + { "1!2", { false, true, 0xff } }, + { "1$2", { false, false, 0xff } }, + { "1,2", { false, false, 0xff } }, + { "1/2", { false, false, 0xff } }, + { "\330\260\331\216\331\204\331\220\331\203\331\216", { false, false, false, false, false, 0xff } }, + { "\330\247\331\204\331\205 \330\247\331\204\331\205", { false, false, false, true, false, false, 0xff } }, + { "1#2", { false, false, 0xff } }, + { "!#!", { false, false, 0xff } }, + { 0, {} } + }; + Breaks *b = brks; + while (b->utf8) { + QString str = QString::fromUtf8(b->utf8); + QTextEngine engine(str, QFont()); + const HB_CharAttributes *attrs = engine.attributes(); + int i; + for (i = 0; i < (int)str.length() - 1; ++i) { + QVERIFY(b->breaks[i] != 0xff); + if ( (attrs[i].lineBreakType != HB_NoBreak) != (bool)b->breaks[i] ) { + qDebug("test case \"%s\" failed at char %d; break type: %d", b->utf8, i, attrs[i].lineBreakType); + QCOMPARE( (attrs[i].lineBreakType != HB_NoBreak), (bool)b->breaks[i] ); + } + } + QVERIFY(attrs[i].lineBreakType == HB_ForcedBreak); + QCOMPARE(b->breaks[i], (uchar)0xff); + ++b; + } +#else + QSKIP("This test can not be run on non-X11 platforms", SkipAll); +#endif +} + +void tst_QTextLayout::simpleBoundingRect() +{ + /* just check if boundingRect() gives sane values. The text is not broken. */ + + QString hello("hello world"); + + const int width = hello.length() * testFont.pixelSize(); + + QTextLayout layout(hello, testFont); + layout.beginLayout(); + + QTextLine line = layout.createLine(); + line.setLineWidth(width); + QCOMPARE(line.textLength(), hello.length()); + QCOMPARE(layout.boundingRect(), QRectF(0, 0, width, QFontMetrics(testFont).height())); +} + +void tst_QTextLayout::threeLineBoundingRect() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + /* stricter check. break text into three lines */ + + QString firstWord("hello"); + QString secondWord("world"); + QString thirdWord("test"); + QString text(firstWord + ' ' + secondWord + ' ' + thirdWord); + + const int firstLineWidth = firstWord.length() * testFont.pixelSize(); + const int secondLineWidth = secondWord.length() * testFont.pixelSize(); + const int thirdLineWidth = thirdWord.length() * testFont.pixelSize(); + + const int longestLine = qMax(firstLineWidth, qMax(secondLineWidth, thirdLineWidth)); + + QTextLayout layout(text, testFont); + layout.beginLayout(); + + int pos = 0; + int y = 0; + QTextLine line = layout.createLine(); + line.setLineWidth(firstLineWidth); + line.setPosition(QPoint(0, y)); + QCOMPARE(line.textStart(), pos); + // + 1 for trailing space + QCOMPARE(line.textLength(), firstWord.length() + 1); + QCOMPARE(qRound(line.naturalTextWidth()), firstLineWidth); + + pos += line.textLength(); + y += qRound(line.ascent() + line.descent()); + + line = layout.createLine(); + line.setLineWidth(secondLineWidth); + line.setPosition(QPoint(0, y)); + // + 1 for trailing space + QCOMPARE(line.textStart(), pos); + QCOMPARE(line.textLength(), secondWord.length() + 1); + QCOMPARE(qRound(line.naturalTextWidth()), secondLineWidth); + + pos += line.textLength(); + y += qRound(line.ascent() + line.descent()); + + line = layout.createLine(); + line.setLineWidth(secondLineWidth); + line.setPosition(QPoint(0, y)); + // no trailing space here! + QCOMPARE(line.textStart(), pos); + QCOMPARE(line.textLength(), thirdWord.length()); + QCOMPARE(qRound(line.naturalTextWidth()), thirdLineWidth); + y += qRound(line.ascent() + line.descent()); + + QCOMPARE(layout.boundingRect(), QRectF(0, 0, longestLine, y + 1)); +} + +void tst_QTextLayout::boundingRectWithLongLineAndNoWrap() +{ + QString longString("thisisaverylongstringthatcannotbewrappedatallitjustgoesonandonlikeonebigword"); + + const int width = longString.length() * testFont.pixelSize() / 20; // very small widthx + + QTextLayout layout(longString, testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(width); + + QVERIFY(layout.boundingRect().width() >= line.width()); + QCOMPARE(layout.boundingRect().width(), line.naturalTextWidth()); +} + +void tst_QTextLayout::forcedBreaks() +{ + QString text = "A\n\nB\nC"; + text.replace('\n', QChar::LineSeparator); + + QTextLayout layout(text, testFont); + + layout.beginLayout(); + + int pos = 0; + + QTextLine line = layout.createLine(); + line.setLineWidth(0x10000); + QCOMPARE(line.textStart(), pos); + QCOMPARE(line.textLength(),2); + QCOMPARE(qRound(line.naturalTextWidth()),testFont.pixelSize()); + QCOMPARE((int) line.height(), testFont.pixelSize() + 1); // + 1 baseline + QCOMPARE(line.xToCursor(0), line.textStart()); + pos += line.textLength(); + + line = layout.createLine(); + line.setLineWidth(0x10000); + QCOMPARE(line.textStart(),pos); + QCOMPARE(line.textLength(),1); + QCOMPARE(qRound(line.naturalTextWidth()), 0); + QCOMPARE((int) line.height(), testFont.pixelSize() + 1); // + 1 baseline + QCOMPARE(line.xToCursor(0), line.textStart()); + pos += line.textLength(); + + line = layout.createLine(); + line.setLineWidth(0x10000); + QCOMPARE(line.textStart(),pos); + QCOMPARE(line.textLength(),2); + QCOMPARE(qRound(line.naturalTextWidth()),testFont.pixelSize()); + QCOMPARE(qRound(line.height()), testFont.pixelSize() + 1); // + 1 baseline + QCOMPARE(line.xToCursor(0), line.textStart()); + pos += line.textLength(); + + line = layout.createLine(); + line.setLineWidth(0x10000); + QCOMPARE(line.textStart(),pos); + QCOMPARE(line.textLength(),1); + QCOMPARE(qRound(line.naturalTextWidth()), testFont.pixelSize()); + QCOMPARE((int) line.height(), testFont.pixelSize() + 1); // + 1 baseline + QCOMPARE(line.xToCursor(0), line.textStart()); +} + +void tst_QTextLayout::breakAny() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + QString text = "ABCD"; + + QTextLayout layout(text, testFont); + QTextLine line; + + QTextOption opt; + opt.setWrapMode(QTextOption::WrapAnywhere); + layout.setTextOption(opt); + layout.beginLayout(); + + line = layout.createLine(); + line.setLineWidth(testFont.pixelSize() * 2); + QCOMPARE(line.textStart(), 0); + QCOMPARE(line.textLength(), 2); + + line = layout.createLine(); + line.setLineWidth(testFont.pixelSize() * 2); + QCOMPARE(line.textStart(), 2); + QCOMPARE(line.textLength(), 2); + + line = layout.createLine(); + QVERIFY(!line.isValid()); + + layout.endLayout(); + + text = "ABCD EFGH"; + layout.setText(text); + layout.beginLayout(); + + line = layout.createLine(); + line.setLineWidth(testFont.pixelSize() * 7); + QCOMPARE(line.textStart(), 0); + QCOMPARE(line.textLength(), 7); + + layout.endLayout(); +} + +void tst_QTextLayout::noWrap() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + QString text = "AB CD"; + + QTextLayout layout(text, testFont); + QTextLine line; + + QTextOption opt; + opt.setWrapMode(QTextOption::NoWrap); + layout.setTextOption(opt); + layout.beginLayout(); + + line = layout.createLine(); + line.setLineWidth(testFont.pixelSize() * 2); + QCOMPARE(line.textStart(), 0); + QCOMPARE(line.textLength(), 5); + + line = layout.createLine(); + QVERIFY(!line.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::cursorToXForInlineObjects() +{ + QChar ch(QChar::ObjectReplacementCharacter); + QString text(ch); + QTextLayout layout(text, testFont); + layout.beginLayout(); + + QTextEngine *engine = layout.engine(); + const int item = engine->findItem(0); + engine->layoutData->items[item].width = 32; + + QTextLine line = layout.createLine(); + line.setLineWidth(0x10000); + + QCOMPARE(line.cursorToX(0), qreal(0)); + QCOMPARE(line.cursorToX(1), qreal(32)); +} + +void tst_QTextLayout::cursorToXForSetColumns() +{ + QTextLayout lay("abc", testFont); + QTextOption o = lay.textOption(); + o.setWrapMode(QTextOption::WrapAnywhere); + + // enable/disable this line for full effect ;) + o.setAlignment(Qt::AlignHCenter); + + lay.setTextOption(o); + lay.beginLayout(); + QTextLine line = lay.createLine(); + line.setNumColumns(1); + lay.endLayout(); + QCOMPARE(line.cursorToX(0), 0.); + QCOMPARE(line.cursorToX(1), (qreal) TESTFONT_SIZE); +} + +void tst_QTextLayout::defaultWordSeparators_data() +{ + QTest::addColumn<QString>("text"); + QTest::addColumn<int>("startPos"); + QTest::addColumn<int>("endPos"); + + QString separators(".,:;-<>[](){}=/+%&^*"); + separators += QLatin1String("!?"); + for (int i = 0; i < separators.count(); ++i) { + QTest::newRow(QString::number(i).toAscii().data()) + << QString::fromLatin1("abcd") + separators.at(i) + QString::fromLatin1("efgh") + << 0 << 4; + } + + QTest::newRow("nbsp") + << QString::fromLatin1("abcd") + QString(QChar::Nbsp) + QString::fromLatin1("efgh") + << 0 << 5; + + QTest::newRow("tab") + << QString::fromLatin1("abcd") + QString::fromLatin1("\t") + QString::fromLatin1("efgh") + << 0 << 5; + + QTest::newRow("lineseparator") + << QString::fromLatin1("abcd") + QString(QChar::LineSeparator) + QString::fromLatin1("efgh") + << 0 << 5; + + QTest::newRow("empty") + << QString() + << 0 << 0; +} + +void tst_QTextLayout::defaultWordSeparators() +{ + QFETCH(QString, text); + QFETCH(int, startPos); + QFETCH(int, endPos); + QTextLayout layout(text, testFont); + + QCOMPARE(layout.nextCursorPosition(startPos, QTextLayout::SkipWords), endPos); + QCOMPARE(layout.previousCursorPosition(endPos, QTextLayout::SkipWords), startPos); +} + +void tst_QTextLayout::cursorMovementFromInvalidPositions() +{ + int badpos = 10000; + + QTextLayout layout("ABC", testFont); + + QCOMPARE(layout.previousCursorPosition(-badpos, QTextLayout::SkipCharacters), -badpos); + QCOMPARE(layout.nextCursorPosition(-badpos, QTextLayout::SkipCharacters), -badpos); + + QCOMPARE(layout.previousCursorPosition(badpos, QTextLayout::SkipCharacters), badpos); + QCOMPARE(layout.nextCursorPosition(badpos, QTextLayout::SkipCharacters), badpos); +} + +void tst_QTextLayout::cursorMovementInsideSpaces() +{ + QTextLayout layout("ABC DEF", testFont); + + QCOMPARE(layout.previousCursorPosition(6, QTextLayout::SkipWords), 0); + QCOMPARE(layout.nextCursorPosition(6, QTextLayout::SkipWords), 15); + + + QTextLayout layout2("ABC\t\t\t\t\t\t\t\t\t\t\t\tDEF", testFont); + + QCOMPARE(layout2.previousCursorPosition(6, QTextLayout::SkipWords), 0); + QCOMPARE(layout2.nextCursorPosition(6, QTextLayout::SkipWords), 15); +} + +void tst_QTextLayout::charWordStopOnLineSeparator() +{ + const QChar lineSeparator(QChar::LineSeparator); + QString txt; + txt.append(lineSeparator); + txt.append(lineSeparator); + QTextLayout layout(txt, testFont); + QTextEngine *engine = layout.engine(); + const HB_CharAttributes *attrs = engine->attributes(); + QVERIFY(attrs); + QVERIFY(attrs[1].charStop); +} + +void tst_QTextLayout::xToCursorAtEndOfLine() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + QString text = "FirstLine SecondLine"; + text.replace('\n', QChar::LineSeparator); + + const qreal firstLineWidth = QString("FirstLine").length() * testFont.pixelSize(); + + QTextLayout layout(text, testFont); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + QVERIFY(line.isValid()); + line.setLineWidth(firstLineWidth); + QVERIFY(layout.createLine().isValid()); + QVERIFY(!layout.createLine().isValid()); + layout.endLayout(); + + line = layout.lineAt(0); + QCOMPARE(line.xToCursor(100000), 9); + line = layout.lineAt(1); + QCOMPARE(line.xToCursor(100000), 20); +} + +void tst_QTextLayout::boundingRectTopLeft() +{ + QString text = "FirstLine\nSecondLine"; + text.replace('\n', QChar::LineSeparator); + + QTextLayout layout(text, testFont); + + layout.beginLayout(); + QTextLine firstLine = layout.createLine(); + QVERIFY(firstLine.isValid()); + firstLine.setPosition(QPointF(10, 10)); + QTextLine secondLine = layout.createLine(); + QVERIFY(secondLine.isValid()); + secondLine.setPosition(QPointF(20, 20)); + layout.endLayout(); + + QCOMPARE(layout.boundingRect().topLeft(), firstLine.position()); +} + +void tst_QTextLayout::charStopForSurrogatePairs() +{ + QString txt; + txt.append("a"); + txt.append(0xd87e); + txt.append(0xdc25); + txt.append("b"); + QTextLayout layout(txt, testFont); + QTextEngine *engine = layout.engine(); + const HB_CharAttributes *attrs = engine->attributes(); + QVERIFY(attrs); + QVERIFY(attrs[0].charStop); + QVERIFY(attrs[1].charStop); + QVERIFY(!attrs[2].charStop); + QVERIFY(attrs[3].charStop); +} + +void tst_QTextLayout::tabStops() +{ +#if defined(Q_WS_MAC) + QSKIP("QTestFontEngine on the mac does not support logclusters at the moment", SkipAll); +#endif + QString txt("Hello there\tworld"); + QTextLayout layout(txt, testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + + QVERIFY(line.isValid()); + line.setNumColumns(11); + QCOMPARE(line.textLength(), TESTFONT_SIZE); + + line = layout.createLine(); + QVERIFY(line.isValid()); + line.setNumColumns(5); + QCOMPARE(line.textLength(), 5); + + layout.endLayout(); +} + +void tst_QTextLayout::integerOverflow() +{ + QString txt("Hello world... "); + + for (int i = 0; i < 8; ++i) + txt += txt; + + QTextLayout layout(txt, testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + + QVERIFY(line.isValid()); + line.setLineWidth(INT_MAX); + QCOMPARE(line.textLength(), txt.length()); + + QVERIFY(!layout.createLine().isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::setNumColumnsWrapAtWordBoundaryOrAnywhere() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setNumColumns(1); + + // qDebug() << line1.naturalTextWidth(); + QCOMPARE(line1.textLength(), 1); + QVERIFY(line1.naturalTextWidth() == testFont.pixelSize()); // contains only one character + + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::setNumColumnsWordWrap() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::WordWrap); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setNumColumns(1); + + // qDebug() << line1.naturalTextWidth(); + QCOMPARE(line1.textLength(), 5); + QVERIFY(line1.naturalTextWidth() > 20.0); // contains the whole first word. + + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::smallTextLengthNoWrap() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::NoWrap); + layout.setTextOption(option); + + /// NoWrap + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setLineWidth(5); // most certainly too short for the word 'This' to fit. + + QCOMPARE(line1.width(), 5.0); + QVERIFY(line1.naturalTextWidth() > 70); // contains all the text. + + QTextLine line2 = layout.createLine(); + QVERIFY(! line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::smallTextLengthWordWrap() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::WordWrap); + layout.setTextOption(option); + + /// WordWrap + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setLineWidth(5); // most certainly too short for the word 'This' to fit. + + QCOMPARE(line1.width(), 5.0); + QVERIFY(line1.naturalTextWidth() > 20.0); // contains the whole first word. + QCOMPARE(line1.textLength(), 5); + + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::smallTextLengthWrapAtWordBoundaryOrAnywhere() +{ + QString txt("This is a small test text"); + QTextLayout layout(txt, testFont); + QTextOption option = layout.textOption(); + option.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + QVERIFY(line1.isValid()); + line1.setLineWidth(5); // most certainly too short for the word 'This' to fit. + + QCOMPARE(line1.width(), 5.0); + // qDebug() << line1.naturalTextWidth(); + QCOMPARE(line1.textLength(), 1); + QVERIFY(line1.naturalTextWidth() == testFont.pixelSize()); // contains just the characters that fit. + + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + + layout.endLayout(); +} + +void tst_QTextLayout::testDefaultTabs() +{ + QTextLayout layout("Foo\tBar\ta slightly longer text\tend.", testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(1000); + layout.endLayout(); + + //qDebug() << "After the tab: " << line.cursorToX(4); + QCOMPARE(line.cursorToX(4), 80.); // default tab is 80 + QCOMPARE(line.cursorToX(8), 160.); + QCOMPARE(line.cursorToX(31), 480.); + + QTextOption option = layout.textOption(); + option.setTabStop(90); + layout.setTextOption(option); + layout.beginLayout(); + line = layout.createLine(); + line.setLineWidth(1000); + layout.endLayout(); + + QCOMPARE(line.cursorToX(4), 90.); + QCOMPARE(line.cursorToX(8), 180.); + QCOMPARE(line.cursorToX(31), 450.); + + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.position = 110; // set one tab to 110, but since the rest is unset they will be at the normal interval again. + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + layout.beginLayout(); + line = layout.createLine(); + line.setLineWidth(1000); + layout.endLayout(); + + QCOMPARE(line.cursorToX(4), 110.); + QCOMPARE(line.cursorToX(8), 180.); + QCOMPARE(line.cursorToX(31), 450.); +} + +void tst_QTextLayout::testTabs() +{ + QTextLayout layout("Foo\tBar.", testFont); + QTextOption option = layout.textOption(); + option.setTabStop(150); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(200.); + layout.endLayout(); + + QVERIFY(line.naturalTextWidth() > 150); + QCOMPARE(line.cursorToX(4), 150.); +} + +void tst_QTextLayout::testMultilineTab() +{ + QTextLayout layout("Lorem ipsum dolor sit\tBar.", testFont); + // test if this works on the second line. + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(220.); // moves 'sit' to next line. + line = layout.createLine(); + line.setLineWidth(220.); + layout.endLayout(); + + QCOMPARE(line.cursorToX(22), 80.); +} + +void tst_QTextLayout::testMultiTab() +{ + QTextLayout layout("Foo\t\t\tBar.", testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(1000.); + layout.endLayout(); + + QCOMPARE(line.cursorToX(6), 80. * 3); +} + +void tst_QTextLayout::testTabsInAlignedParag() +{ + QTextLayout layout("Foo\tsome more words", testFont); + QTextOption option = layout.textOption(); + // right + option.setAlignment(Qt::AlignRight); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(300.); + layout.endLayout(); + + const qreal textWidth = 80 + 15 * TESTFONT_SIZE; // 15 chars right of the tab + QCOMPARE(line.naturalTextWidth(), textWidth); + QCOMPARE(line.cursorToX(0), 300. - textWidth); + + // centered + option.setAlignment(Qt::AlignCenter); + layout.setTextOption(option); + + layout.beginLayout(); + line = layout.createLine(); + line.setLineWidth(300.); + layout.endLayout(); + + QCOMPARE(line.naturalTextWidth(), textWidth); + QCOMPARE(line.cursorToX(0), (300. - textWidth) / 2.); + + // justified + option.setAlignment(Qt::AlignJustify); + layout.setTextOption(option); + + layout.beginLayout(); + line = layout.createLine(); + line.setLineWidth(textWidth - 10); // make the last word slip to the next line so justification actually happens. + layout.endLayout(); + + QCOMPARE(line.cursorToX(0), 0.); + QCOMPARE(line.cursorToX(4), 80.); + + //QTextLayout layout2("Foo\tUt wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis", testFont); // means it will be more then one line long. +} + +void tst_QTextLayout::testRightTab() +{ + QTextLayout layout("Foo\tLorem ipsum te sit\tBar baz\tText\tEnd", testFont); + /* ^ a ^ b ^ c ^ d + a = 4, b = 22, c = 30, d = 35 (position) + + I expect the output to be: + Foo Lorem ipsum te + sit Bar Baz + Text End + + a) tab replaced with a single space due to the text not fitting before the tab. + b) tab takes space so the text until the 3th tab fits to the tab pos. + c) tab is after last tab (both auto and defined) and thus moves text to start of next line. + d) tab takes space so text until enter fits to tab pos. + */ + + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.type = QTextOption::RightTab; + tab.position = 190; // which means only 15(.8) chars of our test font fit left of it. + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + line1.setLineWidth(220.); + // qDebug() << "=====line 2"; + QTextLine line2 = layout.createLine(); + QVERIFY(line2.isValid()); + line2.setLineWidth(220.); + // qDebug() << "=====line 3"; + QTextLine line3 = layout.createLine(); + QVERIFY(line3.isValid()); + line3.setLineWidth(220.); + // qDebug() << "=====line 4"; + QTextLine line4 = layout.createLine(); + QVERIFY(! line4.isValid()); + layout.endLayout(); + // qDebug() << "--------"; + + QCOMPARE(line1.cursorToX(4), 3. * TESTFONT_SIZE ); // a + QCOMPARE(line1.textLength(), 19); + QCOMPARE(line2.cursorToX(23), 190. - 7. * TESTFONT_SIZE); // b + QCOMPARE(line2.textLength(), 12); + QCOMPARE(line3.cursorToX(31), 0.); // c + QCOMPARE(line3.cursorToX(36), 190 - 3. * TESTFONT_SIZE); // d +} + +void tst_QTextLayout::testCenteredTab() +{ + QTextLayout layout("Foo\tBar", testFont); + // test if centering the tab works. We expect the center of 'Bar.' to be at the tab point. + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab(150, QTextOption::CenterTab); + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(200.); + layout.endLayout(); + + const qreal wordLength = 3 * TESTFONT_SIZE; // the length of 'Bar' + QCOMPARE(line.cursorToX(4), 150 - wordLength / 2.); +} + +void tst_QTextLayout::testDelimiterTab() +{ + QTextLayout layout("Foo\tBar. Barrabas", testFont); + // try the different delimiter characters to see if the alignment works there. + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab(100, QTextOption::DelimiterTab, QChar('.')); + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(200.); + layout.endLayout(); + + const qreal distanceBeforeTab = 3.5 * TESTFONT_SIZE; // the length of 'bar' and half the width of the dot. + QCOMPARE(line.cursorToX(4), 100 - distanceBeforeTab); +} + +void tst_QTextLayout::testLineBreakingAllSpaces() +{ + QTextLayout layout(" 123", testFont); // thats 20 spaces + const qreal firstLineWidth = 17 * TESTFONT_SIZE; + layout.beginLayout(); + QTextLine line1 = layout.createLine(); + line1.setLineWidth(firstLineWidth); + QTextLine line2 = layout.createLine(); + line2.setLineWidth(1000); // the rest + layout.endLayout(); + QCOMPARE(line1.width(), firstLineWidth); + QCOMPARE(line1.naturalTextWidth(), 0.); // spaces don't take space + QCOMPARE(line1.textLength(), 20); + QCOMPARE(line2.textLength(), 3); + QCOMPARE(line2.naturalTextWidth(), 3. * TESTFONT_SIZE); +} + +void tst_QTextLayout::tabsForRtl() +{ + QString word(QChar(0x5e9)); // a hebrew character + word = word + word + word; // 3 hebrew characters ;) + + QTextLayout layout(word +'\t'+ word +'\t'+ word +'\t'+ word, testFont); +//QTextLayout layout(word +' '+ word +' '+ word +' '+ word, testFont);// tester ;) + /* ^ a ^ b ^ c + a = 4, b = 8, c = 12, d = 16 (position) + + a) Left tab in RTL is a righ tab; so a is at width - 80 + b) Like a + c) right tab on RTL is a left tab; so its at width - 240 + d) center tab is still a centered tab. + */ + + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.position = 80; + tabs.append(tab); + tab.position = 160; + tabs.append(tab); + tab.position = 240; + tab.type = QTextOption::RightTab; + tabs.append(tab); + option.setTabs(tabs); + option.setTextDirection(Qt::RightToLeft); + option.setAlignment(Qt::AlignRight); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + const qreal WIDTH = 400.; + line.setLineWidth(WIDTH); + layout.endLayout(); + +//qDebug() << "layout ended --------------"; + + QCOMPARE(line.cursorToX(0), WIDTH); + QCOMPARE(line.cursorToX(1), WIDTH - TESTFONT_SIZE); // check its right-aligned + QCOMPARE(line.cursorToX(4), WIDTH - 80 + 3 * TESTFONT_SIZE); + QCOMPARE(line.cursorToX(8), WIDTH - 160 + 3 * TESTFONT_SIZE); + QCOMPARE(line.cursorToX(12), WIDTH - 240); +} + +QT_BEGIN_NAMESPACE +extern int qt_defaultDpiY(); +QT_END_NAMESPACE + +void tst_QTextLayout::testTabDPIScale() +{ + class MyPaintDevice : public QPaintDevice { + QPaintEngine *paintEngine () const { return 0; } + int metric (QPaintDevice::PaintDeviceMetric metric) const { + switch(metric) { + case QPaintDevice::PdmWidth: + case QPaintDevice::PdmHeight: + case QPaintDevice::PdmWidthMM: + case QPaintDevice::PdmHeightMM: + case QPaintDevice::PdmNumColors: + return INT_MAX; + case QPaintDevice::PdmDepth: + return 32; + case QPaintDevice::PdmDpiX: + case QPaintDevice::PdmDpiY: + case QPaintDevice::PdmPhysicalDpiX: + case QPaintDevice::PdmPhysicalDpiY: + return 72; + } + return 0; + } + }; + + MyPaintDevice pd; + + QTextLayout layout("text1\ttext2\ttext3\tend", testFont, &pd); + + QTextOption option = layout.textOption(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab; + tab.position = 300; + tabs.append(tab); + + tab.position = 600; + tab.type = QTextOption::RightTab; + tabs.append(tab); + + tab.position = 800; + tab.type = QTextOption::CenterTab; + tabs.append(tab); + option.setTabs(tabs); + layout.setTextOption(option); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(1500.); + layout.endLayout(); + QCOMPARE(line.cursorToX(0), 0.); + QCOMPARE(line.cursorToX(1), (double) TESTFONT_SIZE); // check that the font does not resize + qreal scale = 72 / (qreal) qt_defaultDpiY(); + // lets do the transformation of deminishing resolution that QFixed has as effect. + int fixedScale = (int)( scale * qreal(64)); // into a QFixed + scale = ((qreal)fixedScale)/(qreal)64; // and out of a QFixed + + QCOMPARE(line.cursorToX(6), tabs.at(0).position * scale); + QCOMPARE(line.cursorToX(12), tabs.at(1).position * scale - TESTFONT_SIZE * 5); + QCOMPARE(line.cursorToX(18), tabs.at(2).position * scale - TESTFONT_SIZE * 3 / 2.0); +} + +void tst_QTextLayout::tabHeight() +{ + QTextLayout layout("\t", testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QCOMPARE(qRound(line.ascent()), QFontMetrics(testFont).ascent()); + QCOMPARE(qRound(line.descent()), QFontMetrics(testFont).descent()); +} + +void tst_QTextLayout::capitalization_allUpperCase() +{ + QFont font(testFont); + font.setCapitalization(QFont::AllUppercase); + QTextLayout layout("Test", font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QTextEngine *engine = layout.engine(); + engine->itemize(); + QCOMPARE(engine->layoutData->items.count(), 1); + QVERIFY(engine->layoutData->items.at(0).analysis.flags == QScriptAnalysis::Uppercase); +} + +void tst_QTextLayout::capitalization_allLowerCase() +{ + QFont font(testFont); + font.setCapitalization(QFont::AllLowercase); + QTextLayout layout("Test", font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QTextEngine *engine = layout.engine(); + engine->itemize(); + QCOMPARE(engine->layoutData->items.count(), 1); + QVERIFY(engine->layoutData->items.at(0).analysis.flags == QScriptAnalysis::Lowercase); +} + +void tst_QTextLayout::capitalization_smallCaps() +{ + QFont font(testFont); + font.setCapitalization(QFont::SmallCaps); + QTextLayout layout("Test", font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QTextEngine *engine = layout.engine(); + engine->itemize(); + QCOMPARE(engine->layoutData->items.count(), 2); + QVERIFY(engine->layoutData->items.at(0).analysis.flags == QScriptAnalysis::None); + QVERIFY(engine->layoutData->items.at(1).analysis.flags == QScriptAnalysis::SmallCaps); +} + +void tst_QTextLayout::capitalization_capitalize() +{ + QFont font(testFont); + font.setCapitalization(QFont::Capitalize); + QTextLayout layout("hello\tworld", font); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QTextEngine *engine = layout.engine(); + engine->itemize(); + QCOMPARE(engine->layoutData->items.count(), 5); + QVERIFY(engine->layoutData->items.at(0).analysis.flags == QScriptAnalysis::Uppercase); + QVERIFY(engine->layoutData->items.at(1).analysis.flags == QScriptAnalysis::None); + QVERIFY(engine->layoutData->items.at(2).analysis.flags == QScriptAnalysis::Tab); + QVERIFY(engine->layoutData->items.at(3).analysis.flags == QScriptAnalysis::Uppercase); + QVERIFY(engine->layoutData->items.at(4).analysis.flags == QScriptAnalysis::None); +} + +void tst_QTextLayout::longText() +{ + QString longText(128000, 'a'); + + { + QTextLayout layout(longText, testFont); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QVERIFY(line.isValid()); + QVERIFY(line.cursorToX(line.textLength() - 1) > 0); + } + + for (int cap = QFont::MixedCase; cap < QFont::Capitalize + 1; ++cap) { + QFont f(testFont); + f.setCapitalization(QFont::Capitalization(cap)); + QTextLayout layout(longText, f); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QVERIFY(line.isValid()); + QVERIFY(line.cursorToX(line.textLength() - 1) > 0); + } + + { + QTextLayout layout(longText, testFont); + layout.setFlags(Qt::TextForceLeftToRight); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QVERIFY(line.isValid()); + QVERIFY(line.cursorToX(line.textLength() - 1) > 0); + } + + { + QTextLayout layout(longText, testFont); + layout.setFlags(Qt::TextForceRightToLeft); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QVERIFY(line.isValid()); + QVERIFY(line.cursorToX(line.textLength() - 1) > 0); + } +} + +void tst_QTextLayout::widthOfTabs() +{ + QTextEngine engine("ddd\t\t", testFont); + engine.ignoreBidi = true; + engine.itemize(); + QCOMPARE(qRound(engine.width(0, 5)), qRound(engine.boundingBox(0, 5).width)); +} + +void tst_QTextLayout::columnWrapWithTabs() +{ + QTextLayout textLayout; + { + QTextOption textOption; + textOption.setWrapMode(QTextOption::WordWrap); + textLayout.setTextOption(textOption); + } + + // Make sure string with spaces does not break + { + QString text = "Foo bar foo bar foo bar"; + textLayout.setText(text); + + textLayout.beginLayout(); + QTextLine line = textLayout.createLine(); + line.setNumColumns(30); + QCOMPARE(line.textLength(), text.length()); + textLayout.endLayout(); + } + + // Make sure string with tabs breaks + { + QString text = "Foo\tbar\tfoo\tbar\tfoo\tbar"; + textLayout.setText(text); + textLayout.beginLayout(); + QTextLine line = textLayout.createLine(); + line.setNumColumns(30); + QVERIFY(line.textLength() < text.length()); + textLayout.endLayout(); + } + +} + +void tst_QTextLayout::boundingRectForUnsetLineWidth() +{ + QTextLayout layout("FOOBAR"); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QCOMPARE(layout.boundingRect().width(), line.naturalTextWidth()); +} + +void tst_QTextLayout::boundingRectForSetLineWidth() +{ + QTextLayout layout("FOOBAR"); + + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(QFIXED_MAX - 1); + layout.endLayout(); + + QCOMPARE(layout.boundingRect().width(), qreal(QFIXED_MAX - 1)); +} + +void tst_QTextLayout::lineWidthFromBOM() +{ + const QString string(QChar(0xfeff)); // BYTE ORDER MARK + QTextLayout layout(string); + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(INT_MAX / 256); + layout.endLayout(); + + // Don't spin into an infinite loop + } + +void tst_QTextLayout::glyphLessItems() +{ + { + QTextLayout layout; + layout.setText("\t\t"); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + } + + { + QTextLayout layout; + layout.setText(QString::fromLatin1("AA") + QChar(QChar::LineSeparator)); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + } +} + +void tst_QTextLayout::textWidthVsWIdth() +{ + QTextLayout layout; + QTextOption opt; + opt.setWrapMode(QTextOption::WrapAnywhere); + layout.setTextOption(opt); + layout.setText(QString::fromLatin1( + "g++ -c -m64 -pipe -g -fvisibility=hidden -fvisibility-inlines-hidden -Wall -W -D_REENTRANT -fPIC -DCORE_LIBRARY -DIDE_LIBRARY_BASENAME=\"lib\" -DWITH_TESTS " + "-DQT_NO_CAST_TO_ASCII -DQT_USE_FAST_OPERATOR_PLUS -DQT_USE_FAST_CONCATENATION -DQT_PLUGIN -DQT_TESTLIB_LIB -DQT_SCRIPT_LIB -DQT_SVG_LIB -DQT_SQL_LIB -DQT_XM" + "L_LIB -DQT_GUI_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_SHARED -I../../../../qt-qml/mkspecs/linux-g++-64 -I. -I../../../../qt-qml/include/QtCore -I../../../." + "./qt-qml/include/QtNetwork -I../../../../qt-qml/include/QtGui -I../../../../qt-qml/include/QtXml -I../../../../qt-qml/include/QtSql -I../../../../qt-qml/inc" + "lude/QtSvg -I../../../../qt-qml/include/QtScript -I../../../../qt-qml/include/QtTest -I../../../../qt-qml/include -I../../../../qt-qml/include/QtHelp -I../." + "./libs -I/home/ettrich/dev/creator/tools -I../../plugins -I../../shared/scriptwrapper -I../../libs/3rdparty/botan/build -Idialogs -Iactionmanager -Ieditorma" + "nager -Iprogressmanager -Iscriptmanager -I.moc/debug-shared -I.uic -o .obj/debug-shared/sidebar.o sidebar.cpp")); + + // textWidth includes right bearing, but it should never be LARGER than width if there is space for at least one character + for (int width = 100; width < 1000; ++width) { + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(width); + layout.endLayout(); + + qreal textWidthIsLargerBy = qMax(qreal(0), line.naturalTextWidth() - line.width()); + qreal thisMustBeZero = 0; + QCOMPARE(textWidthIsLargerBy, thisMustBeZero); + } +} + +void tst_QTextLayout::textWithSurrogates_qtbug15679() +{ + QString str = QString::fromUtf8("🀀a🀀"); + QTextLayout layout(str); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + qreal x[6]; + for (int i = 0; i < 6; i++) + x[i] = line.cursorToX(i); + + // If the first and third character are using the same + // font, they must have the same advance (since they + // are surrogate pairs, we need to add two for each + // character) + QCOMPARE(x[2] - x[0], x[5] - x[3]); +} + +void tst_QTextLayout::textWidthWithStackedTextEngine() +{ + QString text = QString::fromUtf8("คลิก ถัดไป เพื่อดำเนินการต่อ"); + QTextLayout layout(text); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + QFontMetricsF fm(layout.font()); + QCOMPARE(line.naturalTextWidth(), fm.width(text)); +} + +void tst_QTextLayout::textWidthWithLineSeparator() +{ + QString s1("Save Project"), s2("Save Project\ntest"); + s2.replace('\n', QChar::LineSeparator); + + QTextLayout layout1(s1), layout2(s2); + layout1.beginLayout(); + layout2.beginLayout(); + + QTextLine line1 = layout1.createLine(); + QTextLine line2 = layout2.createLine(); + line1.setLineWidth(0x1000); + line2.setLineWidth(0x1000); + QCOMPARE(line1.naturalTextWidth(), line2.naturalTextWidth()); +} + +void tst_QTextLayout::cursorInLigatureWithMultipleLines() +{ +#if !defined(Q_WS_MAC) + QSKIP("This test can not be run on Mac", SkipAll); +#endif + QTextLayout layout("first line finish", QFont("Times", 20)); + layout.beginLayout(); + QTextLine line = layout.createLine(); + line.setLineWidth(70); + line = layout.createLine(); + layout.endLayout(); + + // The second line will be "finish", with "fi" as a ligature + QVERIFY(line.cursorToX(0) != line.cursorToX(1)); +} + +void tst_QTextLayout::xToCursorForLigatures() +{ +#if !defined(Q_WS_MAC) + QSKIP("This test can not be run on Mac", SkipAll); +#endif + QTextLayout layout("fi", QFont("Times", 20)); + layout.beginLayout(); + QTextLine line = layout.createLine(); + layout.endLayout(); + + QVERIFY(line.xToCursor(0) != line.xToCursor(line.naturalTextWidth() / 2)); + + // U+0061 U+0308 + QTextLayout layout2(QString::fromUtf8("\x61\xCC\x88"), QFont("Times", 20)); + + layout2.beginLayout(); + line = layout2.createLine(); + layout2.endLayout(); + + qreal width = line.naturalTextWidth(); + QVERIFY(line.xToCursor(0) == line.xToCursor(width / 2) || + line.xToCursor(width) == line.xToCursor(width / 2)); +} + +QTEST_MAIN(tst_QTextLayout) +#include "tst_qtextlayout.moc" diff --git a/tests/auto/gui/text/qtextlist/.gitignore b/tests/auto/gui/text/qtextlist/.gitignore new file mode 100644 index 0000000000..f1c4f6acab --- /dev/null +++ b/tests/auto/gui/text/qtextlist/.gitignore @@ -0,0 +1 @@ +tst_qtextlist diff --git a/tests/auto/gui/text/qtextlist/qtextlist.pro b/tests/auto/gui/text/qtextlist/qtextlist.pro new file mode 100644 index 0000000000..f66fb96dd0 --- /dev/null +++ b/tests/auto/gui/text/qtextlist/qtextlist.pro @@ -0,0 +1,9 @@ +load(qttest_p4) + +QT += core-private gui-private + +SOURCES += tst_qtextlist.cpp +HEADERS += ../qtextdocument/common.h + + + diff --git a/tests/auto/gui/text/qtextlist/tst_qtextlist.cpp b/tests/auto/gui/text/qtextlist/tst_qtextlist.cpp new file mode 100644 index 0000000000..164dd6fc68 --- /dev/null +++ b/tests/auto/gui/text/qtextlist/tst_qtextlist.cpp @@ -0,0 +1,453 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <qtextdocument.h> +#include <qtextdocumentfragment.h> +#include <qtextlist.h> +#include <qabstracttextdocumentlayout.h> +#include <qtextcursor.h> +#include "../qtextdocument/common.h" + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QTextList : public QObject +{ + Q_OBJECT + +public: + tst_QTextList(); + + +public slots: + void init(); + void cleanup(); +private slots: + void item(); + void autoNumbering(); + void autoNumberingRTL(); + void autoNumberingPrefixAndSuffix(); + void autoNumberingPrefixAndSuffixRTL(); + void autoNumberingPrefixAndSuffixHtmlExportImport(); + void romanNumbering(); + void romanNumberingLimit(); + void formatChange(); + void cursorNavigation(); + void partialRemoval(); + void formatReferenceChange(); + void ensureItemOrder(); + void add(); + void defaultIndent(); + void blockUpdate(); + void numbering_data(); + void numbering(); + +private: + QTextDocument *doc; + QTextCursor cursor; + QTestDocumentLayout *layout; +}; + +tst_QTextList::tst_QTextList() +{} + +void tst_QTextList::init() +{ + doc = new QTextDocument(); + layout = new QTestDocumentLayout(doc); + doc->setDocumentLayout(layout); + cursor = QTextCursor(doc); +} + +void tst_QTextList::cleanup() +{ + cursor = QTextCursor(); + delete doc; + doc = 0; +} + +void tst_QTextList::item() +{ + // this is basically a test for the key() + 1 in QTextList::item. + QTextList *list = cursor.createList(QTextListFormat()); + QVERIFY(list->item(0).blockFormat().objectIndex() != -1); +} + +void tst_QTextList::autoNumbering() +{ + QTextListFormat fmt; + fmt.setStyle(QTextListFormat::ListLowerAlpha); + QTextList *list = cursor.createList(fmt); + QVERIFY(list); + + for (int i = 0; i < 27; ++i) + cursor.insertBlock(); + + QVERIFY(list->count() == 28); + + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->itemNumber(cursor.block()) == 27); + QVERIFY(cursor.currentList()->itemText(cursor.block()) == "ab."); +} + +void tst_QTextList::autoNumberingPrefixAndSuffix() +{ + QTextListFormat fmt; + fmt.setStyle(QTextListFormat::ListLowerAlpha); + fmt.setNumberPrefix("-"); + fmt.setNumberSuffix(")"); + QTextList *list = cursor.createList(fmt); + QVERIFY(list); + + for (int i = 0; i < 27; ++i) + cursor.insertBlock(); + + QVERIFY(list->count() == 28); + + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->itemNumber(cursor.block()) == 27); + QVERIFY(cursor.currentList()->itemText(cursor.block()) == "-ab)"); +} + +void tst_QTextList::autoNumberingPrefixAndSuffixRTL() +{ + QTextBlockFormat bfmt; + bfmt.setLayoutDirection(Qt::RightToLeft); + cursor.setBlockFormat(bfmt); + + QTextListFormat fmt; + fmt.setStyle(QTextListFormat::ListUpperAlpha); + fmt.setNumberPrefix("-"); + fmt.setNumberSuffix("*"); + QTextList *list = cursor.createList(fmt); + QVERIFY(list); + + cursor.insertBlock(); + + QVERIFY(list->count() == 2); + + QVERIFY(cursor.currentList()->itemText(cursor.block()) == "*B-"); +} + +void tst_QTextList::autoNumberingPrefixAndSuffixHtmlExportImport() +{ + QTextListFormat fmt; + fmt.setStyle(QTextListFormat::ListLowerAlpha); + fmt.setNumberPrefix("\""); + fmt.setNumberSuffix("#"); + fmt.setIndent(10); + // FIXME: Would like to test "'" but there's a problem in the css parser (Scanner::preprocess + // is called before the values are being parsed), so the quoting does not work. + QTextList *list = cursor.createList(fmt); + QVERIFY(list); + + for (int i = 0; i < 27; ++i) + cursor.insertBlock(); + + QVERIFY(list->count() == 28); + + QString htmlExport = doc->toHtml(); + QTextDocument importDoc; + importDoc.setHtml(htmlExport); + + QTextCursor importCursor(&importDoc); + for (int i = 0; i < 27; ++i) + importCursor.movePosition(QTextCursor::NextBlock); + + QVERIFY(importCursor.currentList()); + QVERIFY(importCursor.currentList()->itemNumber(importCursor.block()) == 27); + QVERIFY(importCursor.currentList()->itemText(importCursor.block()) == "\"ab#"); + QVERIFY(importCursor.currentList()->format().indent() == 10); +} + +void tst_QTextList::autoNumberingRTL() +{ + QTextBlockFormat bfmt; + bfmt.setLayoutDirection(Qt::RightToLeft); + cursor.setBlockFormat(bfmt); + + QTextListFormat fmt; + fmt.setStyle(QTextListFormat::ListUpperAlpha); + QTextList *list = cursor.createList(fmt); + QVERIFY(list); + + cursor.insertBlock(); + + QVERIFY(list->count() == 2); + + QVERIFY(cursor.currentList()->itemText(cursor.block()) == ".B"); +} + +void tst_QTextList::romanNumbering() +{ + QTextListFormat fmt; + fmt.setStyle(QTextListFormat::ListUpperRoman); + QTextList *list = cursor.createList(fmt); + QVERIFY(list); + + for (int i = 0; i < 4998; ++i) + cursor.insertBlock(); + + QVERIFY(list->count() == 4999); + + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->itemNumber(cursor.block()) == 4998); + QVERIFY(cursor.currentList()->itemText(cursor.block()) == "MMMMCMXCIX."); +} + +void tst_QTextList::romanNumberingLimit() +{ + QTextListFormat fmt; + fmt.setStyle(QTextListFormat::ListLowerRoman); + QTextList *list = cursor.createList(fmt); + QVERIFY(list); + + for (int i = 0; i < 4999; ++i) + cursor.insertBlock(); + + QVERIFY(list->count() == 5000); + + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->itemNumber(cursor.block()) == 4999); + QVERIFY(cursor.currentList()->itemText(cursor.block()) == "?."); +} + +void tst_QTextList::formatChange() +{ + // testing the formatChanged slot in QTextListManager + + /* <initial block> + * 1. + * 2. + */ + QTextList *list = cursor.insertList(QTextListFormat::ListDecimal); + QTextList *firstList = list; + cursor.insertBlock(); + + QVERIFY(list && list->count() == 2); + + QTextBlockFormat bfmt = cursor.blockFormat(); +// QVERIFY(bfmt.object() == list); + + bfmt.setObjectIndex(-1); + cursor.setBlockFormat(bfmt); + + QVERIFY(firstList->count() == 1); +} + +void tst_QTextList::cursorNavigation() +{ + // testing some cursor list methods + + /* <initial block> + * 1. + * 2. + */ + cursor.insertList(QTextListFormat::ListDecimal); + cursor.insertBlock(); + + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.currentList()); + cursor.movePosition(QTextCursor::PreviousBlock); + QVERIFY(cursor.currentList()); + QVERIFY(cursor.currentList()->itemNumber(cursor.block()) == 0); +} + +void tst_QTextList::partialRemoval() +{ + /* this is essentially a test for PieceTable::removeBlock to not miss any + blocks with the blockChanged signal emission that actually get removed. + + It creates two lists, like this: + + 1. Hello World + a. Foobar + + and then removes from within the 'Hello World' into the 'Foobar' . + There used to be no emission for the removal of the second (a.) block, + causing list inconsistencies. + + */ + + QTextList *firstList = cursor.insertList(QTextListFormat::ListDecimal); + + QTextCursor selStart = cursor; + selStart.movePosition(QTextCursor::PreviousCharacter); + + cursor.insertText("Hello World"); + + // position it well into the 'hello world' text. + selStart.movePosition(QTextCursor::NextCharacter); + selStart.movePosition(QTextCursor::NextCharacter); + selStart.clearSelection(); + + QPointer<QTextList> secondList = cursor.insertList(QTextListFormat::ListCircle); + cursor.insertText("Foobar"); + + // position it into the 'foo bar' text. + cursor.movePosition(QTextCursor::PreviousCharacter); + QTextCursor selEnd = cursor; + + // this creates a selection that includes parts of both text-fragments and also the list item of the second list. + QTextCursor selection = selStart; + selection.setPosition(selEnd.position(), QTextCursor::KeepAnchor); + + selection.deleteChar(); // deletes the second list + + QVERIFY(!secondList); + QVERIFY(!firstList->isEmpty()); + + doc->undo(); +} + +void tst_QTextList::formatReferenceChange() +{ + QTextList *list = cursor.insertList(QTextListFormat::ListDecimal); + cursor.insertText("Some Content..."); + cursor.insertBlock(QTextBlockFormat()); + + cursor.setPosition(list->item(0).position()); + int listItemStartPos = cursor.position(); + cursor.movePosition(QTextCursor::NextBlock); + int listItemLen = cursor.position() - listItemStartPos; + layout->expect(listItemStartPos, listItemLen, listItemLen); + + QTextListFormat fmt = list->format(); + fmt.setStyle(QTextListFormat::ListCircle); + list->setFormat(fmt); + + QVERIFY(layout->called); + QVERIFY(!layout->error); +} + +void tst_QTextList::ensureItemOrder() +{ + /* + * Insert a new list item before the first one and verify the blocks + * are sorted after that. + */ + QTextList *list = cursor.insertList(QTextListFormat::ListDecimal); + + QTextBlockFormat fmt = cursor.blockFormat(); + cursor.movePosition(QTextCursor::Start); + cursor.insertBlock(fmt); + + QCOMPARE(list->item(0).position(), 1); + QCOMPARE(list->item(1).position(), 2); +} + +void tst_QTextList::add() +{ + QTextList *list = cursor.insertList(QTextListFormat::ListDecimal); + cursor.insertBlock(QTextBlockFormat()); + QCOMPARE(list->count(), 1); + cursor.insertBlock(QTextBlockFormat()); + list->add(cursor.block()); + QCOMPARE(list->count(), 2); +} + +// Task #72036 +void tst_QTextList::defaultIndent() +{ + QTextListFormat fmt; + QCOMPARE(fmt.indent(), 1); +} + +void tst_QTextList::blockUpdate() +{ + // three items + QTextList *list = cursor.insertList(QTextListFormat::ListDecimal); + cursor.insertBlock(); + cursor.insertBlock(); + + // remove second, needs also update on the third + // since the numbering might have changed + const int len = cursor.position() + cursor.block().length() - 1; + layout->expect(1, len, len); + list->remove(list->item(1)); + QVERIFY(!layout->error); +} + +void tst_QTextList::numbering_data() +{ + QTest::addColumn<int>("format"); + QTest::addColumn<int>("number"); + QTest::addColumn<QString>("result"); + + QTest::newRow("E.") << int(QTextListFormat::ListUpperAlpha) << 5 << "E."; + QTest::newRow("abc.") << int(QTextListFormat::ListLowerAlpha) << (26 + 2) * 26 + 3 << "abc."; + QTest::newRow("12.") << int(QTextListFormat::ListDecimal) << 12 << "12."; + QTest::newRow("XXIV.") << int(QTextListFormat::ListUpperRoman) << 24 << "XXIV."; + QTest::newRow("VIII.") << int(QTextListFormat::ListUpperRoman) << 8 << "VIII."; + QTest::newRow("xxx.") << int(QTextListFormat::ListLowerRoman) << 30 << "xxx."; + QTest::newRow("xxix.") << int(QTextListFormat::ListLowerRoman) << 29 << "xxix."; +// QTest::newRow("xxx. alpha") << int(QTextListFormat::ListLowerAlpha) << (24 * 26 + 24) * 26 + 24 << "xxx."; //Too slow +} + +void tst_QTextList::numbering() +{ + QFETCH(int, format); + QFETCH(int, number); + QFETCH(QString, result); + + + QTextListFormat fmt; + fmt.setStyle(QTextListFormat::Style(format)); + QTextList *list = cursor.createList(fmt); + QVERIFY(list); + + for (int i = 1; i < number; ++i) + cursor.insertBlock(); + + QCOMPARE(list->count(), number); + + QVERIFY(cursor.currentList()); + QCOMPARE(cursor.currentList()->itemNumber(cursor.block()), number - 1); + QCOMPARE(cursor.currentList()->itemText(cursor.block()), result); +} + +QTEST_MAIN(tst_QTextList) +#include "tst_qtextlist.moc" diff --git a/tests/auto/gui/text/qtextobject/.gitignore b/tests/auto/gui/text/qtextobject/.gitignore new file mode 100644 index 0000000000..6a3da09549 --- /dev/null +++ b/tests/auto/gui/text/qtextobject/.gitignore @@ -0,0 +1 @@ +tst_qtextobject diff --git a/tests/auto/gui/text/qtextobject/qtextobject.pro b/tests/auto/gui/text/qtextobject/qtextobject.pro new file mode 100644 index 0000000000..e87a364ab6 --- /dev/null +++ b/tests/auto/gui/text/qtextobject/qtextobject.pro @@ -0,0 +1,9 @@ +############################################################ +# Project file for autotest for file qtextobject.h +############################################################ + +load(qttest_p4) +QT += widgets +SOURCES += tst_qtextobject.cpp + + diff --git a/tests/auto/gui/text/qtextobject/tst_qtextobject.cpp b/tests/auto/gui/text/qtextobject/tst_qtextobject.cpp new file mode 100644 index 0000000000..0d1773e3fa --- /dev/null +++ b/tests/auto/gui/text/qtextobject/tst_qtextobject.cpp @@ -0,0 +1,128 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#include <qcoreapplication.h> +#include <qdebug.h> +#include <qtextobject.h> +#include <qtextdocument.h> +#include <qtextedit.h> +#include <qtextcursor.h> + +//TESTED_CLASS= +//TESTED_FILES= + +class tst_QTextObject : public QObject +{ +Q_OBJECT + +public: + tst_QTextObject(); + virtual ~tst_QTextObject(); + +private slots: + void getSetCheck(); + void testStandAloneTextObject(); +}; + +tst_QTextObject::tst_QTextObject() +{ +} + +tst_QTextObject::~tst_QTextObject() +{ +} + +// Testing get/set functions +void tst_QTextObject::getSetCheck() +{ + QTextEdit edit; + QTextFrame obj1(edit.document()); + // QTextFrameLayoutData * QTextFrame::layoutData() + // void QTextFrame::setLayoutData(QTextFrameLayoutData *) + QTextFrameLayoutData *var1 = new QTextFrameLayoutData; + obj1.setLayoutData(var1); + QCOMPARE(var1, obj1.layoutData()); + obj1.setLayoutData((QTextFrameLayoutData *)0); + QCOMPARE((QTextFrameLayoutData *)0, obj1.layoutData()); + // delete var1; // No delete, since QTextFrame takes ownership + + QTextBlock obj2 = edit.textCursor().block(); + // QTextBlockUserData * QTextBlock::userData() + // void QTextBlock::setUserData(QTextBlockUserData *) + QTextBlockUserData *var2 = new QTextBlockUserData; + obj2.setUserData(var2); + QCOMPARE(var2, obj2.userData()); + obj2.setUserData((QTextBlockUserData *)0); + QCOMPARE((QTextBlockUserData *)0, obj2.userData()); + + // int QTextBlock::userState() + // void QTextBlock::setUserState(int) + obj2.setUserState(0); + QCOMPARE(0, obj2.userState()); + obj2.setUserState(INT_MIN); + QCOMPARE(INT_MIN, obj2.userState()); + obj2.setUserState(INT_MAX); + QCOMPARE(INT_MAX, obj2.userState()); +} + +class TestTextObject : public QTextObject +{ +public: + TestTextObject(QTextDocument *document) : QTextObject(document) {} +}; + +void tst_QTextObject::testStandAloneTextObject() +{ + QTextDocument document; + TestTextObject textObject(&document); + + QCOMPARE(textObject.document(), &document); + // don't crash + textObject.format(); + textObject.formatIndex(); + QCOMPARE(textObject.objectIndex(), -1); +} + +QTEST_MAIN(tst_QTextObject) +#include "tst_qtextobject.moc" diff --git a/tests/auto/gui/text/qtextodfwriter/.gitignore b/tests/auto/gui/text/qtextodfwriter/.gitignore new file mode 100644 index 0000000000..791445d7a9 --- /dev/null +++ b/tests/auto/gui/text/qtextodfwriter/.gitignore @@ -0,0 +1 @@ +tst_qtextodfwriter diff --git a/tests/auto/gui/text/qtextodfwriter/qtextodfwriter.pro b/tests/auto/gui/text/qtextodfwriter/qtextodfwriter.pro new file mode 100644 index 0000000000..25bb5a5ed0 --- /dev/null +++ b/tests/auto/gui/text/qtextodfwriter/qtextodfwriter.pro @@ -0,0 +1,6 @@ +load(qttest_p4) +QT += core-private gui-private +SOURCES += tst_qtextodfwriter.cpp + +!symbian:DEFINES += SRCDIR=\\\"$$PWD\\\" +symbian:INCLUDEPATH+=$$[QT_INSTALL_PREFIX]/include/QtGui/private diff --git a/tests/auto/gui/text/qtextodfwriter/tst_qtextodfwriter.cpp b/tests/auto/gui/text/qtextodfwriter/tst_qtextodfwriter.cpp new file mode 100644 index 0000000000..765afce66e --- /dev/null +++ b/tests/auto/gui/text/qtextodfwriter/tst_qtextodfwriter.cpp @@ -0,0 +1,426 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include <QtTest/QtTest> +#include <QTextDocument> +#include <QTextCursor> +#include <QTextBlock> +#include <QTextList> +#include <QTextTable> +#include <QBuffer> +#include <QDebug> + +#ifdef Q_OS_SYMBIAN +#define SRCDIR "." +#endif + +#include <private/qtextodfwriter_p.h> + +class tst_QTextOdfWriter : public QObject +{ + Q_OBJECT +public slots: + void init(); + void cleanup(); + +private slots: + void testWriteParagraph_data(); + void testWriteParagraph(); + void testWriteStyle1_data(); + void testWriteStyle1(); + void testWriteStyle2(); + void testWriteList(); + void testWriteList2(); + void createArchive(); + void testWriteAll(); + void testWriteSection(); + void testWriteTable(); + +private: + /// closes the document and returns the part of the XML stream that the test wrote + QString getContentFromXml(); + +private: + QTextDocument *document; + QXmlStreamWriter *xmlWriter; + QTextOdfWriter *odfWriter; + QBuffer *buffer; +}; + +void tst_QTextOdfWriter::init() +{ + document = new QTextDocument(); + odfWriter = new QTextOdfWriter(*document, 0); + + buffer = new QBuffer(); + buffer->open(QIODevice::WriteOnly); + xmlWriter = new QXmlStreamWriter(buffer); + xmlWriter->writeNamespace(odfWriter->officeNS, "office"); + xmlWriter->writeNamespace(odfWriter->textNS, "text"); + xmlWriter->writeNamespace(odfWriter->styleNS, "style"); + xmlWriter->writeNamespace(odfWriter->foNS, "fo"); + xmlWriter->writeNamespace(odfWriter->tableNS, "table"); + xmlWriter->writeStartDocument(); + xmlWriter->writeStartElement("dummy"); +} + +void tst_QTextOdfWriter::cleanup() +{ + delete document; + delete odfWriter; + delete xmlWriter; + delete buffer; +} + +QString tst_QTextOdfWriter::getContentFromXml() +{ + xmlWriter->writeEndDocument(); + buffer->close(); + QString stringContent = QString::fromUtf8(buffer->data()); + QString ret; + int index = stringContent.indexOf("<dummy"); + if (index > 0) { + index = stringContent.indexOf('>', index); + if (index > 0) + ret = stringContent.mid(index+1, stringContent.length() - index - 10); + } + return ret; +} + +void tst_QTextOdfWriter::testWriteParagraph_data() +{ + QTest::addColumn<QString>("input"); + QTest::addColumn<QString>("xml"); + + QTest::newRow("empty") << "" << + "<text:p text:style-name=\"p1\"/>"; + QTest::newRow("spaces") << "foobar word" << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">foobar <text:s text:c=\"2\"/>word</text:span></text:p>"; + QTest::newRow("starting spaces") << " starting spaces" << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\"><text:s text:c=\"2\"/>starting spaces</text:span></text:p>"; + QTest::newRow("trailing spaces") << "trailing spaces " << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">trailing spaces <text:s/></text:span></text:p>"; + QTest::newRow("tab") << "word\ttab x" << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">word<text:tab/>tab x</text:span></text:p>"; + QTest::newRow("tab2") << "word\t\ttab\tx" << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">word<text:tab/><text:tab/>tab<text:tab/>x</text:span></text:p>"; + QTest::newRow("misc") << "foobar word\ttab x" << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">foobar <text:s text:c=\"2\"/>word<text:tab/>tab x</text:span></text:p>"; + QTest::newRow("misc2") << "\t \tFoo" << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\"><text:tab/> <text:s text:c=\"4\"/><text:tab/>Foo</text:span></text:p>"; + QTest::newRow("linefeed") << QString("line1%1line2").arg(QChar(0x2028)) << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">line1<text:line-break/>line2</text:span></text:p>"; + QTest::newRow("spaces") << "The quick brown fox jumped over the lazy dog" << + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">The quick brown fox jumped over the lazy dog</text:span></text:p>"; +} + +void tst_QTextOdfWriter::testWriteParagraph() +{ + QFETCH(QString, input); + QFETCH(QString, xml); + + QTextCursor cursor(document); + cursor.insertText(input); + + odfWriter->writeBlock(*xmlWriter, document->begin()); + QCOMPARE( getContentFromXml(), xml); +} + +void tst_QTextOdfWriter::testWriteStyle1_data() +{ + QTest::addColumn<QString>("htmlInput"); + QTest::addColumn<int>("cursorPosition"); + QTest::addColumn<QString>("xml"); + + QString text1 = "Normal<b>bold</b><i>italic</i><b><i>Bold/Italic</i></b>"; + QTest::newRow("normal") << text1 << 2 << + "<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-family=\"Sans\"/></style:style>"; + QTest::newRow("bold") << text1 << 10 << + "<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-weight=\"bold\" fo:font-family=\"Sans\"/></style:style>"; + QTest::newRow("italic") << text1 << 14 << + "<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-style=\"italic\" fo:font-family=\"Sans\"/></style:style>"; + QTest::newRow("bold+italic") << text1 << 25 << + "<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-style=\"italic\" fo:font-weight=\"bold\" fo:font-family=\"Sans\"/></style:style>"; + QString colorText = "<span style=\"color: #00FF00; background-color: #FF0000;\"> Color Text </span>"; + QTest::newRow("green/red") << colorText << 3 << + "<style:style style:name=\"c4\" style:family=\"text\"><style:text-properties fo:font-family=\"Sans\" fo:color=\"#00ff00\" fo:background-color=\"#ff0000\"/></style:style>"; + +} + +void tst_QTextOdfWriter::testWriteStyle1() +{ + QFETCH(QString, htmlInput); + QFETCH(int, cursorPosition); + QFETCH(QString, xml); + document->setHtml(htmlInput); + + QTextCursor cursor(document); + cursor.setPosition(cursorPosition); + odfWriter->writeCharacterFormat(*xmlWriter, cursor.charFormat(), 4); + QCOMPARE( getContentFromXml(), xml); +} + +void tst_QTextOdfWriter::testWriteStyle2() +{ + QTextBlockFormat bf; // = cursor.blockFormat(); + QList<QTextOption::Tab> tabs; + QTextOption::Tab tab1(40, QTextOption::RightTab); + tabs << tab1; + QTextOption::Tab tab2(80, QTextOption::DelimiterTab, 'o'); + tabs << tab2; + bf.setTabPositions(tabs); + + odfWriter->writeBlockFormat(*xmlWriter, bf, 1); + QString xml = QString::fromLatin1( + "<style:style style:name=\"p1\" style:family=\"paragraph\">" + "<style:paragraph-properties>" + "<style:tab-stops>" + "<style:tab-stop style:position=\"30pt\" style:type=\"right\"/>" + "<style:tab-stop style:position=\"60pt\" style:type=\"char\" style:char=\"o\"/>" + "</style:tab-stops>" + "</style:paragraph-properties>" + "</style:style>"); + QCOMPARE(getContentFromXml(), xml); +} + +void tst_QTextOdfWriter::testWriteList() +{ + QTextCursor cursor(document); + QTextList *list = cursor.createList(QTextListFormat::ListDisc); + cursor.insertText("ListItem 1"); + list->add(cursor.block()); + cursor.insertBlock(); + cursor.insertText("ListItem 2"); + list->add(cursor.block()); + + odfWriter->writeBlock(*xmlWriter, cursor.block()); + QString xml = QString::fromLatin1( + "<text:list text:style-name=\"L2\">" + "<text:list-item>" + //"<text:numbered-paragraph text:style-name=\"L2\" text:level=\"1\">" + //"<text:number>")+ QChar(0x25cf) + QString::fromLatin1("</text:number>" // 0x25cf is a bullet + "<text:p text:style-name=\"p3\"><text:span text:style-name=\"c0\">ListItem 2</text:span></text:p>" + "</text:list-item>" + "</text:list>"); + + QCOMPARE(getContentFromXml(), xml); +} + +void tst_QTextOdfWriter::testWriteList2() +{ + QTextCursor cursor(document); + QTextList *list = cursor.createList(QTextListFormat::ListDisc); + cursor.insertText("Cars"); + list->add(cursor.block()); + cursor.insertBlock(); + QTextListFormat level2; + level2.setStyle(QTextListFormat::ListSquare); + level2.setIndent(2); + QTextList *list2 = cursor.createList(level2); + cursor.insertText("Model T"); + list2->add(cursor.block()); + cursor.insertBlock(); + cursor.insertText("Kitt"); + list2->add(cursor.block()); + cursor.insertBlock(); + cursor.insertText("Animals"); + list->add(cursor.block()); + + cursor.insertBlock(QTextBlockFormat(), QTextCharFormat()); // start a new completely unrelated list. + QTextList *list3 = cursor.createList(QTextListFormat::ListDecimal); + cursor.insertText("Foo"); + list3->add(cursor.block()); + + // and another block thats NOT in a list. + cursor.insertBlock(QTextBlockFormat(), QTextCharFormat()); + cursor.insertText("Bar"); + + odfWriter->writeFrame(*xmlWriter, document->rootFrame()); + QString xml = QString::fromLatin1( + "<text:list text:style-name=\"L2\">" + "<text:list-item>" + //"<text:numbered-paragraph text:style-name=\"L2\" text:level=\"1\">" + //"<text:number>")+ QChar(0x25cf) + QString::fromLatin1("</text:number>" // 0x25cf is a bullet + "<text:p text:style-name=\"p3\"><text:span text:style-name=\"c0\">Cars</text:span></text:p>" + "</text:list-item>" + "<text:list-item>" + "<text:list text:style-name=\"L4\">" + "<text:list-item>" + "<text:p text:style-name=\"p5\"><text:span text:style-name=\"c0\">Model T</text:span></text:p>" + "</text:list-item>" + "<text:list-item>" + "<text:p text:style-name=\"p5\"><text:span text:style-name=\"c0\">Kitt</text:span></text:p>" + "</text:list-item>" + "</text:list>" + "</text:list-item>" + "<text:list-item>" + "<text:p text:style-name=\"p3\"><text:span text:style-name=\"c0\">Animals</text:span></text:p>" + "</text:list-item>" + "</text:list>" + "<text:list text:style-name=\"L6\">" + "<text:list-item>" + "<text:p text:style-name=\"p7\"><text:span text:style-name=\"c0\">Foo</text:span></text:p>" + "</text:list-item>" + "</text:list>" + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">Bar</text:span></text:p>"); + + // QString x = getContentFromXml(); + // for (int i=0; i < x.length(); i+=150) qDebug() << x.mid(i, 150); + QCOMPARE(getContentFromXml(), xml); +} + + +void tst_QTextOdfWriter::createArchive() +{ + document->setPlainText("a"); // simple doc is enough ;) + QTextOdfWriter writer(*document, buffer); + QCOMPARE(writer.createArchive(), true); // default + writer.writeAll(); +/* +QFile file("createArchive-odt"); +file.open(QIODevice::WriteOnly); +file.write(buffer->data()); +file.close(); +*/ + QVERIFY(buffer->data().length() > 80); + QCOMPARE(buffer->data()[0], 'P'); // its a zip :) + QCOMPARE(buffer->data()[1], 'K'); + QString mimetype(buffer->data().mid(38, 39)); + QCOMPARE(mimetype, QString::fromLatin1("application/vnd.oasis.opendocument.text")); +} + +void tst_QTextOdfWriter::testWriteAll() +{ + document->setPlainText("a"); // simple doc is enough ;) + QTextOdfWriter writer(*document, buffer); + QCOMPARE(writer.createArchive(), true); + writer.setCreateArchive(false); + writer.writeAll(); + QString result = QString(buffer->data()); + // details we check elsewhere, all we have to do is check availability. + QVERIFY(result.indexOf("office:automatic-styles") >= 0); + QVERIFY(result.indexOf("<style:style style:name=\"p1\"") >= 0); + QVERIFY(result.indexOf("<style:style style:name=\"c0\"") >= 0); + QVERIFY(result.indexOf("office:body") >= 0); + QVERIFY(result.indexOf("office:text") >= 0); + QVERIFY(result.indexOf("style:style") >= 0); +} + +void tst_QTextOdfWriter::testWriteSection() +{ + QTextCursor cursor(document); + cursor.insertText("foo\nBar"); + QTextFrameFormat ff; + cursor.insertFrame(ff); + cursor.insertText("baz"); + + odfWriter->writeFrame(*xmlWriter, document->rootFrame()); + QString xml = QString::fromLatin1( + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">foo</text:span></text:p>" + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">Bar</text:span></text:p>" + "<text:section>" + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">baz</text:span></text:p>" + "</text:section>" + "<text:p text:style-name=\"p1\"/>"); + + QCOMPARE(getContentFromXml(), xml); +} + +void tst_QTextOdfWriter::testWriteTable() +{ + // create table with merged cells + QTextCursor cursor(document); + QTextTable * table = cursor.insertTable(3, 3); + table->mergeCells(1, 0, 2, 2); + table->mergeCells(0, 1, 1, 2); + cursor = table->cellAt(0, 0).firstCursorPosition(); + cursor.insertText("a"); + cursor.movePosition(QTextCursor::NextCell); + cursor.insertText("b"); + cursor.movePosition(QTextCursor::NextCell); + cursor.insertText("c"); + cursor.movePosition(QTextCursor::NextCell); + cursor.insertText("d"); + cursor.movePosition(QTextCursor::NextCell); + cursor.insertText("e"); + /* + +-+---+ + |a|b | + +-+-+-+ + |c |d| + + +-+ + | |e| + +-+-+-+ + */ + + odfWriter->writeFrame(*xmlWriter, document->rootFrame()); + QString xml = QString::fromLatin1( + "<text:p text:style-name=\"p1\"/>" + "<table:table>" + "<table:table-column table:number-columns-repeated=\"3\"/>" + "<table:table-row>" + "<table:table-cell table:style-name=\"T3\">" + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">a</text:span></text:p>" + "</table:table-cell>" + "<table:table-cell table:number-columns-spanned=\"2\" table:style-name=\"T6\">" + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c7\">b</text:span></text:p>" + "</table:table-cell>" + "</table:table-row>" + "<table:table-row>" + "<table:table-cell table:number-columns-spanned=\"2\" table:number-rows-spanned=\"2\" table:style-name=\"T5\">" + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c8\">c</text:span></text:p>" + "</table:table-cell>" + "<table:table-cell table:style-name=\"T3\">" + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">d</text:span></text:p>" + "</table:table-cell>" + "</table:table-row>" + "<table:table-row>" + "<table:table-cell table:style-name=\"T3\">" + "<text:p text:style-name=\"p1\"><text:span text:style-name=\"c0\">e</text:span></text:p>" + "</table:table-cell>" + "</table:table-row>" + "</table:table>" + "<text:p text:style-name=\"p1\"/>"); + + QCOMPARE(getContentFromXml(), xml); +} + +QTEST_MAIN(tst_QTextOdfWriter) +#include "tst_qtextodfwriter.moc" diff --git a/tests/auto/gui/text/qtextpiecetable/.gitignore b/tests/auto/gui/text/qtextpiecetable/.gitignore new file mode 100644 index 0000000000..720b01f79f --- /dev/null +++ b/tests/auto/gui/text/qtextpiecetable/.gitignore @@ -0,0 +1 @@ +tst_qtextpiecetable diff --git a/tests/auto/gui/text/qtextpiecetable/qtextpiecetable.pro b/tests/auto/gui/text/qtextpiecetable/qtextpiecetable.pro new file mode 100644 index 0000000000..cfbbe2bca5 --- /dev/null +++ b/tests/auto/gui/text/qtextpiecetable/qtextpiecetable.pro @@ -0,0 +1,9 @@ +load(qttest_p4) +QT += widgets widgets-private +QT += core-private gui-private +SOURCES += tst_qtextpiecetable.cpp +HEADERS += ../qtextdocument/common.h + +requires(!win32) +requires(contains(QT_CONFIG,private_tests)) + diff --git a/tests/auto/gui/text/qtextpiecetable/tst_qtextpiecetable.cpp b/tests/auto/gui/text/qtextpiecetable/tst_qtextpiecetable.cpp new file mode 100644 index 0000000000..ddaf84134d --- /dev/null +++ b/tests/auto/gui/text/qtextpiecetable/tst_qtextpiecetable.cpp @@ -0,0 +1,1155 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + +#define protected public + +#include <qtextdocument.h> +#undef protected +#include <private/qtextdocument_p.h> +#include <qabstracttextdocumentlayout.h> +#include <qtextobject.h> +#include <qdebug.h> +#include <stdlib.h> +#include <qtextcursor.h> +#include "../qtextdocument/common.h" + +//TESTED_FILES=gui/text/qtextdocument_p.cpp gui/text/qtextdocument_p.h + +class tst_QTextPieceTable : public QObject +{ + Q_OBJECT + +public: + tst_QTextPieceTable(); + + +public slots: + void init(); + void cleanup(); +private slots: + void insertion1(); + void insertion2(); + void insertion3(); + void insertion4(); + void insertion5(); + + void removal1(); + void removal2(); + void removal3(); + void removal4(); + + void undoRedo1(); + void undoRedo2(); + void undoRedo3(); + void undoRedo4(); + void undoRedo5(); + void undoRedo6(); + void undoRedo7(); + void undoRedo8(); + void undoRedo9(); + void undoRedo10(); + void undoRedo11(); + + void checkDocumentChanged(); + void checkDocumentChanged2(); + void setBlockFormat(); + + void blockInsertion(); + void blockInsertion2(); + + void blockRemoval1(); + void blockRemoval2(); + void blockRemoval3(); + void blockRemoval4(); + void blockRemoval5(); + + void checkBlockSeparation(); + + void checkFrames1(); + void removeFrameDirect(); + void removeWithChildFrame(); + void clearWithFrames(); + +private: + QTextDocument *doc; + QTextDocumentPrivate *table; + int blockFormatIndex; + int charFormatIndex; +}; + +tst_QTextPieceTable::tst_QTextPieceTable() +{ doc = 0; table = 0; } + + +void tst_QTextPieceTable::init() +{ + doc = new QTextDocument(0); + table = doc->docHandle(); + blockFormatIndex = table->formatCollection()->indexForFormat(QTextBlockFormat()); + charFormatIndex = table->formatCollection()->indexForFormat(QTextCharFormat()); +} + +void tst_QTextPieceTable::cleanup() +{ + delete doc; + doc = 0; +} + +void tst_QTextPieceTable::insertion1() +{ + table->insert(0, "aacc", charFormatIndex); + QCOMPARE(table->plainText(), QString("aacc")); + table->insert(2, "bb", charFormatIndex); + QCOMPARE(table->plainText(), QString("aabbcc")); + table->insert(1, "1", charFormatIndex); + QCOMPARE(table->plainText(), QString("a1abbcc")); + table->insert(6, "d", charFormatIndex); + QCOMPARE(table->plainText(), QString("a1abbcdc")); + table->insert(8, "z", charFormatIndex); + QCOMPARE(table->plainText(), QString("a1abbcdcz")); +} + +void tst_QTextPieceTable::insertion2() +{ + table->insert(0, "bb", charFormatIndex); + QCOMPARE(table->plainText(), QString("bb")); +} + +void tst_QTextPieceTable::insertion3() +{ + QString compare; + for (int i = 0; i < 20000; ++i) { + int pos = rand() % (i+1); + QChar c((unsigned short)(i & 0xff) + 1); + QString str; + str += c; + table->insert(pos, str, charFormatIndex); + compare.insert(pos, str); + } + QVERIFY(table->plainText() == compare); +} + +void tst_QTextPieceTable::insertion4() +{ + QString compare; + for (int i = 0; i < 20000; ++i) { + int pos = rand() % (i+1); + QChar c((unsigned short)((i % 26) + (i>25?'A':'a'))); + QString str; + str += c; + str += c; + table->insert(pos, str, charFormatIndex); + compare.insert(pos, str); + // if (table->text() != compare) { + // qDebug("compare failed: i=%d (current char=%c) insert at %d\nexpected '%s'\ngot '%s'", i, (i % 26) + (i>25?'A':'a'), pos, compare.latin1(), table->text().latin1()); + // exit(12); + // } + } + QVERIFY(table->plainText() == compare); +} + +void tst_QTextPieceTable::insertion5() +{ + QString compare; + for (int i = 0; i < 20000; ++i) { + int pos = rand() % (i+1); + QChar c((unsigned short)((i % 26) + (i>25?'A':'a'))); + QString str; + str += c; + str += c; + if (c == 'a') { + table->insertBlock(pos, blockFormatIndex, charFormatIndex); + str = QChar(QChar::ParagraphSeparator); + } else { + table->insert(pos, str, charFormatIndex); + } + compare.insert(pos, str); + } + QVERIFY(table->plainText() == compare); + for (QTextBlock it = table->blocksBegin(); it != table->blocksEnd(); it = it.next()) { + QTextDocumentPrivate::FragmentIterator fit = table->find(it.position()); + QVERIFY(fit.position() == it.position()); + } +} + +void tst_QTextPieceTable::removal1() +{ + table->insert(0, "abbccc", charFormatIndex); + QCOMPARE(table->plainText(), QString("abbccc")); + table->remove(1, 2); + QCOMPARE(table->plainText(), QString("accc")); + table->insert(1, "1", charFormatIndex); + QCOMPARE(table->plainText(), QString("a1ccc")); + table->remove(4, 1); + QCOMPARE(table->plainText(), QString("a1cc")); + table->insert(4, "z", charFormatIndex); + QCOMPARE(table->plainText(), QString("a1ccz")); +} + +void tst_QTextPieceTable::removal2() +{ + table->insert(0, "bb", charFormatIndex); + QCOMPARE(table->plainText(), QString("bb")); + table->remove(0, 2); + QCOMPARE(table->plainText(), QString("")); + table->insertBlock(0, blockFormatIndex, charFormatIndex); + QCOMPARE(table->plainText(), QString(QChar(QChar::ParagraphSeparator))); + table->remove(0, 1); + QCOMPARE(table->plainText(), QString("")); + + table->insert(0, "bb", charFormatIndex); + QCOMPARE(table->plainText(), QString("bb")); + table->insertBlock(1, blockFormatIndex, charFormatIndex); + QCOMPARE(table->plainText(), QString("b") + QString(QChar(QChar::ParagraphSeparator)) + QString("b")); + table->remove(1, 1); + QCOMPARE(table->plainText(), QString("bb")); +} + +void tst_QTextPieceTable::removal3() +{ + QString compare; + int l = 0; + for (int i = 0; i < 20000; ++i) { + bool remove = l && (rand() % 2); + int pos = rand() % (remove ? l : (l+1)); + QChar c((unsigned short)((i % 26) + (i>25?'A':'a'))); + QString str; + str += c; + str += c; + if (remove && pos < table->length()) { + compare.remove(pos, 1); + table->remove(pos, 1); + } else { + compare.insert(pos, str); + table->insert(pos, str, charFormatIndex); + } + l += remove ? -1 : 2; + // if (table->text() != compare) { + // qDebug("compare failed: i=%d (current char=%c) insert at %d\nexpected '%s'\ngot '%s'", i, (i % 26) + (i>25?'A':'a'), pos, compare.latin1(), table->text().latin1()); + // exit(12); + // } + } + QVERIFY(table->plainText() == compare); +} + +void tst_QTextPieceTable::removal4() +{ + QString compare; + int l = 0; + for (int i = 0; i < 20000; ++i) { + bool remove = l && (rand() % 2); + int pos = (l > 1) ? rand() % (remove ? l-1 : l) : 0; + QChar c((unsigned short)((i % 26) + (i>25?'A':'a'))); + QString str; + if (c != 'a') { + str += c; + str += c; + } else { + str = QChar(QChar::ParagraphSeparator); + } + if (remove && pos < table->length() - 1) { + compare.remove(pos, 1); + table->remove(pos, 1); + } else { + if (str[0] == QChar(QChar::ParagraphSeparator)) + table->insertBlock(pos, blockFormatIndex, charFormatIndex); + else + table->insert(pos, str, charFormatIndex); + compare.insert(pos, str); + } + l += remove ? -1 : 2; +// if (table->plainText() != compare) { +// qDebug("compare failed: i=%d (current char=%c) insert at %d\nexpected '%s'\ngot '%s'", i, (i % 26) + (i>25?'A':'a'), pos, compare.latin1(), table->plainText().latin1()); +// exit(12); +// } + } + QVERIFY(table->plainText() == compare); +} + +void tst_QTextPieceTable::undoRedo1() +{ + table->insert(0, "01234567", charFormatIndex); + table->insert(0, "a", charFormatIndex); + table->insert(1, "b", charFormatIndex); + QCOMPARE(table->plainText(), QString("ab01234567")); + table->undo(); + QCOMPARE(table->plainText(), QString("01234567")); + table->redo(); + QCOMPARE(table->plainText(), QString("ab01234567")); + table->undo(); + table->insert(1, "c", charFormatIndex); + QCOMPARE(table->plainText(), QString("0c1234567")); + table->undo(); + QCOMPARE(table->plainText(), QString("01234567")); + table->undo(); + QVERIFY(table->plainText().isEmpty()); +} + +void tst_QTextPieceTable::undoRedo2() +{ + table->insert(0, "01", charFormatIndex); + table->insert(1, "a", charFormatIndex); + QCOMPARE(table->plainText(), QString("0a1")); + table->undo(); + QCOMPARE(table->plainText(), QString("01")); + table->undo(); + QCOMPARE(table->plainText(), QString("")); + table->redo(); + QCOMPARE(table->plainText(), QString("01")); + table->redo(); + QCOMPARE(table->plainText(), QString("0a1")); +} + +void tst_QTextPieceTable::undoRedo3() +{ + table->insert(0, "01", charFormatIndex); + table->insert(2, "ab", charFormatIndex); + table->remove(2, 1); + QCOMPARE(table->plainText(), QString("01b")); + table->undo(); + QCOMPARE(table->plainText(), QString("01ab")); + table->undo(); + QVERIFY(table->plainText().isEmpty()); + table->redo(); + QCOMPARE(table->plainText(), QString("01ab")); + table->redo(); + QCOMPARE(table->plainText(), QString("01b")); +} + +void tst_QTextPieceTable::undoRedo4() +{ + table->insert(0, "01", charFormatIndex); + table->insert(0, "ab", charFormatIndex); + table->remove(0, 1); + QCOMPARE(table->plainText(), QString("b01")); + table->undo(); + QCOMPARE(table->plainText(), QString("ab01")); + table->undo(); + QCOMPARE(table->plainText(), QString("01")); + table->undo(); + QCOMPARE(table->plainText(), QString("")); + table->redo(); + QCOMPARE(table->plainText(), QString("01")); + table->redo(); + QCOMPARE(table->plainText(), QString("ab01")); + table->redo(); + QCOMPARE(table->plainText(), QString("b01")); +} + +void tst_QTextPieceTable::undoRedo5() +{ + table->beginEditBlock(); + table->insert(0, "01", charFormatIndex); + table->remove(1, 1); + table->endEditBlock(); + QCOMPARE(table->plainText(), QString("0")); + table->undo(); + QCOMPARE(table->plainText(), QString("")); +} + +void tst_QTextPieceTable::undoRedo6() +{ + // this is essentially a test for the undoStack[undoPosition - 1].block = false in PieceTable::endUndoBlock() + QTextDocument doc; + QTextCursor cursor(&doc); + cursor.insertText("Hello World"); + + cursor.insertBlock(); + cursor.insertText("Hello World2"); + + cursor.movePosition(QTextCursor::Start); + QTextBlockFormat bfmt; + bfmt.setAlignment(Qt::AlignHCenter); + cursor.setBlockFormat(bfmt); + QVERIFY(cursor.blockFormat().alignment() == Qt::AlignHCenter); + + QTextCursor range = cursor; + range.clearSelection(); + range.movePosition(QTextCursor::Start); + range.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); + + QTextCharFormat modifier; + modifier.setFontItalic(true); + range.mergeCharFormat(modifier); + + cursor.movePosition(QTextCursor::Start); + QVERIFY(cursor.blockFormat().alignment() == Qt::AlignHCenter); + + doc.undo(); + + QVERIFY(cursor.blockFormat().alignment() == Qt::AlignHCenter); +} + +void tst_QTextPieceTable::undoRedo7() +{ + table->insert(0, "a", charFormatIndex); + table->insert(1, "b", charFormatIndex); + QCOMPARE(table->plainText(), QString("ab")); + + table->undo(); + QVERIFY(table->plainText().isEmpty()); +} + +void tst_QTextPieceTable::undoRedo8() +{ + table->insert(0, "a", charFormatIndex); + table->insert(1, "b", charFormatIndex); + QCOMPARE(table->plainText(), QString("ab")); + + table->remove(0, 1); + table->remove(0, 1); + + QVERIFY(table->plainText().isEmpty()); + table->undo(); + QCOMPARE(table->plainText(), QString("ab")); +} + +void tst_QTextPieceTable::undoRedo9() +{ + table->insert(0, "a", charFormatIndex); + table->insert(1, "b", charFormatIndex); + QCOMPARE(table->plainText(), QString("ab")); + + table->remove(1, 1); + table->remove(0, 1); + + QVERIFY(table->plainText().isEmpty()); + table->undo(); + QCOMPARE(table->plainText(), QString("ab")); +} + +void tst_QTextPieceTable::undoRedo10() +{ + // testcase for the beginUndoBlock/endUndoBlock calls being surrounded by an if (undoEnabled) + QTextCharFormat cf; + cf.setForeground(Qt::blue); + int cfIdx = table->formatCollection()->indexForFormat(cf); + + QTextBlockFormat f; + int idx = table->formatCollection()->indexForFormat(f); + + table->insert(0, "a", cfIdx); + table->insertBlock(1, idx, cfIdx); + table->insert(1, "b", cfIdx); + + cf.setForeground(Qt::red); + int newCfIdx = table->formatCollection()->indexForFormat(cf); + + table->setCharFormat(0, 3, cf, QTextDocumentPrivate::MergeFormat); + + QCOMPARE(table->find(0).value()->format, newCfIdx); + + table->undo(); + + QCOMPARE(table->find(0).value()->format, cfIdx); +} + +void tst_QTextPieceTable::undoRedo11() +{ + srand(3); + const int loops = 20; + QString compare; + int l = 0; + for (int i = 0; i < loops; ++i) { + bool remove = l && (rand() % 2); + int pos = (l > 1) ? rand() % (remove ? l-1 : l) : 0; + QChar c((unsigned short)((i % 26) + (i>25?'A':'a'))); + QString str; + str += c; + str += c; + if (remove) { + compare.remove(pos, 1); + table->remove(pos, 1); + } else { + compare.insert(pos, str); + table->insert(pos, str, charFormatIndex); + } + l += remove ? -1 : 2; + } + QVERIFY(table->plainText() == compare); + for (int i = 0; i < loops; ++i) + table->undo(); + QVERIFY(table->plainText() == QString("")); + for (int i = 0; i < loops; ++i) + table->redo(); + QVERIFY(table->plainText() == compare); +} + + +void tst_QTextPieceTable::checkDocumentChanged() +{ + table->enableUndoRedo(false); + QTestDocumentLayout *layout = new QTestDocumentLayout(doc); + doc->setDocumentLayout(layout); + + // single insert + layout->expect(0, 0, 15); + table->insert(0, "012345678901234", charFormatIndex); + QVERIFY(!layout->error); + + // single remove + layout->expect(0, 5, 0); + table->remove(0, 5); + QVERIFY(!layout->error); + + // symmetric insert/remove + layout->expect(0, 0, 0); + table->beginEditBlock(); + table->insert(0, "01234", charFormatIndex); + table->remove(0, 5); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(0, 5, 5); + table->beginEditBlock(); + table->remove(0, 5); + table->insert(0, "01234", charFormatIndex); + table->endEditBlock(); + QVERIFY(!layout->error); + + // replace + layout->expect(0, 3, 5); + table->beginEditBlock(); + table->remove(0, 3); + table->insert(0, "01234", charFormatIndex); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(0, 0, 2); + table->beginEditBlock(); + table->insert(0, "01234", charFormatIndex); + table->remove(0, 3); + table->endEditBlock(); + QVERIFY(!layout->error); + + // insert + remove inside insert block + layout->expect(0, 0, 2); + table->beginEditBlock(); + table->insert(0, "01234", charFormatIndex); + table->remove(1, 3); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(0, 0, 2); + table->beginEditBlock(); + table->insert(0, "01234", charFormatIndex); + table->remove(2, 3); + table->endEditBlock(); + QVERIFY(!layout->error); + + // insert + remove partly outside + layout->expect(0, 1, 0); + table->beginEditBlock(); + table->insert(1, "0", charFormatIndex); + table->remove(0, 2); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(0, 1, 1); + table->beginEditBlock(); + table->insert(1, "01", charFormatIndex); + table->remove(0, 2); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(0, 1, 2); + table->beginEditBlock(); + table->insert(1, "012", charFormatIndex); + table->remove(0, 2); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(1, 1, 0); + table->beginEditBlock(); + table->insert(1, "0", charFormatIndex); + table->remove(1, 2); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(1, 1, 1); + table->beginEditBlock(); + table->insert(1, "01", charFormatIndex); + table->remove(2, 2); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(1, 1, 2); + table->beginEditBlock(); + table->insert(1, "012", charFormatIndex); + table->remove(3, 2); + table->endEditBlock(); + QVERIFY(!layout->error); + + // insert + remove non overlapping + layout->expect(0, 1, 1); + table->beginEditBlock(); + table->insert(1, "0", charFormatIndex); + table->remove(0, 1); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(0, 2, 2); + table->beginEditBlock(); + table->insert(2, "1", charFormatIndex); + table->remove(0, 1); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(0, 2, 2); + table->beginEditBlock(); + table->remove(0, 1); + table->insert(1, "0", charFormatIndex); + table->endEditBlock(); + QVERIFY(!layout->error); + + layout->expect(0, 3, 3); + table->beginEditBlock(); + table->remove(0, 1); + table->insert(2, "1", charFormatIndex); + table->endEditBlock(); + + + layout->expect(0, 3, 3); + QTextCharFormat fmt; + fmt.setForeground(Qt::blue); + table->beginEditBlock(); + table->setCharFormat(0, 1, fmt); + table->setCharFormat(2, 1, fmt); + table->endEditBlock(); + QVERIFY(!layout->error); +} + +void tst_QTextPieceTable::checkDocumentChanged2() +{ + QTestDocumentLayout *layout = new QTestDocumentLayout(doc); + doc->setDocumentLayout(layout); + + QTextCharFormat fmt; + fmt.setForeground(Qt::blue); + int anotherCharFormatIndex = table->formatCollection()->indexForFormat(fmt); + + layout->expect(0, 0, 12); + table->beginEditBlock(); + table->insert(0, "0123", charFormatIndex); + table->insert(4, "4567", anotherCharFormatIndex); + table->insert(8, "8901", charFormatIndex); + table->endEditBlock(); + QVERIFY(!layout->error); + + fmt.setFontItalic(true); + + layout->expect(1, 10, 10); + table->beginEditBlock(); + table->setCharFormat(8, 3, fmt); + table->setCharFormat(4, 4, fmt); + table->setCharFormat(1, 3, fmt); + table->endEditBlock(); + QVERIFY(!layout->error); +} + +void tst_QTextPieceTable::setBlockFormat() +{ + QTextBlockFormat bfmt; + int index = table->formatCollection()->indexForFormat(bfmt); + + table->insertBlock(0, index, charFormatIndex); + table->insertBlock(0, index, charFormatIndex); + table->insertBlock(0, index, charFormatIndex); + + QTextBlockFormat newbfmt = bfmt; + newbfmt.setAlignment(Qt::AlignRight); + index = table->formatCollection()->indexForFormat(bfmt); + QTextBlock b = table->blocksFind(1); + table->setBlockFormat(b, b, newbfmt); + + QVERIFY(table->blocksFind(0).blockFormat() == bfmt); + QVERIFY(table->blocksFind(1).blockFormat() == newbfmt); + QVERIFY(table->blocksFind(2).blockFormat() == bfmt); +} + + +void tst_QTextPieceTable::blockInsertion() +{ + QTextBlockFormat fmt; + fmt.setTopMargin(100); + int idx = table->formatCollection()->indexForFormat(fmt); + int charFormat = table->formatCollection()->indexForFormat(QTextCharFormat()); + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + + table->insertBlock(0, idx, charFormat); + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(1).blockFormat() == fmt); + + table->undo(); + QVERIFY(table->blockMap().length() == 1); + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + + table->redo(); + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(1).blockFormat() == fmt); +} + +void tst_QTextPieceTable::blockInsertion2() +{ + // caused evil failing assertion in fragmentmap + int pos = 0; + table->insertBlock(pos, blockFormatIndex, charFormatIndex); + pos += 1; + table->insert(pos, "a", charFormatIndex); + pos += 1; + + pos -= 1; + table->insertBlock(pos, blockFormatIndex, charFormatIndex); + QCOMPARE(table->blocksFind(0).position(), 0); + QCOMPARE(table->blocksFind(1).position(), 1); + QCOMPARE(table->blocksFind(2).position(), 2); +} + +/* + Tests correct removal behaviour when deleting over block boundaries or complete blocks. +*/ + +void tst_QTextPieceTable::blockRemoval1() +{ + QTextBlockFormat fmt1; + fmt1.setTopMargin(100); + QTextBlockFormat fmt2; + fmt2.setAlignment(Qt::AlignRight); + int idx1 = table->formatCollection()->indexForFormat(fmt1); + int idx2 = table->formatCollection()->indexForFormat(fmt2); + + table->insert(0, "0123", charFormatIndex); + table->insertBlock(4, idx1, charFormatIndex); + table->insert(5, "5678", charFormatIndex); + table->insertBlock(9, idx2, charFormatIndex); + table->insert(10, "0123", charFormatIndex); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->beginEditBlock(); + table->remove(5, 5); + table->endEditBlock(); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt2); + QVERIFY(table->blocksFind(4).position() == 0); + QVERIFY(table->blocksFind(5).position() == 5); + + table->undo(); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->redo(); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt2); + QVERIFY(table->blocksFind(4).position() == 0); + QVERIFY(table->blocksFind(5).position() == 5); +} + +void tst_QTextPieceTable::blockRemoval2() +{ + QTextBlockFormat fmt1; + fmt1.setTopMargin(100); + QTextBlockFormat fmt2; + fmt2.setAlignment(Qt::AlignRight); + int idx1 = table->formatCollection()->indexForFormat(fmt1); + int idx2 = table->formatCollection()->indexForFormat(fmt2); + + table->insert(0, "0123", charFormatIndex); + table->insertBlock(4, idx1, charFormatIndex); + table->insert(5, "5678", charFormatIndex); + table->insertBlock(9, idx2, charFormatIndex); + table->insert(10, "0123", charFormatIndex); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->remove(4, 1); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(6).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).position() == 0); + QVERIFY(table->blocksFind(6).position() == 0); + + table->undo(); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->redo(); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(6).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).position() == 0); + QVERIFY(table->blocksFind(6).position() == 0); +} + +void tst_QTextPieceTable::blockRemoval3() +{ + QTextBlockFormat fmt1; + fmt1.setTopMargin(100); + QTextBlockFormat fmt2; + fmt2.setAlignment(Qt::AlignRight); + int idx1 = table->formatCollection()->indexForFormat(fmt1); + int idx2 = table->formatCollection()->indexForFormat(fmt2); + + table->insert(0, "0123", charFormatIndex); + table->insertBlock(4, idx1, charFormatIndex); + table->insert(5, "5678", charFormatIndex); + table->insertBlock(9, idx2, charFormatIndex); + table->insert(10, "0123", charFormatIndex); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->beginEditBlock(); + table->remove(3, 4); + table->endEditBlock(); + + QVERIFY(table->blocksFind(1).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(5).position() == 0); + + table->undo(); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->redo(); + QVERIFY(table->blocksFind(1).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(5).position() == 0); +} + +void tst_QTextPieceTable::blockRemoval4() +{ +#if 0 + QTextBlockFormat fmt1; + fmt1.setTopMargin(100); + QTextBlockFormat fmt2; + fmt2.setAlignment(Qt::AlignRight); + int idx1 = table->formatCollection()->indexForFormat(fmt1); + int idx2 = table->formatCollection()->indexForFormat(fmt2); + + table->insert(0, "0123", charFormatIndex); + table->insertBlock(4, idx1, charFormatIndex); + table->insert(5, "5678", charFormatIndex); + table->insertBlock(9, idx2, charFormatIndex); + table->insert(10, "0123", charFormatIndex); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->remove(3, 7); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(5).position() == 0); + QVERIFY(table->blocksFind(1).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == QTextBlockFormat()); + + table->undo(); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->redo(); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(5).position() == 0); + QVERIFY(table->blocksFind(1).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == QTextBlockFormat()); +#endif +} + +void tst_QTextPieceTable::blockRemoval5() +{ + QTextBlockFormat fmt1; + fmt1.setTopMargin(100); + QTextBlockFormat fmt2; + fmt2.setAlignment(Qt::AlignRight); + int idx1 = table->formatCollection()->indexForFormat(fmt1); + int idx2 = table->formatCollection()->indexForFormat(fmt2); + + table->insert(0, "0123", charFormatIndex); + table->insertBlock(4, idx1, charFormatIndex); + table->insert(5, "5678", charFormatIndex); + table->insertBlock(9, idx2, charFormatIndex); + table->insert(10, "0123", charFormatIndex); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->beginEditBlock(); + table->remove(3, 8); + table->endEditBlock(); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(5).position() == 0); + + table->undo(); + + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(4).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == fmt1); + QVERIFY(table->blocksFind(10).blockFormat() == fmt2); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(6).position() == 5); + QVERIFY(table->blocksFind(11).position() == 10); + + table->redo(); + QVERIFY(table->blocksFind(0).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(5).blockFormat() == QTextBlockFormat()); + QVERIFY(table->blocksFind(1).position() == 0); + QVERIFY(table->blocksFind(5).position() == 0); +} + + +void tst_QTextPieceTable::checkBlockSeparation() +{ + table->insertBlock(0, blockFormatIndex, charFormatIndex); + table->insertBlock(1, blockFormatIndex, charFormatIndex); + + QVERIFY(table->find(0) != table->find(1)); +} + +void tst_QTextPieceTable::checkFrames1() +{ + QTextFrameFormat ffmt; + table->insert(0, "Hello", charFormatIndex); + QPointer<QTextFrame> frame = table->insertFrame(1, 3, ffmt); + QTextFrame *root = table->rootFrame(); + + QVERIFY(root == frame->parentFrame()); + + QVERIFY(root); + QVERIFY(root->parentFrame() == 0); + + QVERIFY(root->childFrames().count() == 1); + QVERIFY(frame->format() == ffmt); + QVERIFY(frame->firstPosition() == 2); + QVERIFY(frame->lastPosition() == 4); + + + QPointer<QTextFrame> frame2 = table->insertFrame(2, 3, ffmt); + + QVERIFY(root->childFrames().count() == 1); + QVERIFY(root->childFrames().at(0) == frame); + QVERIFY(frame->childFrames().count() == 1); + QVERIFY(frame2->childFrames().count() == 0); + QVERIFY(frame2->parentFrame() == frame); + QVERIFY(frame2->firstPosition() == 3); + QVERIFY(frame2->lastPosition() == 4); + + QVERIFY(frame->format() == ffmt); + QVERIFY(frame->firstPosition() == 2); + QVERIFY(frame->lastPosition() == 6); + + table->removeFrame(frame); + + QVERIFY(root->childFrames().count() == 1); + QVERIFY(root->childFrames().at(0) == frame2); + QVERIFY(!frame); + QVERIFY(frame2->childFrames().count() == 0); + QVERIFY(frame2->parentFrame() == root); + QVERIFY(frame2->firstPosition() == 2); + QVERIFY(frame2->lastPosition() == 3); + + table->undo(); + + frame = table->frameAt(2); + + QVERIFY(root->childFrames().count() == 1); + QVERIFY(root->childFrames().at(0) == frame); + QVERIFY(frame->childFrames().count() == 1); + QVERIFY(frame->childFrames().at(0) == frame2); + QVERIFY(frame2->childFrames().count() == 0); + QVERIFY(frame2->parentFrame() == frame); + QVERIFY(frame2->firstPosition() == 3); + QVERIFY(frame2->lastPosition() == 4); + + QVERIFY(frame->firstPosition() == 2); + QVERIFY(frame->lastPosition() == 6); + + table->undo(); + + QVERIFY(root->childFrames().count() == 1); + QVERIFY(root->childFrames().at(0) == frame); + QVERIFY(frame->childFrames().count() == 0); + QVERIFY(!frame2); + + QVERIFY(frame->firstPosition() == 2); + QVERIFY(frame->lastPosition() == 4); +} + +void tst_QTextPieceTable::removeFrameDirect() +{ + QTextFrameFormat ffmt; + table->insert(0, "Hello", charFormatIndex); + + QTextFrame *frame = table->insertFrame(1, 5, ffmt); + + QVERIFY(frame->parentFrame() == table->rootFrame()); + + const int start = frame->firstPosition() - 1; + const int end = frame->lastPosition(); + const int length = end - start + 1; + + table->remove(start, length); +} + +void tst_QTextPieceTable::removeWithChildFrame() +{ + /* + The piecetable layout is: + + ... + 1 BeginningOfFrame(first frame) + 2 text + 3 BeginningOfFrame(second frame) + 4 text + 5 text + 6 EndOfFrame(second frame) + 7 text + 8 text + 9 EndOfFrame(first frame) + ... + + The idea is to remove from [2] until [6], basically some trailing text and the second frame. + In this case frameAt(2) != frameAt(6), so the assertion in remove() needed an adjustement. + */ + QTextFrameFormat ffmt; + table->insert(0, "Hello World", charFormatIndex); + + QTextFrame *frame = table->insertFrame(1, 6, ffmt); + QTextFrame *childFrame = table->insertFrame(3, 5, ffmt); + Q_UNUSED(frame); + Q_UNUSED(childFrame); + + // used to give a failing assertion + table->remove(2, 5); + QVERIFY(true); +} + +void tst_QTextPieceTable::clearWithFrames() +{ + /* + The piecetable layout is: + + ... + 1 BeginningOfFrame(first frame) + 2 text + 3 EndOfFrame(first frame) + 4 BeginningOfFrame(second frame) + 5 text + 6 text + 7 EndOfFrame(second frame) + ... + + The idea is to remove from [1] until [7]. + */ + QTextFrameFormat ffmt; + table->insert(0, "Hello World", charFormatIndex); + + QTextFrame *firstFrame = table->insertFrame(1, 2, ffmt); + QTextFrame *secondFrame = table->insertFrame(4, 6, ffmt); + + const int start = firstFrame->firstPosition() - 1; + const int end = secondFrame->lastPosition(); + const int length = end - start + 1; + // used to give a failing assertion + table->remove(start, length); + QVERIFY(true); +} + +QTEST_MAIN(tst_QTextPieceTable) + + +#include "tst_qtextpiecetable.moc" + diff --git a/tests/auto/gui/text/qtextscriptengine/.gitignore b/tests/auto/gui/text/qtextscriptengine/.gitignore new file mode 100644 index 0000000000..e51a335099 --- /dev/null +++ b/tests/auto/gui/text/qtextscriptengine/.gitignore @@ -0,0 +1 @@ +tst_qtextscriptengine diff --git a/tests/auto/gui/text/qtextscriptengine/generate/generate.pro b/tests/auto/gui/text/qtextscriptengine/generate/generate.pro new file mode 100644 index 0000000000..354e0e5cdf --- /dev/null +++ b/tests/auto/gui/text/qtextscriptengine/generate/generate.pro @@ -0,0 +1,14 @@ +###################################################################### +# Automatically generated by qmake (1.07a) Fri Sep 30 15:20:45 2005 +###################################################################### + +TEMPLATE = app +CONFIG -= moc +INCLUDEPATH += . /usr/include/freetype2 +INCLUDEPATH += $$QT_SOURCE_TREE/src/3rdparty/harfbuzz/src + +# Input +SOURCES += main.cpp +CONFIG += qt warn_on debug thread create_prl link_prl + + diff --git a/tests/auto/gui/text/qtextscriptengine/generate/main.cpp b/tests/auto/gui/text/qtextscriptengine/generate/main.cpp new file mode 100644 index 0000000000..06caa34b4d --- /dev/null +++ b/tests/auto/gui/text/qtextscriptengine/generate/main.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QApplication> +#include <QTextEdit> +#include <QLineEdit> +#include <QVBoxLayout> +#include <QFontDialog> +#include <QPushButton> + +#define private public +#include <qfont.h> +#include <private/qtextengine_p.h> +#include <private/qfontengine_p.h> +#include <qtextlayout.h> +#undef private + + +class MyEdit : public QTextEdit { + Q_OBJECT +public: + MyEdit(QWidget *p) : QTextEdit(p) { setReadOnly(true); } +public slots: + void setText(const QString &str); + void changeFont(); +public: + QLineEdit *lineEdit; +}; + +void MyEdit::setText(const QString &str) +{ + if (str.isEmpty()) { + clear(); + return; + } + QTextLayout layout(str, lineEdit->font()); + QTextEngine *e = layout.d; + e->itemize(); + e->shape(0); + + QString result; + result = "Using font '" + e->fontEngine(e->layoutData->items[0])->fontDef.family + "'\n\n"; + + result += "{ { "; + for (int i = 0; i < str.length(); ++i) + result += "0x" + QString::number(str.at(i).unicode(), 16) + ", "; + result += "0x0 },\n { "; + for (int i = 0; i < e->layoutData->items[0].num_glyphs; ++i) + result += "0x" + QString::number(e->layoutData->glyphLayout.glyphs[i], 16) + ", "; + result += "0x0 } }"; + + setPlainText(result); +} + +void MyEdit::changeFont() +{ + bool ok; + QFont f = QFontDialog::getFont(&ok, lineEdit->font(), topLevelWidget()); + if (ok) + lineEdit->setFont(f); +} + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + QWidget *mw = new QWidget(0); + QVBoxLayout *l = new QVBoxLayout(mw); + + QLineEdit *le = new QLineEdit(mw); + l->addWidget(le); + + MyEdit *view = new MyEdit(mw); + view->lineEdit = le; + l->addWidget(view); + + QPushButton *button = new QPushButton("Change Font", mw); + l->addWidget(button); + + QObject::connect(le, SIGNAL(textChanged(QString)), view, SLOT(setText(QString))); + QObject::connect(button, SIGNAL(clicked()), view, SLOT(changeFont())); + + mw->resize(500, 300); + mw->show(); + + return a.exec(); +} + + +#include <main.moc> diff --git a/tests/auto/gui/text/qtextscriptengine/qtextscriptengine.pro b/tests/auto/gui/text/qtextscriptengine/qtextscriptengine.pro new file mode 100644 index 0000000000..0f5076e2ed --- /dev/null +++ b/tests/auto/gui/text/qtextscriptengine/qtextscriptengine.pro @@ -0,0 +1,7 @@ +load(qttest_p4) + +QT += core-private gui-private + +HEADERS += +SOURCES += tst_qtextscriptengine.cpp +INCLUDEPATH += $$QT_SOURCE_TREE/src/3rdparty/harfbuzz/src diff --git a/tests/auto/gui/text/qtextscriptengine/tst_qtextscriptengine.cpp b/tests/auto/gui/text/qtextscriptengine/tst_qtextscriptengine.cpp new file mode 100644 index 0000000000..cbed675cb7 --- /dev/null +++ b/tests/auto/gui/text/qtextscriptengine/tst_qtextscriptengine.cpp @@ -0,0 +1,1304 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + + +#ifdef Q_WS_X11 +#define private public +#endif + +// cannot do private -> public on windows since it seems to mess up some stl headers +#include <qfont.h> + +#ifdef Q_WS_X11 +#undef private +#endif + +#include <QtTest/QtTest> + + + +#if defined(Q_WS_X11) || defined(Q_WS_MAC) +#define private public +#include <private/qtextengine_p.h> +#include <qtextlayout.h> +#undef private +#else +#include <private/qtextengine_p.h> +#include <qtextlayout.h> +#endif + +#include <qfontdatabase.h> +#include <qfontinfo.h> + + + + + +//TESTED_CLASS= +//TESTED_FILES= gui/text/qscriptengine.cpp + +class tst_QTextScriptEngine : public QObject +{ +Q_OBJECT + +public: + tst_QTextScriptEngine(); + virtual ~tst_QTextScriptEngine(); + + +public slots: + void initTestCase(); + void init(); + void cleanup(); +private slots: + void devanagari(); + void bengali(); + void gurmukhi(); + // gujarati missing + void oriya(); + void tamil(); + void telugu(); + void kannada(); + void malayalam(); + void sinhala(); + void greek(); + + void khmer(); + void linearB(); + void controlInSyllable_qtbug14204(); + void combiningMarks_qtbug15675(); + + void mirroredChars_data(); + void mirroredChars(); + +private: + bool haveTestFonts; +}; + +tst_QTextScriptEngine::tst_QTextScriptEngine() + : haveTestFonts(qgetenv("QT_HAVE_TEST_FONTS") == QByteArray("1")) +{ +} + +tst_QTextScriptEngine::~tst_QTextScriptEngine() +{ +} + +void tst_QTextScriptEngine::initTestCase() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + qWarning( + "Some of these tests depend on the internals of some test fonts which are not freely " + "distributable.\n" + "These tests will be skipped.\n" + "If you have the fonts available, set QT_HAVE_TEST_FONTS=1 in your environment and " + "run the test again." + ); + } +#endif +} + +void tst_QTextScriptEngine::init() +{ +} + +void tst_QTextScriptEngine::cleanup() +{ +} + +struct ShapeTable { + unsigned short unicode[16]; + unsigned short glyphs[16]; +}; + +#if defined(Q_WS_X11) +static bool shaping( const QFont &f, const ShapeTable *s) +{ + QString str = QString::fromUtf16( s->unicode ); + QTextLayout layout(str, f); + QTextEngine *e = layout.d; + e->itemize(); + e->shape(0); + + int nglyphs = 0; + const unsigned short *g = s->glyphs; + while ( *g ) { + nglyphs++; + g++; + } + + if( nglyphs != e->layoutData->items[0].num_glyphs ) + goto error; + + for (int i = 0; i < nglyphs; ++i) { + if ((e->layoutData->glyphLayout.glyphs[i] & 0xffffff) != s->glyphs[i]) + goto error; + } + return true; + error: + str = ""; + const unsigned short *uc = s->unicode; + while (*uc) { + str += QString("%1 ").arg(*uc, 4, 16); + ++uc; + } + qDebug("%s: shaping of string %s failed, nglyphs=%d, expected %d", + f.family().toLatin1().constData(), + str.toLatin1().constData(), + e->layoutData->items[0].num_glyphs, nglyphs); + + str = ""; + int i = 0; + while (i < e->layoutData->items[0].num_glyphs) { + str += QString("%1 ").arg(e->layoutData->glyphLayout.glyphs[i], 4, 16); + ++i; + } + qDebug(" glyph result = %s", str.toLatin1().constData()); + return false; +} +#endif + +void tst_QTextScriptEngine::devanagari() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Devanagari).contains("Raghindi")) { + QFont f("Raghindi"); + const ShapeTable shape_table [] = { + // Ka + { { 0x0915, 0x0 }, + { 0x0080, 0x0 } }, + // Ka Halant + { { 0x0915, 0x094d, 0x0 }, + { 0x0080, 0x0051, 0x0 } }, + // Ka Halant Ka + { { 0x0915, 0x094d, 0x0915, 0x0 }, + { 0x00c8, 0x0080, 0x0 } }, + // Ka MatraI + { { 0x0915, 0x093f, 0x0 }, + { 0x01d1, 0x0080, 0x0 } }, + // Ra Halant Ka + { { 0x0930, 0x094d, 0x0915, 0x0 }, + { 0x0080, 0x005b, 0x0 } }, + // Ra Halant Ka MatraI + { { 0x0930, 0x094d, 0x0915, 0x093f, 0x0 }, + { 0x01d1, 0x0080, 0x005b, 0x0 } }, + // MatraI + { { 0x093f, 0x0 }, + { 0x01d4, 0x029c, 0x0 } }, + // Ka Nukta + { { 0x0915, 0x093c, 0x0 }, + { 0x00a4, 0x0 } }, + // Ka Halant Ra + { { 0x0915, 0x094d, 0x0930, 0x0 }, + { 0x0110, 0x0 } }, + // Ka Halant Ra Halant Ka + { { 0x0915, 0x094d, 0x0930, 0x094d, 0x0915, 0x0 }, + { 0x0158, 0x0080, 0x0 } }, + { { 0x0930, 0x094d, 0x200d, 0x0 }, + { 0x00e2, 0x0 } }, + { { 0x0915, 0x094d, 0x0930, 0x094d, 0x200d, 0x0 }, + { 0x0158, 0x0 } }, + + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Raghindi", SkipAll); + } + } + + { + if (QFontDatabase().families(QFontDatabase::Devanagari).contains("Mangal")) { + QFont f("Mangal"); + const ShapeTable shape_table [] = { + // Ka + { { 0x0915, 0x0 }, + { 0x0080, 0x0 } }, + // Ka Halant + { { 0x0915, 0x094d, 0x0 }, + { 0x0080, 0x0051, 0x0 } }, + // Ka Halant Ka + { { 0x0915, 0x094d, 0x0915, 0x0 }, + { 0x00c8, 0x0080, 0x0 } }, + // Ka MatraI + { { 0x0915, 0x093f, 0x0 }, + { 0x01d1, 0x0080, 0x0 } }, + // Ra Halant Ka + { { 0x0930, 0x094d, 0x0915, 0x0 }, + { 0x0080, 0x005b, 0x0 } }, + // Ra Halant Ka MatraI + { { 0x0930, 0x094d, 0x0915, 0x093f, 0x0 }, + { 0x01d1, 0x0080, 0x005b, 0x0 } }, + // MatraI + { { 0x093f, 0x0 }, + { 0x01d4, 0x029c, 0x0 } }, + // Ka Nukta + { { 0x0915, 0x093c, 0x0 }, + { 0x00a4, 0x0 } }, + // Ka Halant Ra + { { 0x0915, 0x094d, 0x0930, 0x0 }, + { 0x0110, 0x0 } }, + // Ka Halant Ra Halant Ka + { { 0x0915, 0x094d, 0x0930, 0x094d, 0x0915, 0x0 }, + { 0x0158, 0x0080, 0x0 } }, + + { { 0x92b, 0x94d, 0x930, 0x0 }, + { 0x125, 0x0 } }, + { { 0x92b, 0x93c, 0x94d, 0x930, 0x0 }, + { 0x149, 0x0 } }, + { {0}, {0} } + }; + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find mangal", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + +void tst_QTextScriptEngine::bengali() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Bengali).contains("Akaash")) { + QFont f("Akaash"); + const ShapeTable shape_table [] = { + // Ka + { { 0x0995, 0x0 }, + { 0x0151, 0x0 } }, + // Ka Halant + { { 0x0995, 0x09cd, 0x0 }, + { 0x0151, 0x017d, 0x0 } }, + // Ka Halant Ka + { { 0x0995, 0x09cd, 0x0995, 0x0 }, + { 0x019b, 0x0 } }, + // Ka MatraI + { { 0x0995, 0x09bf, 0x0 }, + { 0x0173, 0x0151, 0x0 } }, + // Ra Halant Ka + { { 0x09b0, 0x09cd, 0x0995, 0x0 }, + { 0x0151, 0x0276, 0x0 } }, + // Ra Halant Ka MatraI + { { 0x09b0, 0x09cd, 0x0995, 0x09bf, 0x0 }, + { 0x0173, 0x0151, 0x0276, 0x0 } }, + // Ka Nukta + { { 0x0995, 0x09bc, 0x0 }, + { 0x0151, 0x0171, 0x0 } }, + // Ka Halant Ra + { { 0x0995, 0x09cd, 0x09b0, 0x0 }, + { 0x01f4, 0x0 } }, + // Ka Halant Ra Halant Ka + { { 0x0995, 0x09cd, 0x09b0, 0x09cd, 0x0995, 0x0 }, + { 0x025c, 0x0276, 0x0151, 0x0 } }, + // Ya + Halant + { { 0x09af, 0x09cd, 0x0 }, + { 0x016a, 0x017d, 0x0 } }, + // Da Halant Ya -> Da Ya-Phala + { { 0x09a6, 0x09cd, 0x09af, 0x0 }, + { 0x01e5, 0x0 } }, + // A Halant Ya -> A Ya-phala + { { 0x0985, 0x09cd, 0x09af, 0x0 }, + { 0x0145, 0x01cf, 0x0 } }, + // Na Halant Ka + { { 0x09a8, 0x09cd, 0x0995, 0x0 }, + { 0x026f, 0x0151, 0x0 } }, + // Na Halant ZWNJ Ka + { { 0x09a8, 0x09cd, 0x200c, 0x0995, 0x0 }, + { 0x0164, 0x017d, 0x0151, 0x0 } }, + // Na Halant ZWJ Ka + { { 0x09a8, 0x09cd, 0x200d, 0x0995, 0x0 }, + { 0x026f, 0x0151, 0x0 } }, + // Ka Halant ZWNJ Ka + { { 0x0995, 0x09cd, 0x200c, 0x0995, 0x0 }, + { 0x0151, 0x017d, 0x0151, 0x0 } }, + // Ka Halant ZWJ Ka + { { 0x0995, 0x09cd, 0x200d, 0x0995, 0x0 }, + { 0x025c, 0x0151, 0x0 } }, + // Na Halant Ra + { { 0x09a8, 0x09cd, 0x09b0, 0x0 }, + { 0x0207, 0x0 } }, + // Na Halant ZWNJ Ra + { { 0x09a8, 0x09cd, 0x200c, 0x09b0, 0x0 }, + { 0x0164, 0x017d, 0x016b, 0x0 } }, + // Na Halant ZWJ Ra + { { 0x09a8, 0x09cd, 0x200d, 0x09b0, 0x0 }, + { 0x026f, 0x016b, 0x0 } }, + // Na Halant Ba + { { 0x09a8, 0x09cd, 0x09ac, 0x0 }, + { 0x022f, 0x0 } }, + // Na Halant ZWNJ Ba + { { 0x09a8, 0x09cd, 0x200c, 0x09ac, 0x0 }, + { 0x0164, 0x017d, 0x0167, 0x0 } }, + // Na Halant ZWJ Ba + { { 0x09a8, 0x09cd, 0x200d, 0x09ac, 0x0 }, + { 0x026f, 0x0167, 0x0 } }, + // Na Halant Dha + { { 0x09a8, 0x09cd, 0x09a7, 0x0 }, + { 0x01d3, 0x0 } }, + // Na Halant ZWNJ Dha + { { 0x09a8, 0x09cd, 0x200c, 0x09a7, 0x0 }, + { 0x0164, 0x017d, 0x0163, 0x0 } }, + // Na Halant ZWJ Dha + { { 0x09a8, 0x09cd, 0x200d, 0x09a7, 0x0 }, + { 0x026f, 0x0163, 0x0 } }, + // Ra Halant Ka MatraAU + { { 0x09b0, 0x09cd, 0x0995, 0x09cc, 0x0 }, + { 0x0179, 0x0151, 0x0276, 0x017e, 0x0 } }, + // Ra Halant Ba Halant Ba + { { 0x09b0, 0x09cd, 0x09ac, 0x09cd, 0x09ac, 0x0 }, + { 0x0232, 0x0276, 0x0 } }, + { { 0x9b0, 0x9cd, 0x995, 0x9be, 0x982, 0x0 }, + { 0x151, 0x276, 0x172, 0x143, 0x0 } }, + { { 0x9b0, 0x9cd, 0x995, 0x9be, 0x983, 0x0 }, + { 0x151, 0x276, 0x172, 0x144, 0x0 } }, + // test decomposed two parts matras + { { 0x995, 0x9c7, 0x9be, 0x0 }, + { 0x179, 0x151, 0x172, 0x0 } }, + { { 0x995, 0x9c7, 0x9d7, 0x0 }, + { 0x179, 0x151, 0x17e, 0x0 } }, + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Akaash", SkipAll); + } + } + { + if (QFontDatabase().families(QFontDatabase::Bengali).contains("Mukti Narrow")) { + QFont f("Mukti Narrow"); + const ShapeTable shape_table [] = { + // Ka + { { 0x0995, 0x0 }, + { 0x0073, 0x0 } }, + // Ka Halant + { { 0x0995, 0x09cd, 0x0 }, + { 0x00b9, 0x0 } }, + // Ka Halant Ka + { { 0x0995, 0x09cd, 0x0995, 0x0 }, + { 0x0109, 0x0 } }, + // Ka MatraI + { { 0x0995, 0x09bf, 0x0 }, + { 0x0095, 0x0073, 0x0 } }, + // Ra Halant Ka + { { 0x09b0, 0x09cd, 0x0995, 0x0 }, + { 0x0073, 0x00e1, 0x0 } }, + // Ra Halant Ka MatraI + { { 0x09b0, 0x09cd, 0x0995, 0x09bf, 0x0 }, + { 0x0095, 0x0073, 0x00e1, 0x0 } }, + // MatraI + { { 0x09bf, 0x0 }, + { 0x0095, 0x01c8, 0x0 } }, + // Ka Nukta + { { 0x0995, 0x09bc, 0x0 }, + { 0x0073, 0x0093, 0x0 } }, + // Ka Halant Ra + { { 0x0995, 0x09cd, 0x09b0, 0x0 }, + { 0x00e5, 0x0 } }, + // Ka Halant Ra Halant Ka + { { 0x995, 0x9cd, 0x9b0, 0x9cd, 0x995, 0x0 }, + { 0x234, 0x24e, 0x73, 0x0 } }, + // Ya + Halant + { { 0x09af, 0x09cd, 0x0 }, + { 0x00d2, 0x0 } }, + // Da Halant Ya -> Da Ya-Phala + { { 0x09a6, 0x09cd, 0x09af, 0x0 }, + { 0x0084, 0x00e2, 0x0 } }, + // A Halant Ya -> A Ya-phala + { { 0x0985, 0x09cd, 0x09af, 0x0 }, + { 0x0067, 0x00e2, 0x0 } }, + // Na Halant Ka + { { 0x09a8, 0x09cd, 0x0995, 0x0 }, + { 0x0188, 0x0 } }, + // Na Halant ZWNJ Ka + { { 0x9a8, 0x9cd, 0x200c, 0x995, 0x0 }, + { 0xcc, 0x73, 0x0 } }, + // Na Halant ZWJ Ka + { { 0x9a8, 0x9cd, 0x200d, 0x995, 0x0 }, + { 0x247, 0x73, 0x0 } }, + // Ka Halant ZWNJ Ka + { { 0x9a8, 0x9cd, 0x200d, 0x995, 0x0 }, + { 0x247, 0x73, 0x0 } }, + // Ka Halant ZWJ Ka + { { 0x9a8, 0x9cd, 0x200d, 0x995, 0x0 }, + { 0x247, 0x73, 0x0 } }, + // Na Halant Ra + { { 0x09a8, 0x09cd, 0x09b0, 0x0 }, + { 0x00f8, 0x0 } }, + // Na Halant ZWNJ Ra + { { 0x09a8, 0x09cd, 0x200c, 0x09b0, 0x0 }, + { 0xcc, 0x8d, 0x0 } }, + // Na Halant ZWJ Ra + { { 0x9a8, 0x9cd, 0x200d, 0x9b0, 0x0 }, + { 0x247, 0x8d, 0x0 } }, + // Na Halant Ba + { { 0x09a8, 0x09cd, 0x09ac, 0x0 }, + { 0x0139, 0x0 } }, + // Na Halant ZWNJ Ba + { { 0x9a8, 0x9cd, 0x200c, 0x9ac, 0x0 }, + { 0xcc, 0x89, 0x0 } }, + // Na Halant ZWJ Ba + { { 0x9a8, 0x9cd, 0x200d, 0x9ac, 0x0 }, + { 0x247, 0x89, 0x0 } }, + // Na Halant Dha + { { 0x09a8, 0x09cd, 0x09a7, 0x0 }, + { 0x0145, 0x0 } }, + // Na Halant ZWNJ Dha + { { 0x09a8, 0x09cd, 0x200c, 0x09a7, 0x0 }, + { 0xcc, 0x85, 0x0 } }, + // Na Halant ZWJ Dha + { { 0x09a8, 0x09cd, 0x200d, 0x09a7, 0x0 }, + { 0x247, 0x85, 0x0 } }, + // Ra Halant Ka MatraAU + { { 0x9b0, 0x9cd, 0x995, 0x9cc, 0x0 }, + { 0x232, 0x73, 0xe1, 0xa0, 0x0 } }, + // Ra Halant Ba Halant Ba + { { 0x09b0, 0x09cd, 0x09ac, 0x09cd, 0x09ac, 0x0 }, + { 0x013b, 0x00e1, 0x0 } }, + + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Mukti", SkipAll); + } + } + { + if (QFontDatabase().families(QFontDatabase::Bengali).contains("Likhan")) { + QFont f("Likhan"); + const ShapeTable shape_table [] = { + { { 0x9a8, 0x9cd, 0x9af, 0x0 }, + { 0x1ca, 0x0 } }, + { { 0x09b8, 0x09cd, 0x09af, 0x0 }, + { 0x020e, 0x0 } }, + { { 0x09b6, 0x09cd, 0x09af, 0x0 }, + { 0x01f4, 0x0 } }, + { { 0x09b7, 0x09cd, 0x09af, 0x0 }, + { 0x01fe, 0x0 } }, + { { 0x09b0, 0x09cd, 0x09a8, 0x09cd, 0x200d, 0x0 }, + { 0x10b, 0x167, 0x0 } }, + + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Likhan", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + +void tst_QTextScriptEngine::gurmukhi() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Gurmukhi).contains("Lohit Punjabi")) { + QFont f("Lohit Punjabi"); + const ShapeTable shape_table [] = { + { { 0xA15, 0xA4D, 0xa39, 0x0 }, + { 0x3b, 0x8b, 0x0 } }, + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Lohit Punjabi", SkipAll); + } + } +#endif +} + +void tst_QTextScriptEngine::oriya() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Oriya).contains("utkal")) { + QFont f("utkal"); + const ShapeTable shape_table [] = { + { { 0xb15, 0xb4d, 0xb24, 0xb4d, 0xb30, 0x0 }, + { 0x150, 0x125, 0x0 } }, + { { 0xb24, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0x0 }, + { 0x151, 0x120, 0x0 } }, + { { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0x0 }, + { 0x152, 0x120, 0x0 } }, + { { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb2c, 0x0 }, + { 0x152, 0x120, 0x0 } }, + { { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb30, 0x0 }, + { 0x176, 0x0 } }, + { { 0xb38, 0xb4d, 0xb24, 0xb4d, 0xb30, 0x0 }, + { 0x177, 0x0 } }, + { { 0xb28, 0xb4d, 0xb24, 0xb4d, 0xb30, 0xb4d, 0xb2f, 0x0 }, + { 0x176, 0x124, 0x0 } }, + { {0}, {0} } + + }; + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find utkal", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + + +void tst_QTextScriptEngine::tamil() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Tamil).contains("AkrutiTml1")) { + QFont f("AkrutiTml1"); + const ShapeTable shape_table [] = { + { { 0x0b95, 0x0bc2, 0x0 }, + { 0x004e, 0x0 } }, + { { 0x0bae, 0x0bc2, 0x0 }, + { 0x009e, 0x0 } }, + { { 0x0b9a, 0x0bc2, 0x0 }, + { 0x0058, 0x0 } }, + { { 0x0b99, 0x0bc2, 0x0 }, + { 0x0053, 0x0 } }, + { { 0x0bb0, 0x0bc2, 0x0 }, + { 0x00a8, 0x0 } }, + { { 0x0ba4, 0x0bc2, 0x0 }, + { 0x008e, 0x0 } }, + { { 0x0b9f, 0x0bc2, 0x0 }, + { 0x0062, 0x0 } }, + { { 0x0b95, 0x0bc6, 0x0 }, + { 0x000a, 0x0031, 0x0 } }, + { { 0x0b95, 0x0bca, 0x0 }, + { 0x000a, 0x0031, 0x0007, 0x0 } }, + { { 0x0b95, 0x0bc6, 0x0bbe, 0x0 }, + { 0x000a, 0x0031, 0x007, 0x0 } }, + { { 0x0b95, 0x0bcd, 0x0bb7, 0x0 }, + { 0x0049, 0x0 } }, + { { 0x0b95, 0x0bcd, 0x0bb7, 0x0bca, 0x0 }, + { 0x000a, 0x0049, 0x007, 0x0 } }, + { { 0x0b95, 0x0bcd, 0x0bb7, 0x0bc6, 0x0bbe, 0x0 }, + { 0x000a, 0x0049, 0x007, 0x0 } }, + { { 0x0b9f, 0x0bbf, 0x0 }, + { 0x005f, 0x0 } }, + { { 0x0b9f, 0x0bc0, 0x0 }, + { 0x0060, 0x0 } }, + { { 0x0bb2, 0x0bc0, 0x0 }, + { 0x00ab, 0x0 } }, + { { 0x0bb2, 0x0bbf, 0x0 }, + { 0x00aa, 0x0 } }, + { { 0x0bb0, 0x0bcd, 0x0 }, + { 0x00a4, 0x0 } }, + { { 0x0bb0, 0x0bbf, 0x0 }, + { 0x00a5, 0x0 } }, + { { 0x0bb0, 0x0bc0, 0x0 }, + { 0x00a6, 0x0 } }, + { { 0x0b83, 0x0 }, + { 0x0025, 0x0 } }, + { { 0x0b83, 0x0b95, 0x0 }, + { 0x0025, 0x0031, 0x0 } }, + { { 0xb95, 0xbc6, 0xbbe, 0x0 }, + { 0xa, 0x31, 0x7, 0x0 } }, + { { 0xb95, 0xbc7, 0xbbe, 0x0 }, + { 0xb, 0x31, 0x7, 0x0 } }, + { { 0xb95, 0xbc6, 0xbd7, 0x0 }, + { 0xa, 0x31, 0x40, 0x0 } }, + + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find AkrutiTml1", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + + +void tst_QTextScriptEngine::telugu() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Telugu).contains("Pothana2000")) { + QFont f("Pothana2000"); + const ShapeTable shape_table [] = { + { { 0xc15, 0xc4d, 0x0 }, + { 0xbb, 0x0 } }, + { { 0xc15, 0xc4d, 0xc37, 0x0 }, + { 0x4b, 0x0 } }, + { { 0xc15, 0xc4d, 0xc37, 0xc4d, 0x0 }, + { 0xe0, 0x0 } }, + { { 0xc15, 0xc4d, 0xc37, 0xc4d, 0xc23, 0x0 }, + { 0x4b, 0x91, 0x0 } }, + { { 0xc15, 0xc4d, 0xc30, 0x0 }, + { 0x5a, 0xb2, 0x0 } }, + { { 0xc15, 0xc4d, 0xc30, 0xc4d, 0x0 }, + { 0xbb, 0xb2, 0x0 } }, + { { 0xc15, 0xc4d, 0xc30, 0xc4d, 0xc15, 0x0 }, + { 0x5a, 0xb2, 0x83, 0x0 } }, + { { 0xc15, 0xc4d, 0xc30, 0xc3f, 0x0 }, + { 0xe2, 0xb2, 0x0 } }, + { { 0xc15, 0xc4d, 0xc15, 0xc48, 0x0 }, + { 0xe6, 0xb3, 0x83, 0x0 } }, + { { 0xc15, 0xc4d, 0xc30, 0xc48, 0x0 }, + { 0xe6, 0xb3, 0x9f, 0x0 } }, + { { 0xc15, 0xc46, 0xc56, 0x0 }, + { 0xe6, 0xb3, 0x0 } }, + { {0}, {0} } + + }; + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Pothana2000", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + + +void tst_QTextScriptEngine::kannada() +{ +#if defined(Q_WS_X11) + { + if (QFontDatabase().families(QFontDatabase::Kannada).contains("Sampige")) { + QFont f("Sampige"); + const ShapeTable shape_table [] = { + { { 0x0ca8, 0x0ccd, 0x0ca8, 0x0 }, + { 0x0049, 0x00ba, 0x0 } }, + { { 0x0ca8, 0x0ccd, 0x0ca1, 0x0 }, + { 0x0049, 0x00b3, 0x0 } }, + { { 0x0caf, 0x0cc2, 0x0 }, + { 0x004f, 0x005d, 0x0 } }, + { { 0x0ce0, 0x0 }, + { 0x006a, 0x0 } }, + { { 0x0ce6, 0x0ce7, 0x0ce8, 0x0 }, + { 0x006b, 0x006c, 0x006d, 0x0 } }, + { { 0x0cb5, 0x0ccb, 0x0 }, + { 0x015f, 0x0067, 0x0 } }, + { { 0x0cb0, 0x0ccd, 0x0cae, 0x0 }, + { 0x004e, 0x0082, 0x0 } }, + { { 0x0cb0, 0x0ccd, 0x0c95, 0x0 }, + { 0x0036, 0x0082, 0x0 } }, + { { 0x0c95, 0x0ccd, 0x0cb0, 0x0 }, + { 0x0036, 0x00c1, 0x0 } }, + { { 0x0cb0, 0x0ccd, 0x200d, 0x0c95, 0x0 }, + { 0x0050, 0x00a7, 0x0 } }, + + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Sampige", SkipAll); + } + } + { + if (QFontDatabase().families(QFontDatabase::Kannada).contains("Tunga")) { + QFont f("Tunga"); + const ShapeTable shape_table [] = { + { { 0x0cb7, 0x0cc6, 0x0 }, + { 0x00b0, 0x006c, 0x0 } }, + { { 0x0cb7, 0x0ccd, 0x0 }, + { 0x0163, 0x0 } }, + { { 0xc95, 0xcbf, 0xcd5, 0x0 }, + { 0x114, 0x73, 0x0 } }, + { { 0xc95, 0xcc6, 0xcd5, 0x0 }, + { 0x90, 0x6c, 0x73, 0x0 } }, + { { 0xc95, 0xcc6, 0xcd6, 0x0 }, + { 0x90, 0x6c, 0x74, 0x0 } }, + { { 0xc95, 0xcc6, 0xcc2, 0x0 }, + { 0x90, 0x6c, 0x69, 0x0 } }, + { { 0xc95, 0xcca, 0xcd5, 0x0 }, + { 0x90, 0x6c, 0x69, 0x73, 0x0 } }, + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Tunga", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + + + +void tst_QTextScriptEngine::malayalam() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Malayalam).contains("AkrutiMal2")) { + QFont f("AkrutiMal2"); + const ShapeTable shape_table [] = { + { { 0x0d15, 0x0d46, 0x0 }, + { 0x005e, 0x0034, 0x0 } }, + { { 0x0d15, 0x0d47, 0x0 }, + { 0x005f, 0x0034, 0x0 } }, + { { 0x0d15, 0x0d4b, 0x0 }, + { 0x005f, 0x0034, 0x0058, 0x0 } }, + { { 0x0d15, 0x0d48, 0x0 }, + { 0x0060, 0x0034, 0x0 } }, + { { 0x0d15, 0x0d4a, 0x0 }, + { 0x005e, 0x0034, 0x0058, 0x0 } }, + { { 0x0d30, 0x0d4d, 0x0d15, 0x0 }, + { 0x009e, 0x0034, 0x0 } }, + { { 0x0d15, 0x0d4d, 0x0d35, 0x0 }, + { 0x0034, 0x007a, 0x0 } }, + { { 0x0d15, 0x0d4d, 0x0d2f, 0x0 }, + { 0x0034, 0x00a2, 0x0 } }, + { { 0x0d1f, 0x0d4d, 0x0d1f, 0x0 }, + { 0x0069, 0x0 } }, + { { 0x0d26, 0x0d4d, 0x0d26, 0x0 }, + { 0x0074, 0x0 } }, + { { 0x0d30, 0x0d4d, 0x0 }, + { 0x009e, 0x0 } }, + { { 0x0d30, 0x0d4d, 0x200c, 0x0 }, + { 0x009e, 0x0 } }, + { { 0x0d30, 0x0d4d, 0x200d, 0x0 }, + { 0x009e, 0x0 } }, + { { 0xd15, 0xd46, 0xd3e, 0x0 }, + { 0x5e, 0x34, 0x58, 0x0 } }, + { { 0xd15, 0xd47, 0xd3e, 0x0 }, + { 0x5f, 0x34, 0x58, 0x0 } }, + { { 0xd15, 0xd46, 0xd57, 0x0 }, + { 0x5e, 0x34, 0x65, 0x0 } }, + { { 0xd15, 0xd57, 0x0 }, + { 0x34, 0x65, 0x0 } }, + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find AkrutiMal2", SkipAll); + } + } + { + if (QFontDatabase().families(QFontDatabase::Malayalam).contains("Rachana")) { + QFont f("Rachana"); + const ShapeTable shape_table [] = { + { { 0xd37, 0xd4d, 0xd1f, 0xd4d, 0xd30, 0xd40, 0x0 }, + { 0x385, 0xa3, 0x0 } }, + { { 0xd2f, 0xd4d, 0xd15, 0xd4d, 0xd15, 0xd41, 0x0 }, + { 0x2ff, 0x0 } }, + { { 0xd33, 0xd4d, 0xd33, 0x0 }, + { 0x3f8, 0x0 } }, + { { 0xd2f, 0xd4d, 0xd15, 0xd4d, 0xd15, 0xd41, 0x0 }, + { 0x2ff, 0x0 } }, + { { 0xd30, 0xd4d, 0x200d, 0xd35, 0xd4d, 0xd35, 0x0 }, + { 0xf3, 0x350, 0x0 } }, + + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Rachana", SkipAll); + } + } + +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + +void tst_QTextScriptEngine::sinhala() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Sinhala).contains("Malithi Web")) { + QFont f("Malithi Web"); + const ShapeTable shape_table [] = { + { { 0xd9a, 0xdd9, 0xdcf, 0x0 }, + { 0x4a, 0x61, 0x42, 0x0 } }, + { { 0xd9a, 0xdd9, 0xddf, 0x0 }, + { 0x4a, 0x61, 0x50, 0x0 } }, + { { 0xd9a, 0xdd9, 0xdca, 0x0 }, + { 0x4a, 0x62, 0x0 } }, + { { 0xd9a, 0xddc, 0xdca, 0x0 }, + { 0x4a, 0x61, 0x42, 0x41, 0x0 } }, + { { 0xd9a, 0xdda, 0x0 }, + { 0x4a, 0x62, 0x0 } }, + { { 0xd9a, 0xddd, 0x0 }, + { 0x4a, 0x61, 0x42, 0x41, 0x0 } }, + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Malithi Web", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + + +void tst_QTextScriptEngine::khmer() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Khmer).contains("Khmer OS")) { + QFont f("Khmer OS"); + const ShapeTable shape_table [] = { + { { 0x179a, 0x17cd, 0x0 }, + { 0x24c, 0x27f, 0x0 } }, + { { 0x179f, 0x17c5, 0x0 }, + { 0x273, 0x203, 0x0 } }, + { { 0x1790, 0x17d2, 0x1784, 0x17c3, 0x0 }, + { 0x275, 0x242, 0x182, 0x0 } }, + { { 0x179a, 0x0 }, + { 0x24c, 0x0 } }, + { { 0x1781, 0x17d2, 0x1798, 0x17c2, 0x0 }, + { 0x274, 0x233, 0x197, 0x0 } }, + { { 0x1798, 0x17b6, 0x0 }, + { 0x1cb, 0x0 } }, + { { 0x179a, 0x17b8, 0x0 }, + { 0x24c, 0x26a, 0x0 } }, + { { 0x1787, 0x17b6, 0x0 }, + { 0x1ba, 0x0 } }, + { { 0x1798, 0x17d2, 0x1796, 0x17bb, 0x0 }, + { 0x24a, 0x195, 0x26d, 0x0 } }, + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Khmer OS", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + +void tst_QTextScriptEngine::linearB() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Any).contains("Penuturesu")) { + QFont f("Penuturesu"); + const ShapeTable shape_table [] = { + { { 0xd800, 0xdc01, 0xd800, 0xdc02, 0xd800, 0xdc03, 0 }, + { 0x5, 0x6, 0x7, 0 } }, + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find Penuturesu", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + +#if defined(Q_WS_X11) +static bool decomposedShaping( const QFont &f, const QChar &ch) +{ + QString str = QString().append(ch); + QTextLayout layout(str, f); + QTextEngine *e = layout.d; + e->itemize(); + e->shape(0); + + QTextLayout decomposed(str.normalized(QString::NormalizationForm_D), f); + QTextEngine *de = decomposed.d; + de->itemize(); + de->shape(0); + + if( e->layoutData->items[0].num_glyphs != de->layoutData->items[0].num_glyphs ) + goto error; + + for (int i = 0; i < e->layoutData->items[0].num_glyphs; ++i) { + if ((e->layoutData->glyphLayout.glyphs[i] & 0xffffff) != (de->layoutData->glyphLayout.glyphs[i] & 0xffffff)) + goto error; + } + return true; + error: + qDebug("%s: decomposedShaping of char %4x failed, nglyphs=%d, decomposed nglyphs %d", + f.family().toLatin1().constData(), + ch.unicode(), + e->layoutData->items[0].num_glyphs, + de->layoutData->items[0].num_glyphs); + + str = ""; + int i = 0; + while (i < e->layoutData->items[0].num_glyphs) { + str += QString("%1 ").arg(e->layoutData->glyphLayout.glyphs[i], 4, 16); + ++i; + } + qDebug(" composed glyph result = %s", str.toLatin1().constData()); + str = ""; + i = 0; + while (i < de->layoutData->items[0].num_glyphs) { + str += QString("%1 ").arg(de->layoutData->glyphLayout.glyphs[i], 4, 16); + ++i; + } + qDebug(" decomposed glyph result = %s", str.toLatin1().constData()); + return false; +} +#endif + + +void tst_QTextScriptEngine::greek() +{ +#if defined(Q_WS_X11) + if (!haveTestFonts) { + QSKIP("Test fonts are not available", SkipAll); + } + + { + if (QFontDatabase().families(QFontDatabase::Any).contains("DejaVu Sans")) { + QFont f("DejaVu Sans"); + for (int uc = 0x1f00; uc <= 0x1fff; ++uc) { + QString str; + str.append(uc); + if (str.normalized(QString::NormalizationForm_D).normalized(QString::NormalizationForm_C) != str) { + //qDebug() << "skipping" << hex << uc; + continue; + } + if (uc == 0x1fc1 || uc == 0x1fed) + continue; + QVERIFY( decomposedShaping(f, QChar(uc)) ); + } + } else { + QSKIP("couldn't find DejaVu Sans", SkipAll); + } + } + + { + if (QFontDatabase().families(QFontDatabase::Any).contains("SBL Greek")) { + QFont f("SBL Greek"); + for (int uc = 0x1f00; uc <= 0x1fff; ++uc) { + QString str; + str.append(uc); + if (str.normalized(QString::NormalizationForm_D).normalized(QString::NormalizationForm_C) != str) { + //qDebug() << "skipping" << hex << uc; + continue; + } + if (uc == 0x1fc1 || uc == 0x1fed) + continue; + QVERIFY( decomposedShaping(f, QChar(uc) ) ); + + } + + const ShapeTable shape_table [] = { + { { 0x3b1, 0x300, 0x313, 0x0 }, + { 0xb8, 0x3d3, 0x3c7, 0x0 } }, + { { 0x3b1, 0x313, 0x300, 0x0 }, + { 0xd4, 0x0 } }, + + { {0}, {0} } + }; + + + const ShapeTable *s = shape_table; + while (s->unicode[0]) { + QVERIFY( shaping(f, s) ); + ++s; + } + } else { + QSKIP("couldn't find SBL_grk", SkipAll); + } + } +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + +void tst_QTextScriptEngine::controlInSyllable_qtbug14204() +{ +#if defined(Q_WS_X11) + QString s; + s.append(QChar(0x0915)); + s.append(QChar(0x094d)); + s.append(QChar(0x200d)); + s.append(QChar(0x0915)); + + QTextLayout layout(s); + QTextEngine *e = layout.d; + e->itemize(); + e->shape(0); + + QVERIFY(e->layoutData->items[0].num_glyphs == 2); + QVERIFY(e->layoutData->glyphLayout.advances_x[1] != 0); +#else + QSKIP("X11 specific test", SkipAll); +#endif +} + +void tst_QTextScriptEngine::combiningMarks_qtbug15675() +{ +#if defined(Q_WS_MAC) && defined(QT_MAC_USE_COCOA) + QString s; + s.append(QChar(0x0061)); + s.append(QChar(0x0062)); + s.append(QChar(0x0300)); + s.append(QChar(0x0063)); + + QFont font("Monaco"); + QTextLayout layout(s, font); + QTextEngine *e = layout.d; + e->itemize(); + e->shape(0); + + QVERIFY(e->layoutData->items[0].num_glyphs == 4); + QVERIFY(e->layoutData->glyphLayout.advances_y[2] > 0); +#elif defined(Q_WS_X11) + QFontDatabase db; + + if (!db.families().contains("DejaVu Sans Mono")) { + QSKIP("Required font (DejaVu Sans Mono) doesn't exist, skip test.", SkipAll); + return; + } + + QString s; + s.append(QChar(0x0062)); + s.append(QChar(0x0332)); + s.append(QChar(0x0063)); + + QTextLayout layout(s, QFont("DejaVu Sans Mono")); + QTextEngine *e = layout.d; + e->itemize(); + e->shape(0); + + QVERIFY(e->layoutData->items[0].num_glyphs == 3); + QVERIFY(e->layoutData->glyphLayout.advances_x[1] == 0); +#else + QSKIP("X11/Mac specific test", SkipAll); +#endif +} + +void tst_QTextScriptEngine::mirroredChars_data() +{ + QTest::addColumn<int>("hintingPreference"); + + QTest::newRow("Default hinting") << int(QFont::PreferDefaultHinting); + QTest::newRow("No hinting") << int(QFont::PreferNoHinting); + QTest::newRow("Vertical hinting") << int(QFont::PreferVerticalHinting); + QTest::newRow("Full hinting") << int(QFont::PreferFullHinting); +} + +void tst_QTextScriptEngine::mirroredChars() +{ +#if defined(Q_WS_MAC) + QSKIP("Not supported on Mac", SkipAll); +#endif + QFETCH(int, hintingPreference); + + QFont font; + font.setHintingPreference(QFont::HintingPreference(hintingPreference)); + + QString s; + s.append(QLatin1Char('(')); + s.append(QLatin1Char(')')); + + HB_Glyph leftParenthesis; + HB_Glyph rightParenthesis; + { + QTextLayout layout(s); + layout.beginLayout(); + layout.createLine(); + layout.endLayout(); + + QTextEngine *e = layout.engine(); + e->itemize(); + e->shape(0); + QCOMPARE(e->layoutData->items[0].num_glyphs, ushort(2)); + + const QGlyphLayout &glyphLayout = e->layoutData->glyphLayout; + leftParenthesis = glyphLayout.glyphs[0]; + rightParenthesis = glyphLayout.glyphs[1]; + } + + { + QTextLayout layout(s); + layout.setFlags(Qt::TextForceRightToLeft); + + QTextEngine *e = layout.engine(); + e->itemize(); + e->shape(0); + QCOMPARE(e->layoutData->items[0].num_glyphs, ushort(2)); + + const QGlyphLayout &glyphLayout = e->layoutData->glyphLayout; + QCOMPARE(glyphLayout.glyphs[0], rightParenthesis); + QCOMPARE(glyphLayout.glyphs[1], leftParenthesis); + } +} + +QTEST_MAIN(tst_QTextScriptEngine) +#include "tst_qtextscriptengine.moc" diff --git a/tests/auto/gui/text/qtexttable/.gitignore b/tests/auto/gui/text/qtexttable/.gitignore new file mode 100644 index 0000000000..876aa75c0a --- /dev/null +++ b/tests/auto/gui/text/qtexttable/.gitignore @@ -0,0 +1 @@ +tst_qtexttable diff --git a/tests/auto/gui/text/qtexttable/qtexttable.pro b/tests/auto/gui/text/qtexttable/qtexttable.pro new file mode 100644 index 0000000000..611b706fc4 --- /dev/null +++ b/tests/auto/gui/text/qtexttable/qtexttable.pro @@ -0,0 +1,6 @@ +load(qttest_p4) +QT += widgets +SOURCES += tst_qtexttable.cpp + + + diff --git a/tests/auto/gui/text/qtexttable/tst_qtexttable.cpp b/tests/auto/gui/text/qtexttable/tst_qtexttable.cpp new file mode 100644 index 0000000000..83343811d8 --- /dev/null +++ b/tests/auto/gui/text/qtexttable/tst_qtexttable.cpp @@ -0,0 +1,1004 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtTest/QtTest> + + +#include <qtextdocument.h> +#include <qtextdocumentfragment.h> +#include <qtexttable.h> +#include <qdebug.h> +#include <qtextcursor.h> +#include <qtextdocument.h> +#include <qtextedit.h> + +//TESTED_FILES= + +typedef QList<int> IntList; +Q_DECLARE_METATYPE(IntList) + +QT_FORWARD_DECLARE_CLASS(QTextDocument) + +class tst_QTextTable : public QObject +{ + Q_OBJECT + +public: + tst_QTextTable(); + + +public slots: + void init(); + void cleanup(); +private slots: + void cursorPositioning(); + void variousTableModifications(); + void tableShrinking(); + void spans(); + void variousModifications2(); + void tableManager_undo(); + void tableManager_removeCell(); + void rowAt(); + void rowAtWithSpans(); + void multiBlockCells(); + void insertRows(); + void deleteInTable(); + void mergeCells(); + void mergeAndInsert(); + void splitCells(); + void blocksForTableShouldHaveEmptyFormat(); + void removeTableByRemoveRows(); + void removeTableByRemoveColumns(); + void setCellFormat(); + void removeRows1(); + void removeRows2(); + void removeRows3(); + void removeRows4(); + void removeRows5(); + void removeColumns1(); + void removeColumns2(); + void removeColumns3(); + void removeColumns4(); + void removeColumns5(); + void removeColumnsInTableWithMergedRows(); + void QTBUG11282_insertBeforeMergedEnding_data(); + void QTBUG11282_insertBeforeMergedEnding(); + +private: + QTextTable *create2x2Table(); + QTextTable *create4x4Table(); + + QTextTable *createTable(int rows, int cols); + + QTextDocument *doc; + QTextCursor cursor; +}; + +tst_QTextTable::tst_QTextTable() +{} + +void tst_QTextTable::init() +{ + doc = new QTextDocument; + cursor = QTextCursor(doc); +} + +void tst_QTextTable::cleanup() +{ + cursor = QTextCursor(); + delete doc; + doc = 0; +} + +void tst_QTextTable::cursorPositioning() +{ + // ensure the cursor is placed at the beginning of the first cell upon + // table creation + QTextTable *table = cursor.insertTable(2, 2); + + QVERIFY(cursor == table->cellAt(0, 0).firstCursorPosition()); + QVERIFY(table->cellAt(0, 0).firstPosition() == table->firstPosition()); +} + +void tst_QTextTable::variousTableModifications() +{ + QTextTableFormat tableFmt; + + QTextTable *tab = cursor.insertTable(2, 2, tableFmt); + QVERIFY(doc->toPlainText().length() == 5); + QVERIFY(tab == cursor.currentTable()); + QVERIFY(tab->columns() == 2); + QVERIFY(tab->rows() == 2); + + QVERIFY(cursor.position() == 1); + QTextCharFormat fmt = cursor.charFormat(); + QVERIFY(fmt.objectIndex() == -1); + QTextTableCell cell = tab->cellAt(cursor); + QVERIFY(cell.isValid()); + QVERIFY(cell.row() == 0); + QVERIFY(cell.column() == 0); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.position() == 2); + fmt = cursor.charFormat(); + QVERIFY(fmt.objectIndex() == -1); + cell = tab->cellAt(cursor); + QVERIFY(cell.isValid()); + QVERIFY(cell.row() == 0); + QVERIFY(cell.column() == 1); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.position() == 3); + fmt = cursor.charFormat(); + QVERIFY(fmt.objectIndex() == -1); + cell = tab->cellAt(cursor); + QVERIFY(cell.isValid()); + QVERIFY(cell.row() == 1); + QVERIFY(cell.column() == 0); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.position() == 4); + fmt = cursor.charFormat(); + QVERIFY(fmt.objectIndex() == -1); + cell = tab->cellAt(cursor); + QVERIFY(cell.isValid()); + QVERIFY(cell.row() == 1); + QVERIFY(cell.column() == 1); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.position() == 5); + fmt = cursor.charFormat(); + QVERIFY(fmt.objectIndex() == -1); + cell = tab->cellAt(cursor); + QVERIFY(!cell.isValid()); + + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.position() == 5); + + // check we can't delete the cells with the cursor + cursor.movePosition(QTextCursor::Start); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.position() == 1); + cursor.deleteChar(); + QVERIFY(doc->toPlainText().length() == 5); + cursor.movePosition(QTextCursor::NextBlock); + QVERIFY(cursor.position() == 2); + cursor.deleteChar(); + QVERIFY(doc->toPlainText().length() == 5); + cursor.deletePreviousChar(); + QVERIFY(cursor.position() == 2); + QVERIFY(doc->toPlainText().length() == 5); + + QTextTable *table = cursor.currentTable(); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 2); + + table->insertRows(2, 1); + QVERIFY(table->rows() == 3); + QVERIFY(table->columns() == 2); + QVERIFY(doc->toPlainText().length() == 7); + table->insertColumns(2, 2); + QVERIFY(table->rows() == 3); + QVERIFY(table->columns() == 4); + QVERIFY(doc->toPlainText().length() == 13); + + table->resize(4, 5); + QVERIFY(table->rows() == 4); + QVERIFY(table->columns() == 5); + QVERIFY(doc->toPlainText().length() == 21); +} + +void tst_QTextTable::tableShrinking() +{ + QTextTableFormat tableFmt; + + cursor.insertTable(3, 4, tableFmt); + QVERIFY(doc->toPlainText().length() == 13); + + QTextTable *table = cursor.currentTable(); + QVERIFY(table->rows() == 3); + QVERIFY(table->columns() == 4); + + table->removeRows(1, 1); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 4); + QVERIFY(doc->toPlainText().length() == 9); + table->removeColumns(1, 2); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 2); + QVERIFY(doc->toPlainText().length() == 5); + + table->resize(1, 1); + QVERIFY(table->rows() == 1); + QVERIFY(table->columns() == 1); + QVERIFY(doc->toPlainText().length() == 2); +} + +void tst_QTextTable::spans() +{ + QTextTableFormat tableFmt; + + cursor.insertTable(2, 2, tableFmt); + + QTextTable *table = cursor.currentTable(); + QVERIFY(table->cellAt(0, 0) != table->cellAt(0, 1)); + table->mergeCells(0, 0, 1, 2); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 2); + QVERIFY(table->cellAt(0, 0) == table->cellAt(0, 1)); + table->mergeCells(0, 0, 2, 2); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 2); +} + +void tst_QTextTable::variousModifications2() +{ + QTextTableFormat tableFmt; + + cursor.insertTable(2, 5, tableFmt); + QVERIFY(doc->toPlainText().length() == 11); + QTextTable *table = cursor.currentTable(); + QVERIFY(cursor.position() == 1); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 5); + + table->insertColumns(0, 1); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 6); + table->insertColumns(6, 1); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 7); + + table->insertRows(0, 1); + QVERIFY(table->rows() == 3); + QVERIFY(table->columns() == 7); + table->insertRows(3, 1); + QVERIFY(table->rows() == 4); + QVERIFY(table->columns() == 7); + + table->removeRows(0, 1); + QVERIFY(table->rows() == 3); + QVERIFY(table->columns() == 7); + table->removeRows(2, 1); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 7); + + table->removeColumns(0, 1); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 6); + table->removeColumns(5, 1); + QVERIFY(table->rows() == 2); + QVERIFY(table->columns() == 5); + + tableFmt = table->format(); + table->insertColumns(2, 1); + table->setFormat(tableFmt); + table->insertColumns(2, 1); + QVERIFY(table->columns() == 7); +} + +void tst_QTextTable::tableManager_undo() +{ + QTextTableFormat fmt; + fmt.setBorder(10); + QTextTable *table = cursor.insertTable(2, 2, fmt); + QVERIFY(table); + + QVERIFY(table->format().border() == 10); + + fmt.setBorder(20); + table->setFormat(fmt); + + QVERIFY(table->format().border() == 20); + + doc->undo(); + + QVERIFY(table->format().border() == 10); +} + +void tst_QTextTable::tableManager_removeCell() +{ + // essentially a test for TableManager::removeCell, in particular to remove empty items from the rowlist. + // If it fails it'll triger assertions inside TableManager. Yeah, not pretty, should VERIFY here ;( + cursor.insertTable(2, 2, QTextTableFormat()); + doc->undo(); + // ### + QVERIFY(true); +} + +void tst_QTextTable::rowAt() +{ + // test TablePrivate::rowAt + QTextTable *table = cursor.insertTable(4, 2); + + QCOMPARE(table->rows(), 4); + QCOMPARE(table->columns(), 2); + + QTextCursor cell00Cursor = table->cellAt(0, 0).firstCursorPosition(); + QTextCursor cell10Cursor = table->cellAt(1, 0).firstCursorPosition(); + QTextCursor cell20Cursor = table->cellAt(2, 0).firstCursorPosition(); + QTextCursor cell21Cursor = table->cellAt(2, 1).firstCursorPosition(); + QTextCursor cell30Cursor = table->cellAt(3, 0).firstCursorPosition(); + QVERIFY(table->cellAt(cell00Cursor).firstCursorPosition() == cell00Cursor); + QVERIFY(table->cellAt(cell10Cursor).firstCursorPosition() == cell10Cursor); + QVERIFY(table->cellAt(cell20Cursor).firstCursorPosition() == cell20Cursor); + QVERIFY(table->cellAt(cell30Cursor).firstCursorPosition() == cell30Cursor); + + table->mergeCells(1, 0, 2, 1); + + QCOMPARE(table->rows(), 4); + QCOMPARE(table->columns(), 2); + + QVERIFY(cell00Cursor == table->cellAt(0, 0).firstCursorPosition()); + QVERIFY(cell10Cursor == table->cellAt(1, 0).firstCursorPosition()); + QVERIFY(cell10Cursor == table->cellAt(2, 0).firstCursorPosition()); + QVERIFY(cell21Cursor == table->cellAt(2, 1).firstCursorPosition()); + QVERIFY(cell30Cursor == table->cellAt(3, 0).firstCursorPosition()); + + table->mergeCells(1, 0, 2, 2); + + QCOMPARE(table->rows(), 4); + QCOMPARE(table->columns(), 2); + + QVERIFY(cell00Cursor == table->cellAt(0, 0).firstCursorPosition()); + QVERIFY(cell00Cursor == table->cellAt(0, 0).firstCursorPosition()); + QVERIFY(cell10Cursor == table->cellAt(1, 0).firstCursorPosition()); + QVERIFY(cell10Cursor == table->cellAt(1, 1).firstCursorPosition()); + QVERIFY(cell10Cursor == table->cellAt(2, 0).firstCursorPosition()); + QVERIFY(cell10Cursor == table->cellAt(2, 1).firstCursorPosition()); + QVERIFY(cell30Cursor == table->cellAt(3, 0).firstCursorPosition()); +} + +void tst_QTextTable::rowAtWithSpans() +{ + QTextTable *table = cursor.insertTable(2, 2); + + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); + + table->mergeCells(0, 0, 2, 1); + QVERIFY(table->cellAt(0, 0).rowSpan() == 2); + + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); + + table->mergeCells(0, 0, 2, 2); + QVERIFY(table->cellAt(0, 0).columnSpan() == 2); + + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); +} + +void tst_QTextTable::multiBlockCells() +{ + // little testcase for multi-block cells + QTextTable *table = cursor.insertTable(2, 2); + + QVERIFY(cursor == table->cellAt(0, 0).firstCursorPosition()); + + cursor.insertText("Hello"); + cursor.insertBlock(QTextBlockFormat()); + cursor.insertText("World"); + + cursor.movePosition(QTextCursor::Left); + QVERIFY(table->cellAt(0, 0) == table->cellAt(cursor)); +} + +void tst_QTextTable::insertRows() +{ + // little testcase for multi-block cells + QTextTable *table = cursor.insertTable(2, 2); + + QVERIFY(cursor == table->cellAt(0, 0).firstCursorPosition()); + + table->insertRows(0, 1); + QVERIFY(table->rows() == 3); + + table->insertRows(1, 1); + QVERIFY(table->rows() == 4); + + table->insertRows(-1, 1); + QVERIFY(table->rows() == 5); + + table->insertRows(5, 2); + QVERIFY(table->rows() == 7); + +} + +void tst_QTextTable::deleteInTable() +{ + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("Blah"); + table->cellAt(0, 1).firstCursorPosition().insertText("Foo"); + table->cellAt(1, 0).firstCursorPosition().insertText("Bar"); + table->cellAt(1, 1).firstCursorPosition().insertText("Hah"); + + cursor = table->cellAt(1, 1).firstCursorPosition(); + cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::KeepAnchor); + + QCOMPARE(table->cellAt(cursor.position()).row(), 1); + QCOMPARE(table->cellAt(cursor.position()).column(), 0); + + cursor.removeSelectedText(); + + QCOMPARE(table->columns(), 2); + QCOMPARE(table->rows(), 2); + + // verify table is still all in shape. Only the text inside should get deleted + for (int row = 0; row < table->rows(); ++row) + for (int col = 0; col < table->columns(); ++col) { + const QTextTableCell cell = table->cellAt(row, col); + QVERIFY(cell.isValid()); + QCOMPARE(cell.rowSpan(), 1); + QCOMPARE(cell.columnSpan(), 1); + } +} + +QTextTable *tst_QTextTable::create2x2Table() +{ + cleanup(); + init(); + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("Blah"); + table->cellAt(0, 1).firstCursorPosition().insertText("Foo"); + table->cellAt(1, 0).firstCursorPosition().insertText("Bar"); + table->cellAt(1, 1).firstCursorPosition().insertText("Hah"); + return table; +} + +QTextTable *tst_QTextTable::create4x4Table() +{ + cleanup(); + init(); + QTextTable *table = cursor.insertTable(4, 4); + table->cellAt(0, 0).firstCursorPosition().insertText("Blah"); + table->cellAt(0, 1).firstCursorPosition().insertText("Foo"); + table->cellAt(1, 0).firstCursorPosition().insertText("Bar"); + table->cellAt(1, 1).firstCursorPosition().insertText("Hah"); + return table; +} + +QTextTable *tst_QTextTable::createTable(int rows, int cols) +{ + cleanup(); + init(); + QTextTable *table = cursor.insertTable(rows, cols); + return table; +} + +void tst_QTextTable::mergeCells() +{ + QTextTable *table = create4x4Table(); + + table->mergeCells(1, 1, 1, 2); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + + table->mergeCells(1, 1, 2, 2); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 2)); + + table = create4x4Table(); + + table->mergeCells(1, 1, 2, 1); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + + table->mergeCells(1, 1, 2, 2); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 2)); + + table = create4x4Table(); + + table->mergeCells(1, 1, 2, 2); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 2)); + + // should do nothing + table->mergeCells(1, 1, 1, 1); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 2)); + + table = create2x2Table(); + + table->mergeCells(0, 1, 2, 1); + table->mergeCells(0, 0, 2, 2); + QVERIFY(table->cellAt(0, 0) == table->cellAt(0, 1)); + QVERIFY(table->cellAt(0, 0) == table->cellAt(1, 0)); + QVERIFY(table->cellAt(0, 0) == table->cellAt(1, 1)); + + QTextBlock block = table->cellAt(0, 0).firstCursorPosition().block(); + + QVERIFY(block.text() == "Blah Foo"); + QVERIFY(block.next().text() == "Hah"); + QVERIFY(block.next().next().text() == "Bar"); + + table = create4x4Table(); + + QTextCursor cursor = table->cellAt(3, 3).firstCursorPosition(); + QTextTable *t2 = cursor.insertTable(2, 2); + t2->cellAt(0, 0).firstCursorPosition().insertText("Test"); + + table->mergeCells(2, 2, 2, 2); + cursor = table->cellAt(2, 2).firstCursorPosition(); + + QTextFrame *frame = cursor.currentFrame(); + + QTextFrame::iterator it = frame->begin(); + + // find the embedded table + while (it != frame->end() && !it.currentFrame()) + ++it; + + table = qobject_cast<QTextTable *>(it.currentFrame()); + + QVERIFY(table); + + if (table) { + cursor = table->cellAt(0, 0).firstCursorPosition(); + + QVERIFY(cursor.block().text() == "Test"); + } + + table = create2x2Table(); + + table->mergeCells(0, 1, 2, 1); + + QVERIFY(table->cellAt(0, 0) != table->cellAt(0, 1)); + QVERIFY(table->cellAt(0, 1) == table->cellAt(1, 1)); + + // should do nothing + table->mergeCells(0, 0, 1, 2); + + QVERIFY(table->cellAt(0, 0) != table->cellAt(0, 1)); + QVERIFY(table->cellAt(0, 1) == table->cellAt(1, 1)); +} + +void tst_QTextTable::mergeAndInsert() +{ + QTextTable *table = cursor.insertTable(4,3); + table->mergeCells(0,1,3,2); + table->mergeCells(3,0,1,3); + //Don't crash ! + table->insertColumns(1,2); + QCOMPARE(table->columns(), 5); +} + +void tst_QTextTable::splitCells() +{ + QTextTable *table = create4x4Table(); + table->mergeCells(1, 1, 2, 2); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 2)); + + table->splitCell(1, 1, 1, 2); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 2)); + + table->splitCell(1, 1, 1, 1); + QVERIFY(table->cellAt(1, 1) != table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 2)); + + + table = create4x4Table(); + table->mergeCells(1, 1, 2, 2); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 2)); + + table->splitCell(1, 1, 2, 1); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 2)); + + table->splitCell(1, 1, 1, 1); + QVERIFY(table->cellAt(1, 1) != table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 2)); + + + table = create4x4Table(); + table->mergeCells(1, 1, 2, 2); + QVERIFY(table->cellAt(1, 1) == table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) == table->cellAt(2, 2)); + + table->splitCell(1, 1, 1, 1); + QVERIFY(table->cellAt(1, 1) != table->cellAt(1, 2)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 1)); + QVERIFY(table->cellAt(1, 1) != table->cellAt(2, 2)); + + table = createTable(2, 5); + table->mergeCells(0, 0, 2, 1); + table->mergeCells(0, 1, 2, 1); + QVERIFY(table->cellAt(0, 0) == table->cellAt(1, 0)); + QVERIFY(table->cellAt(0, 1) == table->cellAt(1, 1)); + table->splitCell(0, 0, 1, 1); + QVERIFY(table->cellAt(0, 0) != table->cellAt(1, 0)); + QVERIFY(table->cellAt(0, 1) == table->cellAt(1, 1)); + + table = createTable(2, 5); + table->mergeCells(0, 4, 2, 1); + QVERIFY(table->cellAt(0, 4) == table->cellAt(1, 4)); + + table->splitCell(0, 4, 1, 1); + QVERIFY(table->cellAt(0, 4) != table->cellAt(1, 4)); +} + +void tst_QTextTable::blocksForTableShouldHaveEmptyFormat() +{ + QTextBlockFormat fmt; + fmt.setProperty(QTextFormat::UserProperty, true); + cursor.insertBlock(fmt); + QVERIFY(cursor.blockFormat().hasProperty(QTextFormat::UserProperty)); + + QTextTable *table = cursor.insertTable(1, 1); + QVERIFY(!table->cellAt(0, 0).firstCursorPosition().blockFormat().hasProperty(QTextFormat::UserProperty)); + + int userPropCount = 0; + for (QTextBlock block = doc->begin(); + block.isValid(); block = block.next()) { + if (block.blockFormat().hasProperty(QTextFormat::UserProperty)) + userPropCount++; + } + QCOMPARE(userPropCount, 1); +} + +void tst_QTextTable::removeTableByRemoveRows() +{ + QPointer<QTextTable> table1 = QTextCursor(cursor).insertTable(4, 4); + QPointer<QTextTable> table2 = QTextCursor(cursor).insertTable(4, 4); + QPointer<QTextTable> table3 = QTextCursor(cursor).insertTable(4, 4); + + QVERIFY(table1); + QVERIFY(table2); + QVERIFY(table3); + + table2->removeRows(1, 1); + + QVERIFY(table1); + QVERIFY(table2); + QVERIFY(table3); + + table2->removeRows(0, table2->rows()); + + QVERIFY(table1); + QVERIFY(!table2); + QVERIFY(table3); +} + +void tst_QTextTable::removeTableByRemoveColumns() +{ + QPointer<QTextTable> table1 = QTextCursor(cursor).insertTable(4, 4); + QPointer<QTextTable> table2 = QTextCursor(cursor).insertTable(4, 4); + QPointer<QTextTable> table3 = QTextCursor(cursor).insertTable(4, 4); + + QVERIFY(table1); + QVERIFY(table2); + QVERIFY(table3); + + table2->removeColumns(1, 1); + + QVERIFY(table1); + QVERIFY(table2); + QVERIFY(table3); + + table2->removeColumns(0, table2->columns()); + + QVERIFY(table1); + QVERIFY(!table2); + QVERIFY(table3); +} + +void tst_QTextTable::setCellFormat() +{ + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + QTextTableCell cell = table->cellAt(0, 0); + QTextCharFormat fmt; + fmt.setObjectIndex(23); + fmt.setBackground(Qt::blue); + fmt.setTableCellColumnSpan(25); + fmt.setTableCellRowSpan(42); + cell.setFormat(fmt); + QVERIFY(cell.format().background().color() == QColor(Qt::blue)); + QCOMPARE(cell.format().tableCellColumnSpan(), 1); + QCOMPARE(cell.format().tableCellRowSpan(), 1); +} + +void tst_QTextTable::removeRows1() +{ + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + table->removeRows(0, 1); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("Third")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Fourth")); +} + +void tst_QTextTable::removeRows2() +{ + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + table->removeRows(1, 1); + QCOMPARE(table->rows(), 1); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Second")); +} + +void tst_QTextTable::removeRows3() +{ + QTextTable *table = cursor.insertTable(3, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + table->cellAt(2, 0).firstCursorPosition().insertText("Fifth"); + table->cellAt(2, 1).firstCursorPosition().insertText("Sixth"); + table->removeRows(1, 1); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Second")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Fifth")); + QCOMPARE(table->cellAt(1, 1).firstCursorPosition().block().text(), QString("Sixth")); +} + +void tst_QTextTable::removeRows4() +{ + QTextTable *table = cursor.insertTable(4, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + table->cellAt(2, 0).firstCursorPosition().insertText("Fifth"); + table->cellAt(2, 1).firstCursorPosition().insertText("Sixth"); + table->cellAt(3, 0).firstCursorPosition().insertText("Seventh"); + table->cellAt(3, 1).firstCursorPosition().insertText("Eighth"); + table->removeRows(1, 2); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Second")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Seventh")); + QCOMPARE(table->cellAt(1, 1).firstCursorPosition().block().text(), QString("Eighth")); +} + +void tst_QTextTable::removeRows5() +{ + QTextTable *table = cursor.insertTable(2,2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + table->insertRows(1,1); + table->mergeCells(1,0,1,2); + table->removeRows(1,1); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Second")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Third")); + QCOMPARE(table->cellAt(1, 1).firstCursorPosition().block().text(), QString("Fourth")); +} + +void tst_QTextTable::removeColumns1() +{ + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + table->removeColumns(0, 1); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("Second")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Fourth")); +} + +void tst_QTextTable::removeColumns2() +{ + QTextTable *table = cursor.insertTable(2, 2); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(1, 0).firstCursorPosition().insertText("Third"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fourth"); + table->removeColumns(1, 1); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 1); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Third")); +} + +void tst_QTextTable::removeColumns3() +{ + QTextTable *table = cursor.insertTable(2, 3); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(0, 2).firstCursorPosition().insertText("Third"); + table->cellAt(1, 0).firstCursorPosition().insertText("Fourth"); + table->cellAt(1, 1).firstCursorPosition().insertText("Fifth"); + table->cellAt(1, 2).firstCursorPosition().insertText("Sixth"); + table->removeColumns(1, 1); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Third")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Fourth")); + QCOMPARE(table->cellAt(1, 1).firstCursorPosition().block().text(), QString("Sixth")); +} + +void tst_QTextTable::removeColumns4() +{ + QTextTable *table = cursor.insertTable(2, 4); + table->cellAt(0, 0).firstCursorPosition().insertText("First"); + table->cellAt(0, 1).firstCursorPosition().insertText("Second"); + table->cellAt(0, 2).firstCursorPosition().insertText("Third"); + table->cellAt(0, 3).firstCursorPosition().insertText("Fourth"); + table->cellAt(1, 0).firstCursorPosition().insertText("Fifth"); + table->cellAt(1, 1).firstCursorPosition().insertText("Sixth"); + table->cellAt(1, 2).firstCursorPosition().insertText("Seventh"); + table->cellAt(1, 3).firstCursorPosition().insertText("Eighth"); + table->removeColumns(1, 2); + QCOMPARE(table->rows(), 2); + QCOMPARE(table->columns(), 2); + QCOMPARE(table->cellAt(0, 0).firstCursorPosition().block().text(), QString("First")); + QCOMPARE(table->cellAt(0, 1).firstCursorPosition().block().text(), QString("Fourth")); + QCOMPARE(table->cellAt(1, 0).firstCursorPosition().block().text(), QString("Fifth")); + QCOMPARE(table->cellAt(1, 1).firstCursorPosition().block().text(), QString("Eighth")); +} + +void tst_QTextTable::removeColumns5() +{ + QTextTable *table = cursor.insertTable(4, 4); + QTextCursor tc (doc); + tc.setPosition(table->cellAt(2,0).firstPosition()); + tc.setPosition(table->cellAt(3,1).firstPosition(), QTextCursor::KeepAnchor); + table->mergeCells(tc); + QCOMPARE(table->rows(), 4); + QCOMPARE(table->columns(), 4); + QCOMPARE(table->cellAt(0, 0).firstPosition(), 1); + QCOMPARE(table->cellAt(0, 1).firstPosition(), 2); + QCOMPARE(table->cellAt(0, 2).firstPosition(), 3); + QCOMPARE(table->cellAt(0, 3).firstPosition(), 4); + QCOMPARE(table->cellAt(1, 0).firstPosition(), 5); + QCOMPARE(table->cellAt(1, 1).firstPosition(), 6); + QCOMPARE(table->cellAt(1, 2).firstPosition(), 7); + QCOMPARE(table->cellAt(1, 3).firstPosition(), 8); + QCOMPARE(table->cellAt(2, 0).firstPosition(), 9); + QCOMPARE(table->cellAt(2, 0).rowSpan(), 2); + QCOMPARE(table->cellAt(2, 0).columnSpan(), 2); + QCOMPARE(table->cellAt(2, 1).firstPosition(), 9); + QCOMPARE(table->cellAt(2, 2).firstPosition(), 10); + QCOMPARE(table->cellAt(2, 3).firstPosition(), 11); + QCOMPARE(table->cellAt(3, 0).firstPosition(), 9); + QCOMPARE(table->cellAt(3, 1).firstPosition(), 9); + QCOMPARE(table->cellAt(3, 2).firstPosition(), 12); + QCOMPARE(table->cellAt(3, 3).firstPosition(), 13); + + table->removeColumns(1, 1); + QCOMPARE(table->rows(), 4); + QCOMPARE(table->columns(), 3); + QCOMPARE(table->cellAt(0, 0).firstPosition(), 1); + QCOMPARE(table->cellAt(0, 1).firstPosition(), 2); + QCOMPARE(table->cellAt(0, 2).firstPosition(), 3); + QCOMPARE(table->cellAt(1, 0).firstPosition(), 4); + QCOMPARE(table->cellAt(1, 1).firstPosition(), 5); + QCOMPARE(table->cellAt(1, 2).firstPosition(), 6); + QCOMPARE(table->cellAt(2, 0).firstPosition(), 7); + QCOMPARE(table->cellAt(2, 0).rowSpan(), 2); + QCOMPARE(table->cellAt(2, 0).columnSpan(), 1); + QCOMPARE(table->cellAt(2, 1).firstPosition(), 8); + QCOMPARE(table->cellAt(2, 2).firstPosition(), 9); + QCOMPARE(table->cellAt(3, 0).firstPosition(), 7); + QCOMPARE(table->cellAt(3, 1).firstPosition(), 10); + QCOMPARE(table->cellAt(3, 2).firstPosition(), 11); +} + +void tst_QTextTable::removeColumnsInTableWithMergedRows() +{ + QTextTable *table = cursor.insertTable(3, 4); + table->mergeCells(0, 0, 1, 4); + QCOMPARE(table->rows(), 3); + QCOMPARE(table->columns(), 4); + + table->removeColumns(0, table->columns() - 1); + + QCOMPARE(table->rows(), 3); + QCOMPARE(table->columns(), 1); +} + +void tst_QTextTable::QTBUG11282_insertBeforeMergedEnding_data() +{ + QTest::addColumn<int>("rows"); + QTest::addColumn<int>("columns"); + QTest::addColumn<QList<int> >("merge"); + QTest::addColumn<QList<int> >("insert"); + + QTest::newRow("2x3, merge two, insert one") << 2 << 3 << (QList<int>() << 1 << 2 << 2) + << (QList<int>() << 1 << 1) ; + QTest::newRow("3x4, merge three, insert one") << 3 << 4 << (QList<int>() << 1 << 3 << 3) + << (QList<int>() << 1 << 1) ; + QTest::newRow("4x3, merge two, insert two") << 4 << 3 << (QList<int>() << 1 << 4 << 2) + << (QList<int>() << 1 << 2) ; + QTest::newRow("4x4, merge middle two, insert one") << 4 << 4 << (QList<int>() << 1 << 4 << 2) + << (QList<int>() << 1 << 1) ; +} + +void tst_QTextTable::QTBUG11282_insertBeforeMergedEnding() +{ + QFETCH(int, rows); + QFETCH(int, columns); + QFETCH(QList<int>, merge); + QFETCH(QList<int>, insert); + QTextTable *table = cursor.insertTable(rows, columns); + QTextEdit *textEdit = new QTextEdit; + textEdit->setDocument(doc); + textEdit->show(); + QTest::qWaitForWindowShown(textEdit); + table->mergeCells(0,merge.at(0), merge.at(1), merge.at(2)); + //Don't crash ! + table->insertColumns(insert.at(0), insert.at(1)); + //Check that the final size is what we expected + QCOMPARE(table->rows(), rows); + QCOMPARE(table->columns(), columns + insert.at(1)); + delete textEdit; +} + +QTEST_MAIN(tst_QTextTable) +#include "tst_qtexttable.moc" diff --git a/tests/auto/gui/text/qzip/.gitignore b/tests/auto/gui/text/qzip/.gitignore new file mode 100644 index 0000000000..2d7dfbe70c --- /dev/null +++ b/tests/auto/gui/text/qzip/.gitignore @@ -0,0 +1 @@ +tst_qzip diff --git a/tests/auto/gui/text/qzip/qzip.pro b/tests/auto/gui/text/qzip/qzip.pro new file mode 100644 index 0000000000..7631a3e93a --- /dev/null +++ b/tests/auto/gui/text/qzip/qzip.pro @@ -0,0 +1,12 @@ +load(qttest_p4) +QT += gui-private +SOURCES += tst_qzip.cpp + +wince*|symbian: { + addFiles.files = testdata + addFiles.path = . + DEPLOYMENT += addFiles + !symbian:DEFINES += SRCDIR=\\\".\\\" +} else { + DEFINES += SRCDIR=\\\"$$PWD\\\" +} diff --git a/tests/auto/gui/text/qzip/testdata/symlink.zip b/tests/auto/gui/text/qzip/testdata/symlink.zip Binary files differnew file mode 100644 index 0000000000..027f96477a --- /dev/null +++ b/tests/auto/gui/text/qzip/testdata/symlink.zip diff --git a/tests/auto/gui/text/qzip/testdata/test.zip b/tests/auto/gui/text/qzip/testdata/test.zip Binary files differnew file mode 100644 index 0000000000..a57ba4e2a9 --- /dev/null +++ b/tests/auto/gui/text/qzip/testdata/test.zip diff --git a/tests/auto/gui/text/qzip/tst_qzip.cpp b/tests/auto/gui/text/qzip/tst_qzip.cpp new file mode 100644 index 0000000000..0955ac069f --- /dev/null +++ b/tests/auto/gui/text/qzip/tst_qzip.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** 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, Nokia gives you certain additional +** rights. These rights are described in the Nokia 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. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QDebug> +#include <private/qzipwriter_p.h> +#include <private/qzipreader_p.h> + +#ifdef Q_OS_SYMBIAN +#define SRCDIR "." +#endif + +class tst_QZip : public QObject +{ + Q_OBJECT +public slots: + void init(); + void cleanup(); + +private slots: + void basicUnpack(); + void symlinks(); + void readTest(); + void createArchive(); +}; + +void tst_QZip::init() +{ +} + +void tst_QZip::cleanup() +{ +} + +void tst_QZip::basicUnpack() +{ + QZipReader zip(QString(SRCDIR) + "/testdata/test.zip", QIODevice::ReadOnly); + QList<QZipReader::FileInfo> files = zip.fileInfoList(); + QCOMPARE(files.count(), 2); + + QZipReader::FileInfo fi = files.at(0); + QVERIFY(fi.isValid()); + QCOMPARE(fi.filePath, QString("test/")); + QCOMPARE(uint(fi.isDir), (uint) 1); + QCOMPARE(uint(fi.isFile), (uint) 0); + QCOMPARE(uint(fi.isSymLink), (uint) 0); + + QCOMPARE(fi.permissions,QFile::Permissions( QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner + | QFile::ReadUser | QFile::WriteUser | QFile::ExeUser )); + + QCOMPARE(fi.lastModified, QDateTime::fromString("2005.11.11 13:08:02", "yyyy.MM.dd HH:mm:ss")); + + fi = files.at(1); + QVERIFY(fi.isValid()); + QCOMPARE(fi.filePath, QString("test/test.txt")); + QCOMPARE(uint(fi.isDir), (uint) 0); + QCOMPARE(uint(fi.isFile), (uint) 1); + QCOMPARE(uint(fi.isSymLink), (uint) 0); + + QVERIFY(fi.permissions == QFile::Permissions( QFile::ReadOwner | QFile::WriteOwner + | QFile::ReadUser | QFile::WriteUser )); + + QCOMPARE(fi.lastModified, QDateTime::fromString("2005.11.11 13:08:02", "yyyy.MM.dd HH:mm:ss")); + + QCOMPARE(zip.fileData("test/test.txt"), QByteArray("content\n")); + + fi = files.at(-1); + QVERIFY(!fi.isValid()); +} + +void tst_QZip::symlinks() +{ + QZipReader zip(QString(SRCDIR) + "/testdata/symlink.zip", QIODevice::ReadOnly); + QList<QZipReader::FileInfo> files = zip.fileInfoList(); + QCOMPARE(files.count(), 2); + + QZipReader::FileInfo fi = files.at(0); + QVERIFY(fi.isValid()); + QCOMPARE(fi.filePath, QString("symlink")); + QVERIFY(!fi.isDir); + QVERIFY(!fi.isFile); + QVERIFY(fi.isSymLink); + + QCOMPARE(zip.fileData("symlink"), QByteArray("destination")); + + fi = files.at(1); + QVERIFY(fi.isValid()); + QCOMPARE(fi.filePath, QString("destination")); + QVERIFY(!fi.isDir); + QVERIFY(fi.isFile); + QVERIFY(!fi.isSymLink); +} + +void tst_QZip::readTest() +{ + QZipReader zip("foobar.zip", QIODevice::ReadOnly); // non existing file. + QList<QZipReader::FileInfo> files = zip.fileInfoList(); + QCOMPARE(files.count(), 0); + QByteArray b = zip.fileData("foobar"); + QCOMPARE(b.size(), 0); +} + +void tst_QZip::createArchive() +{ + QBuffer buffer; + QZipWriter zip(&buffer); + QByteArray fileContents("simple file contents\nline2\n"); + zip.addFile("My Filename", fileContents); + zip.close(); + QByteArray zipFile = buffer.buffer(); + + // QFile f("createArchiveTest.zip"); f.open(QIODevice::WriteOnly); f.write(zipFile); f.close(); + + QBuffer buffer2(&zipFile); + QZipReader zip2(&buffer2); + QList<QZipReader::FileInfo> files = zip2.fileInfoList(); + QCOMPARE(files.count(), 1); + QZipReader::FileInfo file = files.at(0); + QCOMPARE(file.filePath, QString("My Filename")); + QCOMPARE(uint(file.isDir), (uint) 0); + QCOMPARE(uint(file.isFile), (uint) 1); + QCOMPARE(uint(file.isSymLink), (uint) 0); + QCOMPARE(file.permissions, QFile::Permissions(QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser | QFile::WriteUser) ); + QCOMPARE(file.size, (long long) 27); + QCOMPARE(zip2.fileData("My Filename"), fileContents); +} + +QTEST_MAIN(tst_QZip) +#include "tst_qzip.moc" diff --git a/tests/auto/gui/text/text.pro b/tests/auto/gui/text/text.pro new file mode 100644 index 0000000000..5055ab61a3 --- /dev/null +++ b/tests/auto/gui/text/text.pro @@ -0,0 +1,33 @@ +TEMPLATE=subdirs +SUBDIRS=\ + qabstracttextdocumentlayout \ + qcssparser \ + qfont \ + qfontdatabase \ + qfontmetrics \ + qglyphrun \ + qrawfont \ + qstatictext \ + qsyntaxhighlighter \ + qtextblock \ + qtextcursor \ + qtextdocument \ + qtextdocumentfragment \ + qtextdocumentlayout \ + qtextformat \ + qtextlayout \ + qtextlist \ + qtextobject \ + qtextpiecetable \ + qtextscriptengine \ + qtexttable \ + +contains(QT_CONFIG, OdfWriter):SUBDIRS += qzip qtextodfwriter + +win32:SUBDIRS -= qtextpiecetable + +!contains(QT_CONFIG, private_tests): SUBDIRS -= \ + qcssparser \ + qstatictext \ + qtextlayout \ + qtextpiecetable \ |