diff options
-rw-r--r-- | src/quick/items/qquicktextdocument.cpp | 63 | ||||
-rw-r--r-- | src/quick/items/qquicktextdocument_p.h | 6 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit.cpp | 72 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit_p_p.h | 1 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextdocument/data/sideBySideIndependent.qml | 21 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextdocument/data/sideBySideIndependentReverse.qml | 21 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp | 256 | ||||
-rw-r--r-- | tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp | 1 |
8 files changed, 393 insertions, 48 deletions
diff --git a/src/quick/items/qquicktextdocument.cpp b/src/quick/items/qquicktextdocument.cpp index 575f54df29..1fab2e6da0 100644 --- a/src/quick/items/qquicktextdocument.cpp +++ b/src/quick/items/qquicktextdocument.cpp @@ -16,6 +16,8 @@ QT_BEGIN_NAMESPACE +Q_LOGGING_CATEGORY(lcTextDoc, "qt.quick.textdocument") + using namespace Qt::StringLiterals; /*! @@ -209,7 +211,7 @@ void QQuickTextDocumentPrivate::load() QFile file(filePath); if (file.exists()) { #if QT_CONFIG(mimetype) - mimeType = QMimeDatabase().mimeTypeForFile(filePath); + QMimeType mimeType = QMimeDatabase().mimeTypeForFile(filePath); const bool isHtml = mimeType.inherits("text/html"_L1); const bool isMarkdown = mimeType.inherits("text/markdown"_L1); #else @@ -218,17 +220,24 @@ void QQuickTextDocumentPrivate::load() const bool isMarkdown = filePath.endsWith(".md"_L1, Qt::CaseInsensitive) || filePath.endsWith(".markdown"_L1, Qt::CaseInsensitive); #endif + if (isHtml) + detectedFormat = Qt::RichText; + else if (isMarkdown) + detectedFormat = Qt::MarkdownText; + else + detectedFormat = Qt::PlainText; if (file.open(QFile::ReadOnly | QFile::Text)) { setStatus(QQuickTextDocument::Status::Loading); QByteArray data = file.readAll(); doc->setBaseUrl(resolvedUrl.adjusted(QUrl::RemoveFilename)); + const bool plainText = editor->textFormat() == QQuickTextEdit::PlainText; #if QT_CONFIG(textmarkdownreader) - if (isMarkdown) { + if (!plainText && isMarkdown) { doc->setMarkdown(QString::fromUtf8(data)); } else #endif #ifndef QT_NO_TEXTHTMLPARSER - if (isHtml) { + if (!plainText && isHtml) { // If a user loads an HTML file, remember the encoding. // If the user then calls save() later, the same encoding will be used. encoding = QStringConverter::encodingForHtml(data); @@ -245,6 +254,12 @@ void QQuickTextDocumentPrivate::load() doc->setPlainText(QString::fromUtf8(data)); } setStatus(QQuickTextDocument::Status::Loaded); + qCDebug(lcTextDoc) << editor << "loaded" << filePath + << "as" << editor->textFormat() << "detected" << detectedFormat +#if QT_CONFIG(mimetype) + << "(file type" << mimeType << ')' +#endif + ; doc->setModified(false); return; } @@ -264,31 +279,44 @@ void QQuickTextDocumentPrivate::writeTo(const QUrl &fileUrl) const QString filePath = fileUrl.toLocalFile(); const bool sameUrl = fileUrl == url; + if (!sameUrl) { #if QT_CONFIG(mimetype) - const auto type = (sameUrl ? mimeType : QMimeDatabase().mimeTypeForUrl(fileUrl)); - const bool isHtml = type.inherits("text/html"_L1); - const bool isMarkdown = type.inherits("text/markdown"_L1); + const auto type = QMimeDatabase().mimeTypeForUrl(fileUrl); + if (type.inherits("text/html"_L1)) + detectedFormat = Qt::RichText; + else if (type.inherits("text/markdown"_L1)) + detectedFormat = Qt::MarkdownText; + else + detectedFormat = Qt::PlainText; #else - const bool isHtml = filePath.endsWith(".html"_L1, Qt::CaseInsensitive) || - filePath.endsWith(".htm"_L1, Qt::CaseInsensitive); - const bool isMarkdown = filePath.endsWith(".md"_L1, Qt::CaseInsensitive) || - filePath.endsWith(".markdown"_L1, Qt::CaseInsensitive); + if (filePath.endsWith(".html"_L1, Qt::CaseInsensitive) || + filePath.endsWith(".htm"_L1, Qt::CaseInsensitive)) + detectedFormat = Qt::RichText; + else if (filePath.endsWith(".md"_L1, Qt::CaseInsensitive) || + filePath.endsWith(".markdown"_L1, Qt::CaseInsensitive)) + detectedFormat = Qt::MarkdownText; + else + detectedFormat = Qt::PlainText; #endif + } QFile file(filePath); - if (!file.open(QFile::WriteOnly | QFile::Truncate | (isHtml ? QFile::NotOpen : QFile::Text))) { - qmlWarning(q) << QQuickTextDocument::tr("Cannot save: %1").arg(file.errorString()); + if (!file.open(QFile::WriteOnly | QFile::Truncate | + (detectedFormat == Qt::RichText ? QFile::NotOpen : QFile::Text))) { + qmlWarning(q) << QQuickTextDocument::tr("Cannot save:") << file.errorString(); setStatus(QQuickTextDocument::Status::WriteError); return; } setStatus(QQuickTextDocument::Status::Saving); QByteArray raw; + + switch (detectedFormat) { #if QT_CONFIG(textmarkdownwriter) - if (isMarkdown) { + case Qt::MarkdownText: raw = doc->toMarkdown().toUtf8(); - } else + break; #endif #ifndef QT_NO_TEXTHTMLPARSER - if (isHtml) { + case Qt::RichText: if (sameUrl && encoding) { QStringEncoder enc(*encoding); raw = enc.encode(doc->toHtml()); @@ -296,10 +324,11 @@ void QQuickTextDocumentPrivate::writeTo(const QUrl &fileUrl) // default to UTF-8 unless the user is saving the same file as previously loaded raw = doc->toHtml().toUtf8(); } - } else + break; #endif - { + default: raw = doc->toPlainText().toUtf8(); + break; } file.write(raw); diff --git a/src/quick/items/qquicktextdocument_p.h b/src/quick/items/qquicktextdocument_p.h index fd8fd54ac4..8471653611 100644 --- a/src/quick/items/qquicktextdocument_p.h +++ b/src/quick/items/qquicktextdocument_p.h @@ -68,10 +68,8 @@ public: // so far the QQuickItem given to the QQuickTextDocument ctor is always a QQuickTextEdit QQuickTextEdit *editor = nullptr; QUrl url; -#if QT_CONFIG(mimetype) - QMimeType mimeType; -#endif - std::optional<QStringConverter::Encoding> encoding; // only relevant for HTML + Qt::TextFormat detectedFormat = Qt::AutoText; // url's extension, independent of TextEdit.textFormat + std::optional<QStringConverter::Encoding> encoding; // only relevant for HTML (Qt::RichText) QQuickTextDocument::Status status = QQuickTextDocument::Status::Null; }; diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index b341f2cbe3..3db8e6e661 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -510,24 +510,40 @@ void QQuickTextEdit::setTextFormat(TextFormat format) const bool wasRich = d->richText; const bool wasMarkdown = d->markdownText; + const bool wasAuto = d->format == AutoText; + bool textCachedChanged = false; d->richText = format == RichText || (format == AutoText && (wasRich || Qt::mightBeRichText(text()))); d->markdownText = format == MarkdownText; + qCDebug(lcTextEdit) << d->format << "->" << format + << "was rich?" << wasRich << "md?" << wasMarkdown << "auto?" << wasAuto + << "now: rich?" << d->richText << "md?" << d->markdownText; + if (isComponentComplete()) { + const Qt::TextFormat detectedFormat = d->quickDocument ? + QQuickTextDocumentPrivate::get(d->quickDocument)->detectedFormat : Qt::AutoText; + // If converting between markdown and HTML, avoid using cached text: have QTD re-generate it + if (format != PlainText && (wasRich || detectedFormat == Qt::RichText) != + (wasMarkdown || detectedFormat == Qt::MarkdownText)) { + d->textCached = false; + textCachedChanged = true; + } #if QT_CONFIG(texthtmlparser) - if (wasRich && !d->richText && !d->markdownText) { + if ((wasRich || wasAuto) && !d->richText && !d->markdownText) { d->control->setPlainText(!d->textCached ? d->control->toHtml() : d->text); updateSize(); - } else if (!wasRich && d->richText) { + } else if (!(wasRich || wasAuto) && + (d->richText || (format == AutoText && detectedFormat == Qt::RichText))) { d->control->setHtml(!d->textCached ? d->control->toPlainText() : d->text); updateSize(); } #endif #if QT_CONFIG(textmarkdownwriter) && QT_CONFIG(textmarkdownreader) - if (wasMarkdown && !d->markdownText && !d->richText) { + if ((wasMarkdown || wasAuto) && !d->markdownText && !d->richText) { d->control->setPlainText(!d->textCached ? d->control->toMarkdown() : d->text); updateSize(); - } else if (!wasMarkdown && d->markdownText) { + } else if (!(wasMarkdown || wasAuto) && + (d->markdownText || (format == AutoText && detectedFormat == Qt::MarkdownText))) { d->control->setMarkdownText(!d->textCached ? d->control->toPlainText() : d->text); updateSize(); } @@ -537,6 +553,8 @@ void QQuickTextEdit::setTextFormat(TextFormat format) d->format = format; d->control->setAcceptRichText(d->format != PlainText); emit textFormatChanged(d->format); + if (textCachedChanged) + emit textChanged(); } /*! @@ -1612,20 +1630,17 @@ void QQuickTextEdit::componentComplete() const QUrl url = baseUrl(); const QQmlContext *context = qmlContext(this); d->document->setBaseUrl(context ? context->resolvedUrl(url) : url); + if (!d->text.isEmpty()) { #if QT_CONFIG(texthtmlparser) - if (d->richText) - d->control->setHtml(d->text); - else + if (d->richText) + d->control->setHtml(d->text); + else #endif #if QT_CONFIG(textmarkdownreader) - if (d->markdownText) - d->control->setMarkdownText(d->text); - else -#endif - if (!d->text.isEmpty()) { if (d->markdownText) d->control->setMarkdownText(d->text); else +#endif d->control->setPlainText(d->text); } @@ -3091,6 +3106,34 @@ void QQuickTextEditPrivate::updateDefaultTextOption() } } +void QQuickTextEditPrivate::onDocumentStatusChanged() +{ + Q_ASSERT(quickDocument); + switch (quickDocument->status()) { + case QQuickTextDocument::Status::Loaded: + case QQuickTextDocument::Status::Saved: + switch (QQuickTextDocumentPrivate::get(quickDocument)->detectedFormat) { + case Qt::RichText: + richText = (format == QQuickTextEdit::RichText || format == QQuickTextEdit::AutoText); + markdownText = false; + break; + case Qt::MarkdownText: + richText = false; + markdownText = (format == QQuickTextEdit::MarkdownText || format == QQuickTextEdit::AutoText); + break; + case Qt::PlainText: + richText = false; + markdownText = false; + break; + case Qt::AutoText: // format not detected + break; + } + break; + default: + break; + } +} + void QQuickTextEdit::focusInEvent(QFocusEvent *event) { Q_D(QQuickTextEdit); @@ -3287,8 +3330,11 @@ void QQuickTextEdit::remove(int start, int end) QQuickTextDocument *QQuickTextEdit::textDocument() { Q_D(QQuickTextEdit); - if (!d->quickDocument) + if (!d->quickDocument) { d->quickDocument = new QQuickTextDocument(this); + connect(d->quickDocument, &QQuickTextDocument::statusChanged, d->quickDocument, + [d]() { d->onDocumentStatusChanged(); } ); + } return d->quickDocument; } diff --git a/src/quick/items/qquicktextedit_p_p.h b/src/quick/items/qquicktextedit_p_p.h index e0ffa97119..49c85b431e 100644 --- a/src/quick/items/qquicktextedit_p_p.h +++ b/src/quick/items/qquicktextedit_p_p.h @@ -113,6 +113,7 @@ public: void resetInputMethod(); void updateDefaultTextOption(); + void onDocumentStatusChanged(); void relayoutDocument(); bool determineHorizontalAlignment(); bool setHAlign(QQuickTextEdit::HAlignment, bool forceAlign = false); diff --git a/tests/auto/quick/qquicktextdocument/data/sideBySideIndependent.qml b/tests/auto/quick/qquicktextdocument/data/sideBySideIndependent.qml new file mode 100644 index 0000000000..8275e7f5bd --- /dev/null +++ b/tests/auto/quick/qquicktextdocument/data/sideBySideIndependent.qml @@ -0,0 +1,21 @@ +import QtQuick + +Row { + width: 480; height: 200 + TextEdit { + objectName: "plain" + width: parent.width / 2 - 1 + textFormat: TextEdit.PlainText + textDocument.source: "hello.md" + } + Rectangle { + width: 2; height: parent.height + color: "lightsteelblue" + } + TextEdit { + objectName: "markdown" + width: parent.width / 2 - 1 + textFormat: TextEdit.MarkdownText + textDocument.source: "hello.md" + } +} diff --git a/tests/auto/quick/qquicktextdocument/data/sideBySideIndependentReverse.qml b/tests/auto/quick/qquicktextdocument/data/sideBySideIndependentReverse.qml new file mode 100644 index 0000000000..cbd92accec --- /dev/null +++ b/tests/auto/quick/qquicktextdocument/data/sideBySideIndependentReverse.qml @@ -0,0 +1,21 @@ +import QtQuick + +Row { + width: 480; height: 200 + TextEdit { + objectName: "plain" + width: parent.width / 2 - 1 + textDocument.source: "hello.md" + textFormat: TextEdit.PlainText + } + Rectangle { + width: 2; height: parent.height + color: "lightsteelblue" + } + TextEdit { + objectName: "markdown" + width: parent.width / 2 - 1 + textDocument.source: "hello.md" + textFormat: TextEdit.MarkdownText + } +} diff --git a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp index f477780980..f88741737e 100644 --- a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp +++ b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp @@ -3,8 +3,9 @@ #include <qtest.h> #include <QtTest/QtTest> -#include <QtQuick/QQuickTextDocument> #include <QtQuick/QQuickItem> +#include <QtQuick/QQuickTextDocument> +#include <QtQuick/QQuickView> #include <QtQuick/private/qquicktextdocument_p.h> #include <QtQuick/private/qquicktextedit_p.h> #include <QtQuick/private/qquicktextedit_p_p.h> @@ -16,6 +17,7 @@ #include <QtQml/QQmlEngine> #include <QtQml/QQmlFile> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtQuickTestUtils/private/viewtestutils_p.h> #ifdef Q_OS_UNIX #include <unistd.h> #endif @@ -30,6 +32,10 @@ class tst_qquicktextdocument : public QQmlDataTest public: tst_qquicktextdocument(); +private: + QPair<int, int> fragmentsAndItalics(const QTextDocument *doc); + bool isMainFontFixed(); + private slots: void textDocumentWriter(); void customDocument(); @@ -37,6 +43,10 @@ private slots: void sourceAndSave(); void loadErrorNoSuchFile(); void loadErrorPermissionDenied(); + void overrideTextFormat_data(); + void overrideTextFormat(); + void independentDocumentsSameSource_data(); + void independentDocumentsSameSource(); }; QString text = QStringLiteral("foo bar"); @@ -79,6 +89,34 @@ tst_qquicktextdocument::tst_qquicktextdocument() { } +/*! \internal + Returns {fragmentCount, italicFragmentIndex}. If no italic fragment is found, + italicFragmentIndex is -1. +*/ +QPair<int, int> tst_qquicktextdocument::fragmentsAndItalics(const QTextDocument *doc) +{ + int fragmentCount = 0; + int italicFragment = -1; + for (QTextBlock::iterator it = doc->firstBlock().begin(); !(it.atEnd()); ++it) { + QTextFragment currentFragment = it.fragment(); + if (currentFragment.charFormat().fontItalic()) + italicFragment = fragmentCount; + ++fragmentCount; + qCDebug(lcTests) << (currentFragment.charFormat().fontItalic() ? "italic" : "roman") << currentFragment.text(); + } + return {fragmentCount, italicFragment}; +} + +bool tst_qquicktextdocument::isMainFontFixed() +{ + bool ret = QFontInfo(QGuiApplication::font()).fixedPitch(); + if (ret) { + qCWarning(lcTests) << "QFontDatabase::GeneralFont is monospaced: markdown writing is likely to use too many backticks" + << QFontDatabase::systemFont(QFontDatabase::GeneralFont); + } + return ret; +} + void tst_qquicktextdocument::textDocumentWriter() { QQmlEngine e; @@ -161,31 +199,33 @@ void tst_qquicktextdocument::customDocument() void tst_qquicktextdocument::sourceAndSave_data() { + QTest::addColumn<QQuickTextEdit::TextFormat>("textFormat"); QTest::addColumn<QString>("source"); QTest::addColumn<std::optional<QStringConverter::Encoding>>("expectedEncoding"); - QTest::addColumn<QString>("expectedMimeType"); + QTest::addColumn<QQuickTextEdit::TextFormat>("expectedTextFormat"); QTest::addColumn<int>("minCharCount"); QTest::addColumn<QString>("expectedPlainText"); const std::optional<QStringConverter::Encoding> nullEnc; - QTest::newRow("plain") << "hello.txt" - << nullEnc << "text/plain" << 15 << u"Γειά σου Κόσμε!"_s; - QTest::newRow("markdown") << "hello.md" - << nullEnc << "text/markdown" << 15 << u"Γειά σου Κόσμε!"_s; - QTest::newRow("html") << "hello.html" + QTest::newRow("plain") << QQuickTextEdit::PlainText << "hello.txt" + << nullEnc << QQuickTextEdit::PlainText << 15 << u"Γειά σου Κόσμε!"_s; + QTest::newRow("markdown") << QQuickTextEdit::MarkdownText << "hello.md" + << nullEnc << QQuickTextEdit::MarkdownText << 15 << u"Γειά σου Κόσμε!"_s; + QTest::newRow("html") << QQuickTextEdit::RichText << "hello.html" << std::optional<QStringConverter::Encoding>(QStringConverter::Utf8) - << "text/html" << 15 << u"Γειά σου Κόσμε!"_s; - QTest::newRow("html-utf16be") << "hello-utf16be.html" + << QQuickTextEdit::RichText << 15 << u"Γειά σου Κόσμε!"_s; + QTest::newRow("html-utf16be") << QQuickTextEdit::AutoText << "hello-utf16be.html" << std::optional<QStringConverter::Encoding>(QStringConverter::Utf16BE) - << "text/html" << 15 << u"Γειά σου Κόσμε!"_s; + << QQuickTextEdit::RichText << 15 << u"Γειά σου Κόσμε!"_s; } void tst_qquicktextdocument::sourceAndSave() { + QFETCH(QQuickTextEdit::TextFormat, textFormat); QFETCH(QString, source); QFETCH(std::optional<QStringConverter::Encoding>, expectedEncoding); - QFETCH(QString, expectedMimeType); + QFETCH(QQuickTextEdit::TextFormat, expectedTextFormat); QFETCH(int, minCharCount); QFETCH(QString, expectedPlainText); @@ -219,14 +259,14 @@ void tst_qquicktextdocument::sourceAndSave() QCOMPARE(statusChangedSpy.size(), 0); QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::Null); + textEdit->setTextFormat(textFormat); qqdoc->setProperty("source", QUrl::fromLocalFile(tmpPath)); QCOMPARE(sourceChangedSpy.size(), 1); QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 1); QCOMPARE(statusChangedSpy.size(), 2); // Loading, then Loaded QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::Loaded); const auto *qqdp = QQuickTextDocumentPrivate::get(qqdoc); - QVERIFY(qqdp->mimeType.inherits(expectedMimeType)); - const bool expectHtml = (expectedMimeType == "text/html"); + QCOMPARE(qqdp->detectedFormat, expectedTextFormat); QCOMPARE_GE(doc->characterCount(), minCharCount); QCOMPARE(doc->toPlainText().trimmed(), expectedPlainText); QCOMPARE(qqdp->encoding, expectedEncoding); @@ -246,7 +286,7 @@ void tst_qquicktextdocument::sourceAndSave() QFile tf(tmpPath); QVERIFY(tf.open(QIODeviceBase::ReadOnly)); auto readBack = tf.readAll(); - if (expectHtml) { + if (expectedTextFormat == Qt::RichText) { QStringDecoder dec(*expectedEncoding); const QString decStr = dec(readBack); QVERIFY(decStr.contains("hello!</p>")); @@ -323,6 +363,194 @@ void tst_qquicktextdocument::loadErrorPermissionDenied() QCOMPARE(qqdoc->status(), QQuickTextDocument::Status::ReadError); } +void tst_qquicktextdocument::overrideTextFormat_data() +{ + QTest::addColumn<QUrl>("qmlfile"); + QTest::addColumn<QQuickTextEdit::TextFormat>("initialFormat"); + QTest::addColumn<QUrl>("source"); + QTest::addColumn<int>("expectedInitialFragmentCount"); + QTest::addColumn<int>("expectedInitialItalicFragment"); + // first part of TextEdit.text after loading + QTest::addColumn<QString>("expectedTextPrefix"); + + QTest::addColumn<QQuickTextEdit::TextFormat>("replacementFormat"); + QTest::addColumn<int>("expectedFragmentCount"); + QTest::addColumn<int>("expectedItalicFragment"); + // first part of TextEdit.text after switching to replacementFormat + QTest::addColumn<QString>("expectedReplacementPrefix"); + + QTest::addColumn<QQuickTextEdit::TextFormat>("finalFormat"); + QTest::addColumn<int>("expectedFinalFragmentCount"); + QTest::addColumn<int>("expectedFinalItalicFragment"); + // first part of TextEdit.text after switching to finalFormat + QTest::addColumn<QString>("expectedFinalPrefix"); + + QTest::newRow("load md, switch to plain, back to md") + << testFileUrl("text.qml") << QQuickTextEdit::MarkdownText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s; + QTest::newRow("load md, switch to plain, then auto") + << testFileUrl("text.qml") << QQuickTextEdit::MarkdownText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::AutoText << 3 << 1 << u"Γειά σου Κόσμε!"_s; + QTest::newRow("load md, switch to html, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::MarkdownText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::RichText << 1 << -1 << u"<!DOCTYPE HTML"_s // throws away formatting, unfortunately + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load md as plain text, switch to md, back to plain") + << testFileUrl("text.qml") << QQuickTextEdit::PlainText << testFileUrl("hello.md") + << 1 << -1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s; + QTest::newRow("load md as autotext, switch to plain, back to auto") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::AutoText << 3 << 1 << u"Γειά σου Κόσμε!"_s; + QTest::newRow("load md as autotext, switch to md, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s; + QTest::newRow("load md as autotext, switch to html, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.md") + << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s; + + QTest::newRow("load html, switch to plain, back to rich") + << testFileUrl("text.qml") << QQuickTextEdit::RichText << testFileUrl("hello.html") + << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load html as plain text, switch to html, back to plain") + << testFileUrl("text.qml") << QQuickTextEdit::PlainText << testFileUrl("hello.html") + << 1 << -1 << u"Γειά σου <i>Κόσμε</i>!"_s + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load html as autotext, switch to plain, back to auto") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.html") + << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::AutoText << 3 << 1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load html as autotext, switch to html, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.html") + << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s; + QTest::newRow("load html as autotext, switch to markdown, then plain") + << testFileUrl("text.qml") << QQuickTextEdit::AutoText << testFileUrl("hello.html") + << 3 << 1 << u"<!DOCTYPE HTML"_s + << QQuickTextEdit::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s + << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s; +} + +void tst_qquicktextdocument::overrideTextFormat() // QTBUG-120772 +{ + if (isMainFontFixed()) + QSKIP("fixed-pitch main font (QTBUG-103484)"); + + QFETCH(QUrl, qmlfile); + QFETCH(QQuickTextEdit::TextFormat, initialFormat); + QFETCH(QUrl, source); + QFETCH(int, expectedInitialFragmentCount); + QFETCH(int, expectedInitialItalicFragment); + QFETCH(QString, expectedTextPrefix); + + QFETCH(QQuickTextEdit::TextFormat, replacementFormat); + QFETCH(int, expectedFragmentCount); + QFETCH(int, expectedItalicFragment); + QFETCH(QString, expectedReplacementPrefix); + + QFETCH(QQuickTextEdit::TextFormat, finalFormat); + QFETCH(int, expectedFinalFragmentCount); + QFETCH(int, expectedFinalItalicFragment); + QFETCH(QString, expectedFinalPrefix); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, qmlfile)); + QQuickTextEdit *textEdit = qobject_cast<QQuickTextEdit *>(window.rootObject()); + QVERIFY(textEdit); + QQuickTextDocument *qqdoc = textEdit->property("textDocument").value<QQuickTextDocument*>(); + QVERIFY(qqdoc); + QTextDocument *doc = qqdoc->textDocument(); + QVERIFY(doc); + + textEdit->setTextFormat(initialFormat); + QCOMPARE(qqdoc->isModified(), false); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 0); + QSignalSpy sourceChangedSpy(qqdoc, &QQuickTextDocument::sourceChanged); + QSignalSpy textChangedSpy(textEdit, &QQuickTextEdit::textChanged); + + qqdoc->setProperty("source", source); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE(textEdit->property("sourceChangeCount").toInt(), 1); + QCOMPARE_GE(textChangedSpy.size(), 1); + auto fragCountAndItalic = fragmentsAndItalics(doc); + QCOMPARE(fragCountAndItalic.first, expectedInitialFragmentCount); + QCOMPARE(fragCountAndItalic.second, expectedInitialItalicFragment); + QString textPropValue = textEdit->text(); + qCDebug(lcTests) << "expect text()" << textPropValue.first(qMin(20, textPropValue.size() - 1)) + << "to start with" << expectedTextPrefix; + QVERIFY(textPropValue.startsWith(expectedTextPrefix)); + + textEdit->setTextFormat(replacementFormat); + QCOMPARE(qqdoc->isModified(), false); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE_GE(textChangedSpy.size(), 2); // loading and then the format change + fragCountAndItalic = fragmentsAndItalics(doc); + QCOMPARE(fragCountAndItalic.first, expectedFragmentCount); + QCOMPARE(fragCountAndItalic.second, expectedItalicFragment); + textPropValue = textEdit->text(); + qCDebug(lcTests) << "expect text()" << textPropValue.first(qMin(20, textPropValue.size() - 1)) + << "to start with" << expectedReplacementPrefix; + QVERIFY(textPropValue.startsWith(expectedReplacementPrefix)); + + textEdit->setTextFormat(finalFormat); + QCOMPARE(qqdoc->isModified(), false); + QCOMPARE(sourceChangedSpy.size(), 1); + QCOMPARE_GE(textChangedSpy.size(), 3); + fragCountAndItalic = fragmentsAndItalics(doc); + QCOMPARE(fragCountAndItalic.first, expectedFinalFragmentCount); + QCOMPARE(fragCountAndItalic.second, expectedFinalItalicFragment); + textPropValue = textEdit->text(); + qCDebug(lcTests) << "expect text()" << textPropValue.first(qMin(20, textPropValue.size() - 1)) + << "to start with" << expectedFinalPrefix; + QVERIFY(textPropValue.startsWith(expectedFinalPrefix)); +} + +void tst_qquicktextdocument::independentDocumentsSameSource_data() +{ + QTest::addColumn<QUrl>("qmlfile"); + + QTest::newRow("textFormat above source") << testFileUrl("sideBySideIndependent.qml"); + QTest::newRow("source above textFormat") << testFileUrl("sideBySideIndependentReverse.qml"); +} + +// ensure that two TextEdits' textFormat properties take effect, regardless of qml init order +void tst_qquicktextdocument::independentDocumentsSameSource() // QTBUG-120772 +{ + QFETCH(QUrl, qmlfile); + + QQuickView window; + QVERIFY(QQuickTest::showView(window, qmlfile)); + QQuickTextEdit *textEditPlain = window.rootObject()->findChild<QQuickTextEdit *>("plain"); + QVERIFY(textEditPlain); + QQuickTextEdit *textEditMarkdown = window.rootObject()->findChild<QQuickTextEdit *>("markdown"); + QVERIFY(textEditMarkdown); + + auto fragCountAndItalic = fragmentsAndItalics(textEditPlain->textDocument()->textDocument()); + QCOMPARE(fragCountAndItalic.first, 1); + QCOMPARE(fragCountAndItalic.second, -1); + + fragCountAndItalic = fragmentsAndItalics(textEditMarkdown->textDocument()->textDocument()); + QCOMPARE(fragCountAndItalic.first, 3); + QCOMPARE(fragCountAndItalic.second, 1); +} + QTEST_MAIN(tst_qquicktextdocument) #include "tst_qquicktextdocument.moc" diff --git a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp index c39bb3ea21..6dcff765c1 100644 --- a/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp +++ b/tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp @@ -6125,6 +6125,7 @@ void tst_qquicktextedit::remoteImagesInDocumentSource() QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Protocol \"gopher\" is unknown")); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*Connection closed")); // httpfail/warning.png + textEdit->setTextFormat(QQuickTextEdit::MarkdownText); textEdit->textDocument()->setSource(QUrl::fromLocalFile(tmpPath)); // the document gets loaded first, then the resources |