diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2024-01-11 22:57:58 -0700 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2024-02-21 23:20:22 -0700 |
commit | fdbacf2d5c0a04925bcb3aecd7bf47da5fb69227 (patch) | |
tree | b1ac6e3cde912ae99890119ba147873aba457a51 /src/quick | |
parent | 5a762ba09cdeb8df5cf54ce562bc60dfcc11c97f (diff) |
Respect TextEdit.textFormat in TextDocument load(); detect format
Don't load rich text if TextEdit.textFormat is PlainText. Thus it
should be possible to have two independent TextEdit instances with
independent document instances, viewing the same source file: the
raw syntax and the rich/WYSIWYG formatting visible at the same time.
Also, after the QQuickTextDocument's QTextDocument has already loaded
the declared source, don't let QQuickTextEdit::componentComplete()
replace it with empty text from the (unbound) TextEdit.text property.
That was happening only if textFormat was RichText or MarkdownText.
In case the user transitions TextEdit.textFormat from AutoText to any
other, the text property now holds the format that was loaded from
TextDocument.source; QQuickTextDocumentPrivate remembers the format
rather than the mime type (because QMimeType is an optional feature in
Qt); and TextEdit functions such as getFormattedText(), insert() and
append() behave as if the textFormat property had been appropriate for
the loaded file type in the first place. We cannot algorithmically
detect markdown text by looking at the text itself; but now,
QQuickTextDocumentPrivate::load() can populate markdown or html text
into the document by detecting the file's mime type, even if the
textFormat is AutoText. After that, if the user changes the textFormat
to PlainText, we assume that they want to see the raw markdown or HTML.
Amends b46d6a75ac16089de1a29c773e7594a82ffea13e : it already seemed odd
at the time that it was OK for QQuickTextDocumentPrivate::load() to load
any text format, without first changing TextEdit.textFormat to match
what's expected. The default is PlainText, so it didn't make sense to
read e.g. a markdown file and have the result look like WYSIWYG rich
text. Now you have to change textFormat to MarkdownText or AutoText
beforehand if you want to see WYSIWYG; and
tst_qquicktextdocument::sourceAndSave() needs to do that too.
On the other hand, if you switch textFormat to PlainText, we convert the
QTextDocument to plain text, using Qt-generated markup/markdown syntax.
Maybe the user would prefer to have the original syntax as read from
the file; but so far it has always been this way: we just parse it,
we don't store it. We are quite incapable of incrementally modifying
the original syntax anyway: we can only regenerate it wholesale.
So it makes sense that the text property holds Qt-generated syntax
after the parsing is done.
Pick-to: 6.7
Fixes: QTBUG-120772
Change-Id: I7db533c714e14bb4eb36745996c732e5162fb9cf
Reviewed-by: Oliver Eftevaag <oliver.eftevaag@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'src/quick')
-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 |
4 files changed, 108 insertions, 34 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); |