aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2024-03-04 19:26:00 -0700
committerShawn Rutledge <shawn.rutledge@qt.io>2024-03-12 06:16:31 +0000
commit6b8bf79a5ef70d72ffa349fae1dafb5149f7f1f4 (patch)
treedd7bc20e3a7cfd4aab51bd2f609832ef574ff802
parentd5faecd1d85325366bb4a54c5f4fd3dd2d4e5857 (diff)
Keep markup when toggling TextEdit.format: AutoText / PlainText
This is useful when writing an editor that allows you to do wysiwyg editing or raw HTML/markdown. Usually you want `format: AutoText` because you don't know what the user is going to load, and we expect QQuickTextDocumentPrivate::load() to find the type from the URL extension (since b46d6a75ac16089de1a29c773e7594a82ffea13e). Then if the user toggles format to PlainText, that's assumed to be for the purpose of raw viewing or editing, not for the purpose of removing the formatting. (Switching to plain text can be accomplished by `textEdit.text = textEdit.getText(0, textEdit.length)` .) We adjust tst_qquicktextdocument::overrideTextFormat() slightly: loading an html document as AutoText and then converting to HTML no longer emits the textChanged signal (it's not really a conversion). Likewise, converting markdown loaded as AutoText to explicit markdown should not need to emit the textChanged signal. Amends fdbacf2d5c0a04925bcb3aecd7bf47da5fb69227 Done-with: Oliver Eftevaag <oliver.eftevaag@qt.io> Fixes: QTBUG-122985 Task-number: QTBUG-120772 Change-Id: I1d43b5e5662197dfeb1a93a2d1f638a193aa66b2 Reviewed-by: Axel Spoerl <axel.spoerl@qt.io> (cherry picked from commit bc1bdd0e15ad1fbc1ffb2af666b0ca5676858ee0) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> (cherry picked from commit 206a1fa2a4c1f04421cc2a80c4f3e329495d9d38) Reviewed-by: Jani Heikkinen <jani.heikkinen@qt.io>
-rw-r--r--src/quick/items/qquicktextedit.cpp140
-rw-r--r--tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp32
2 files changed, 130 insertions, 42 deletions
diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp
index 712b038be9..1c76772ec6 100644
--- a/src/quick/items/qquicktextedit.cpp
+++ b/src/quick/items/qquicktextedit.cpp
@@ -507,48 +507,130 @@ void QQuickTextEdit::setTextFormat(TextFormat format)
if (format == d->format)
return;
- 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;
+ auto mightBeRichText = [this]() {
+ return Qt::mightBeRichText(text());
+ };
- qCDebug(lcTextEdit) << d->format << "->" << format
- << "was rich?" << wasRich << "md?" << wasMarkdown << "auto?" << wasAuto
- << "now: rich?" << d->richText << "md?" << d->markdownText;
+ auto findSourceFormat = [d, mightBeRichText](Qt::TextFormat detectedFormat) {
+ if (d->format == PlainText)
+ return PlainText;
+ if (d->richText) return RichText;
+ if (d->markdownText) return MarkdownText;
+ if (detectedFormat == Qt::AutoText && mightBeRichText())
+ return RichText;
+ return PlainText;
+ };
+
+ auto findDestinationFormat = [format, mightBeRichText](Qt::TextFormat detectedFormat, TextFormat sourceFormat) {
+ if (format == AutoText) {
+ if (detectedFormat == Qt::MarkdownText || (detectedFormat == Qt::AutoText && sourceFormat == MarkdownText))
+ return MarkdownText;
+ if (detectedFormat == Qt::RichText || (detectedFormat == Qt::AutoText && (sourceFormat == RichText || mightBeRichText())))
+ return RichText;
+ return PlainText; // fallback
+ }
+ return format;
+ };
+
+ bool textCachedChanged = false;
+ bool converted = false;
if (isComponentComplete()) {
- const Qt::TextFormat detectedFormat = d->quickDocument ?
- QQuickTextDocumentPrivate::get(d->quickDocument)->detectedFormat : Qt::AutoText;
+ Qt::TextFormat detectedFormat = Qt::AutoText; // default if we don't know
+ if (d->quickDocument) {
+ // If QQuickTextDocument is in use, content can be loaded from a file,
+ // and then mime type detection overrides mightBeRichText().
+ detectedFormat = QQuickTextDocumentPrivate::get(d->quickDocument)->detectedFormat;
+ }
+
+ const TextFormat sourceFormat = findSourceFormat(detectedFormat);
+ const TextFormat destinationFormat = findDestinationFormat(detectedFormat, sourceFormat);
+
+ d->richText = destinationFormat == RichText;
+ d->markdownText = destinationFormat == MarkdownText;
+
// 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)) {
+ if (format != PlainText && (sourceFormat != destinationFormat)) {
d->textCached = false;
textCachedChanged = true;
}
+
+ switch (destinationFormat) {
+ case PlainText:
#if QT_CONFIG(texthtmlparser)
- if ((wasRich || wasAuto) && !d->richText && !d->markdownText) {
- d->control->setPlainText(!d->textCached ? d->control->toHtml() : d->text);
- updateSize();
- } else if (!(wasRich || wasAuto) &&
- (d->richText || (format == AutoText && detectedFormat == Qt::RichText))) {
- d->control->setHtml(!d->textCached ? d->control->toPlainText() : d->text);
- updateSize();
- }
+ if (sourceFormat == RichText) {
+ // If rich or unknown text was loaded and now the user wants plain text, get the raw HTML.
+ // But if we didn't set textCached to false above, assume d->text already contains HTML.
+ // This will allow the user to see the actual HTML they loaded (rather than Qt regenerating crufty HTML).
+ d->control->setPlainText(d->textCached ? d->text : d->control->toHtml());
+ converted = true;
+ }
#endif
#if QT_CONFIG(textmarkdownwriter) && QT_CONFIG(textmarkdownreader)
- if ((wasMarkdown || wasAuto) && !d->markdownText && !d->richText) {
- d->control->setPlainText(!d->textCached ? d->control->toMarkdown() : d->text);
- updateSize();
- } else if (!(wasMarkdown || wasAuto) &&
- (d->markdownText || (format == AutoText && detectedFormat == Qt::MarkdownText))) {
- d->control->setMarkdownText(!d->textCached ? d->control->toPlainText() : d->text);
- updateSize();
- }
+ if (sourceFormat == MarkdownText) {
+ // If markdown or unknown text was loaded and now the user wants plain text, get the raw Markdown.
+ // But if we didn't set textCached to false above, assume d->text already contains markdown.
+ // This will allow the user to see the actual markdown they loaded.
+ d->control->setPlainText(d->textCached ? d->text : d->control->toMarkdown());
+ converted = true;
+ }
+#endif
+ break;
+ case RichText:
+#if QT_CONFIG(texthtmlparser)
+ switch (sourceFormat) {
+ case MarkdownText:
+ // If markdown was loaded and now the user wants HTML, convert markdown to HTML.
+ d->control->setHtml(d->control->toHtml());
+ converted = true;
+ break;
+ case PlainText:
+ // If plain text was loaded and now the user wants HTML, interpret plain text as HTML.
+ // But if we didn't set textCached to false above, assume d->text already contains HTML.
+ d->control->setHtml(d->textCached ? d->text : d->control->toPlainText());
+ converted = true;
+ break;
+ case AutoText:
+ case RichText: // nothing to do
+ break;
+ }
#endif
+ break;
+ case MarkdownText:
+#if QT_CONFIG(textmarkdownwriter) && QT_CONFIG(textmarkdownreader)
+ switch (sourceFormat) {
+ case RichText:
+ // If HTML was loaded and now the user wants markdown, convert HTML to markdown.
+ d->control->setMarkdownText(d->control->toMarkdown());
+ converted = true;
+ break;
+ case PlainText:
+ // If plain text was loaded and now the user wants markdown, interpret plain text as markdown.
+ // But if we didn't set textCached to false above, assume d->text already contains markdown.
+ d->control->setMarkdownText(d->textCached ? d->text : d->control->toPlainText());
+ converted = true;
+ break;
+ case AutoText:
+ case MarkdownText: // nothing to do
+ break;
+ }
+#endif
+ break;
+ case AutoText: // nothing to do
+ break;
+ }
+
+ if (converted)
+ updateSize();
+ } else {
+ d->richText = format == RichText || (format == AutoText && (d->richText || mightBeRichText()));
+ d->markdownText = format == MarkdownText;
}
+ qCDebug(lcTextEdit) << d->format << "->" << format
+ << "rich?" << d->richText << "md?" << d->markdownText
+ << "converted?" << converted << "cache invalidated?" << textCachedChanged;
+
d->format = format;
d->control->setAcceptRichText(d->format != PlainText);
emit textFormatChanged(d->format);
diff --git a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp
index 66313420f5..09abd188d7 100644
--- a/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp
+++ b/tests/auto/quick/qquicktextdocument/tst_qquicktextdocument.cpp
@@ -384,6 +384,7 @@ void tst_qquicktextdocument::overrideTextFormat_data()
QTest::addColumn<int>("expectedItalicFragment");
// first part of TextEdit.text after switching to replacementFormat
QTest::addColumn<QString>("expectedReplacementPrefix");
+ QTest::addColumn<int>("expectedTextChangedSignalsAfterReplacement");
QTest::addColumn<QQuickTextEdit::TextFormat>("finalFormat");
QTest::addColumn<int>("expectedFinalFragmentCount");
@@ -394,58 +395,62 @@ void tst_qquicktextdocument::overrideTextFormat_data()
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::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s << 2
<< 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;
+ << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s << 2
+ << 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::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s << 2
<< 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::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s << 2
<< 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;
+ << QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s << 2
+ << 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
+ // going from AutoText to a matching explicit format does not cause extra textChanged()
+ << 1
<< 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::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s << 2
<< 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::PlainText << 1 << -1 << u"<!DOCTYPE HTML"_s << 2
<< 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::RichText << 3 << 1 << u"<!DOCTYPE HTML"_s << 2
<< QQuickTextEdit::PlainText << 1 << -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
+ // going from AutoText to a matching explicit format does not cause extra textChanged()
+ << 1
<< 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::MarkdownText << 3 << 1 << u"Γειά σου *Κόσμε*!"_s << 2
<< QQuickTextEdit::PlainText << 1 << -1 << u"Γειά σου *Κόσμε*!"_s;
}
@@ -465,6 +470,7 @@ void tst_qquicktextdocument::overrideTextFormat() // QTBUG-120772
QFETCH(int, expectedFragmentCount);
QFETCH(int, expectedItalicFragment);
QFETCH(QString, expectedReplacementPrefix);
+ QFETCH(int, expectedTextChangedSignalsAfterReplacement);
QFETCH(QQuickTextEdit::TextFormat, finalFormat);
QFETCH(int, expectedFinalFragmentCount);
@@ -501,7 +507,7 @@ void tst_qquicktextdocument::overrideTextFormat() // QTBUG-120772
textEdit->setTextFormat(replacementFormat);
QCOMPARE(qqdoc->isModified(), false);
QCOMPARE(sourceChangedSpy.size(), 1);
- QCOMPARE_GE(textChangedSpy.size(), 2); // loading and then the format change
+ QCOMPARE_GE(textChangedSpy.size(), expectedTextChangedSignalsAfterReplacement);
fragCountAndItalic = fragmentsAndItalics(doc);
QCOMPARE(fragCountAndItalic.first, expectedFragmentCount);
QCOMPARE(fragCountAndItalic.second, expectedItalicFragment);
@@ -513,7 +519,7 @@ void tst_qquicktextdocument::overrideTextFormat() // QTBUG-120772
textEdit->setTextFormat(finalFormat);
QCOMPARE(qqdoc->isModified(), false);
QCOMPARE(sourceChangedSpy.size(), 1);
- QCOMPARE_GE(textChangedSpy.size(), 3);
+ QCOMPARE_GE(textChangedSpy.size(), expectedTextChangedSignalsAfterReplacement + 1);
fragCountAndItalic = fragmentsAndItalics(doc);
QCOMPARE(fragCountAndItalic.first, expectedFinalFragmentCount);
QCOMPARE(fragCountAndItalic.second, expectedFinalItalicFragment);