aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/quick/items/qquicktextdocument.cpp63
-rw-r--r--src/quick/items/qquicktextdocument_p.h6
-rw-r--r--src/quick/items/qquicktextedit.cpp72
-rw-r--r--src/quick/items/qquicktextedit_p_p.h1
-rw-r--r--tests/auto/quick/qquicktextdocument/data/sideBySideIndependent.qml21
-rw-r--r--tests/auto/quick/qquicktextdocument/data/sideBySideIndependentReverse.qml21
-rw-r--r--tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp256
-rw-r--r--tests/auto/quick/qquicktextedit/tst_qquicktextedit.cpp1
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