diff options
Diffstat (limited to 'src/quick/items')
-rw-r--r-- | src/quick/items/qquicktextdocument.cpp | 234 | ||||
-rw-r--r-- | src/quick/items/qquicktextdocument.h | 17 | ||||
-rw-r--r-- | src/quick/items/qquicktextdocument_p.h | 5 | ||||
-rw-r--r-- | src/quick/items/qquicktextedit.cpp | 1 |
4 files changed, 254 insertions, 3 deletions
diff --git a/src/quick/items/qquicktextdocument.cpp b/src/quick/items/qquicktextdocument.cpp index 81298596b0..432a69b494 100644 --- a/src/quick/items/qquicktextdocument.cpp +++ b/src/quick/items/qquicktextdocument.cpp @@ -12,10 +12,33 @@ #include <QtQml/qqmlcontext.h> #include <QtQuick/private/qquickpixmap_p.h> +#include <QtCore/qmimedatabase.h> #include <QtCore/qpointer.h> QT_BEGIN_NAMESPACE +using namespace Qt::StringLiterals; + +/*! + \qmltype TextDocument + \instantiates QQuickTextDocument + \inqmlmodule QtQuick + \brief A wrapper around TextEdit's backing QTextDocument. + + To load text into the document, set the \l source property. If the user then + modifies the text and wants to save the same document, call \l save() to save + it to the same source again (only if \l {QUrl::isLocalFile()}{it's a local file}). + Or call \l saveAs() to save it to a different file. + + This class cannot be instantiated in QML, but is available from \l TextEdit::textDocument. + + \note All loading and saving is done synchronously for now. + This may block the UI if the \l source is a slow network drive. + This may be improved in future versions of Qt. + + \note This API is considered tech preview and may change in future versions of Qt. +*/ + /*! \class QQuickTextDocument \since 5.1 @@ -27,8 +50,6 @@ QT_BEGIN_NAMESPACE including document modifications. It can also be used to output content, for example with \l{QTextDocumentWriter}), or provide additional formatting, for example with \l{QSyntaxHighlighter}. - - This class cannot be instantiated in QML, but is available from \l TextEdit::textDocument. */ /*! @@ -42,6 +63,146 @@ QQuickTextDocument::QQuickTextDocument(QQuickItem *parent) Q_ASSERT(parent); d->editor = qobject_cast<QQuickTextEdit *>(parent); Q_ASSERT(d->editor); + connect(textDocument(), &QTextDocument::modificationChanged, + this, &QQuickTextDocument::modifiedChanged); +} + +/*! + \qmlproperty url QtQuick::TextDocument::source + \since 6.7 + + QQuickTextDocument can handle any text format supported by Qt, loaded from + any URL scheme supported by Qt. + + The URL may be absolute, or relative to the URL of the component. + + The \c source property cannot be changed while the document's \l modified + state is \c true. If the user has modified the document contents, you + should prompt the user whether to \l save(), or else discard changes by + setting \c {modified = false} before setting the \l source property to a + different URL. + + \sa QTextDocumentWriter::supportedDocumentFormats() +*/ +QUrl QQuickTextDocument::source() const +{ + Q_D(const QQuickTextDocument); + return d->url; +} + +void QQuickTextDocument::setSource(const QUrl &url) +{ + Q_D(QQuickTextDocument); + + if (url == d->url) + return; + + if (isModified()) { + qmlWarning(this) << "Existing document modified: you should save()," + "or call TextEdit.clear() before setting a different source"; + return; + } + + d->url = url; + emit sourceChanged(); + d->load(); +} + +/*! + \qmlproperty bool QtQuick::TextDocument::modified + \since 6.7 + + This property holds whether the document has been modified by the user + since the last time it was loaded or saved. By default, this property is + \c false. + + As with \l QTextDocument::modified, you can set the modified property: + for example, set it to \c false to allow setting the \c source property + to a different URL (thus discarding the user's changes). + + \sa QTextDocument::modified +*/ +bool QQuickTextDocument::isModified() const +{ + const auto *doc = textDocument(); + return doc && doc->isModified(); +} + +void QQuickTextDocument::setModified(bool modified) +{ + if (auto *doc = textDocument()) + doc->setModified(modified); +} + +void QQuickTextDocumentPrivate::load() +{ + Q_Q(QQuickTextDocument); + const QString fileName = QQmlFile::urlToLocalFileOrQrc(url); + if (QFile::exists(fileName)) { + mimeType = QMimeDatabase().mimeTypeForFile(fileName); + QFile file(fileName); + if (file.open(QFile::ReadOnly)) { + QByteArray data = file.readAll(); + if (auto *doc = editor->document()) { + doc->setBaseUrl(url.adjusted(QUrl::RemoveFilename)); + if (mimeType.inherits("text/markdown"_L1)) { + doc->setMarkdown(QString::fromUtf8(data)); + } else if (mimeType.inherits("text/html"_L1)) { + // 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); + if (encoding) { + QStringDecoder decoder(*encoding); + doc->setHtml(decoder(data)); + } else { + // fall back to utf8 + doc->setHtml(QString::fromUtf8(data)); + } + } else { + doc->setPlainText(QString::fromUtf8(data)); + } + doc->setModified(false); + } + } else { + emit q->error(QQuickTextDocument::tr("Cannot load: ") + file.errorString()); + } + } +} + +void QQuickTextDocumentPrivate::writeTo(const QUrl &fileUrl) +{ + Q_Q(QQuickTextDocument); + auto *doc = editor->document(); + if (!doc) + return; + + const QString filePath = fileUrl.toLocalFile(); + const bool sameUrl = fileUrl == url; + const auto type = (sameUrl ? mimeType : QMimeDatabase().mimeTypeForUrl(fileUrl)); + const bool isHtml = type.inherits("text/html"_L1); + QFile file(filePath); + if (!file.open(QFile::WriteOnly | QFile::Truncate | (isHtml ? QFile::NotOpen : QFile::Text))) { + emit q->error(QQuickTextDocument::tr("Cannot save: ") + file.errorString()); + return; + } + QByteArray raw; + if (type.inherits("text/markdown"_L1)) { + raw = doc->toMarkdown().toUtf8(); + } else if (isHtml) { + if (sameUrl && encoding) { + QStringEncoder enc(*encoding); + raw = enc.encode(doc->toHtml()); + } else { + // default to UTF-8 unless the user is saving the same file as previously loaded + raw = doc->toHtml().toUtf8(); + } + } else { + raw = doc->toPlainText().toUtf8(); + } + + file.write(raw); + file.close(); + doc->setModified(false); } QTextDocument *QQuickTextDocumentPrivate::document() const @@ -52,9 +213,16 @@ QTextDocument *QQuickTextDocumentPrivate::document() const void QQuickTextDocumentPrivate::setDocument(QTextDocument *doc) { Q_Q(QQuickTextDocument); - if (doc == editor->document()) + QTextDocument *oldDoc = editor->document(); + if (doc == oldDoc) return; + if (oldDoc) + oldDoc->disconnect(q); + if (doc) { + q->connect(doc, &QTextDocument::modificationChanged, + q, &QQuickTextDocument::modifiedChanged); + } editor->setDocument(doc); emit q->textDocumentChanged(); } @@ -79,6 +247,59 @@ void QQuickTextDocument::setTextDocument(QTextDocument *document) d_func()->setDocument(document); } +/*! + \fn void QQuickTextDocument::textDocumentChanged() + \since 6.7 + + This signal is emitted when the underlying QTextDocument is + replaced with a different instance. + + \sa setTextDocument() +*/ + +/*! + \qmlmethod void QtQuick::TextDocument::save() + \brief Saves the contents to the same file and format specified by \l source. + \since 6.7 + + \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}. + + \sa source, saveAs() +*/ +void QQuickTextDocument::save() +{ + Q_D(QQuickTextDocument); + d->writeTo(d->url); +} + +/*! + \qmlmethod void QtQuick::TextDocument::saveAs(url url) + \brief Saves the contents to the file and format specified by \a url. + \since 6.7 + + The file extension in \a url specifies the file format + (as determined by QMimeDatabase::mimeTypeForUrl()). + + \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}. + + \sa source, saveAs() +*/ +void QQuickTextDocument::saveAs(const QUrl &url) +{ + Q_D(QQuickTextDocument); + if (!url.isLocalFile()) { + emit error(tr("Can only save to local files")); + return; + } + d->writeTo(url); + + if (url == d->url) + return; + + d->url = url; + emit sourceChanged(); +} + QQuickTextImageHandler::QQuickTextImageHandler(QObject *parent) : QObject(parent) { @@ -125,6 +346,13 @@ QSizeF QQuickTextImageHandler::intrinsicSize( return QSizeF(); } +/*! + \qmlsignal QtQuick::TextDocument::error(string message) + + This signal is emitted when an error \a message (translated string) should + be presented to the user, for example with a MessageDialog. +*/ + QT_END_NAMESPACE #include "moc_qquicktextdocument.cpp" diff --git a/src/quick/items/qquicktextdocument.h b/src/quick/items/qquicktextdocument.h index 5ed612b8ae..b2e8137c0a 100644 --- a/src/quick/items/qquicktextdocument.h +++ b/src/quick/items/qquicktextdocument.h @@ -13,16 +13,33 @@ class QQuickTextDocumentPrivate; class Q_QUICK_EXPORT QQuickTextDocument : public QObject { Q_OBJECT + Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged REVISION(6, 7) FINAL) + Q_PROPERTY(bool modified READ isModified WRITE setModified NOTIFY modifiedChanged REVISION(6, 7) FINAL) + QML_ANONYMOUS QML_ADDED_IN_VERSION(2, 0) public: QQuickTextDocument(QQuickItem *parent); + + QUrl source() const; + void setSource(const QUrl &url); + + bool isModified() const; + void setModified(bool modified); + QTextDocument *textDocument() const; void setTextDocument(QTextDocument *document); + Q_REVISION(6, 7) Q_INVOKABLE void save(); + Q_REVISION(6, 7) Q_INVOKABLE void saveAs(const QUrl &url); + Q_SIGNALS: Q_REVISION(6,7) void textDocumentChanged(); + Q_REVISION(6, 7) void sourceChanged(); + Q_REVISION(6, 7) void modifiedChanged(); + + Q_REVISION(6, 7) void error(const QString &message); private: Q_DISABLE_COPY(QQuickTextDocument) diff --git a/src/quick/items/qquicktextdocument_p.h b/src/quick/items/qquicktextdocument_p.h index 04aa06bb97..8c5bc07a0a 100644 --- a/src/quick/items/qquicktextdocument_p.h +++ b/src/quick/items/qquicktextdocument_p.h @@ -60,11 +60,16 @@ public: static QQuickTextDocumentPrivate *get(QQuickTextDocument *doc) { return doc->d_func(); } static const QQuickTextDocumentPrivate *get(const QQuickTextDocument *doc) { return doc->d_func(); } + void load(); + void writeTo(const QUrl &fileUrl); QTextDocument *document() const; void setDocument(QTextDocument *doc); // so far the QQuickItem given to the QQuickTextDocument ctor is always a QQuickTextEdit QQuickTextEdit *editor = nullptr; + QUrl url; + QMimeType mimeType; + std::optional<QStringConverter::Encoding> encoding; // only relevant for HTML }; namespace QtPrivate { diff --git a/src/quick/items/qquicktextedit.cpp b/src/quick/items/qquicktextedit.cpp index 9185c11848..ede599cfa3 100644 --- a/src/quick/items/qquicktextedit.cpp +++ b/src/quick/items/qquicktextedit.cpp @@ -2646,6 +2646,7 @@ void QQuickTextEditPrivate::init() document->setUndoRedoEnabled(false); // flush undo buffer. document->setUndoRedoEnabled(true); updateDefaultTextOption(); + document->setModified(false); // we merely changed some defaults: no edits worth saving yet q->updateSize(); #if QT_CONFIG(cursor) updateMouseCursorShape(); |