| 1 | // Copyright (C) 2016 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | |
| 4 | #include "qquicktextdocument.h" |
| 5 | #include "qquicktextdocument_p.h" |
| 6 | |
| 7 | #include "qquicktextedit_p.h" |
| 8 | |
| 9 | #include <QtQml/qqmlcontext.h> |
| 10 | #include <QtQml/qqmlfile.h> |
| 11 | #include <QtQml/qqmlinfo.h> |
| 12 | #include <QtQuick/private/qquickpixmap_p.h> |
| 13 | |
| 14 | #include <QtCore/qfile.h> |
| 15 | #include <QtCore/qpointer.h> |
| 16 | |
| 17 | QT_BEGIN_NAMESPACE |
| 18 | |
| 19 | Q_LOGGING_CATEGORY(lcTextDoc, "qt.quick.textdocument" ) |
| 20 | |
| 21 | using namespace Qt::StringLiterals; |
| 22 | |
| 23 | /*! |
| 24 | \qmltype TextDocument |
| 25 | \nativetype QQuickTextDocument |
| 26 | \inqmlmodule QtQuick |
| 27 | \brief A wrapper around TextEdit's backing QTextDocument. |
| 28 | \preliminary |
| 29 | |
| 30 | To load text into the document, set the \l source property. If the user then |
| 31 | modifies the text and wants to save the same document, call \l save() to save |
| 32 | it to the same source again (only if \l {QUrl::isLocalFile()}{it's a local file}). |
| 33 | Or call \l saveAs() to save it to a different file. |
| 34 | |
| 35 | This class cannot be instantiated in QML, but is available from \l TextEdit::textDocument. |
| 36 | |
| 37 | \note All loading and saving is done synchronously for now. |
| 38 | This may block the UI if the \l source is a slow network drive. |
| 39 | This may be improved in future versions of Qt. |
| 40 | |
| 41 | \note This API is considered tech preview and may change in future versions of Qt. |
| 42 | */ |
| 43 | |
| 44 | /*! |
| 45 | \class QQuickTextDocument |
| 46 | \since 5.1 |
| 47 | \brief The QQuickTextDocument class provides access to the QTextDocument of QQuickTextEdit. |
| 48 | \inmodule QtQuick |
| 49 | |
| 50 | This class provides access to the QTextDocument of QQuickTextEdit elements. |
| 51 | This is provided to allow usage of the \l{Rich Text Processing} functionalities of Qt, |
| 52 | including document modifications. It can also be used to output content, |
| 53 | for example with \l{QTextDocumentWriter}, or provide additional formatting, |
| 54 | for example with \l{QSyntaxHighlighter}. |
| 55 | */ |
| 56 | |
| 57 | /*! |
| 58 | Constructs a QQuickTextDocument object with |
| 59 | \a parent as the parent object. |
| 60 | */ |
| 61 | QQuickTextDocument::QQuickTextDocument(QQuickItem *parent) |
| 62 | : QObject(*(new QQuickTextDocumentPrivate), parent) |
| 63 | { |
| 64 | Q_D(QQuickTextDocument); |
| 65 | Q_ASSERT(parent); |
| 66 | d->editor = qobject_cast<QQuickTextEdit *>(object: parent); |
| 67 | Q_ASSERT(d->editor); |
| 68 | connect(sender: textDocument(), signal: &QTextDocument::modificationChanged, |
| 69 | context: this, slot: &QQuickTextDocument::modifiedChanged); |
| 70 | } |
| 71 | |
| 72 | /*! |
| 73 | \property QQuickTextDocument::status |
| 74 | \brief the status of document loading or saving |
| 75 | \since 6.7 |
| 76 | \preliminary |
| 77 | |
| 78 | This property holds the status of document loading or saving. It can be one of: |
| 79 | |
| 80 | \value Null No file has been loaded |
| 81 | \value Loading Reading from \l source has begun |
| 82 | \value Loaded Reading has successfully finished |
| 83 | \value Saving File writing has begun after save() or saveAs() |
| 84 | \value Saved Writing has successfully finished |
| 85 | \value ReadError An error occurred while reading from \l source |
| 86 | \value WriteError An error occurred in save() or saveAs() |
| 87 | \value NonLocalFileError saveAs() was called with a URL pointing |
| 88 | to a remote resource rather than a local file |
| 89 | |
| 90 | \sa errorString, source, save(), saveAs() |
| 91 | */ |
| 92 | |
| 93 | /*! |
| 94 | \qmlproperty enumeration QtQuick::TextDocument::status |
| 95 | \readonly |
| 96 | \since 6.7 |
| 97 | \preliminary |
| 98 | |
| 99 | This property holds the status of document loading or saving. It can be one of: |
| 100 | |
| 101 | \value TextDocument.Null No file has been loaded |
| 102 | \value TextDocument.Loading Reading from \l source has begun |
| 103 | \value TextDocument.Loaded Reading has successfully finished |
| 104 | \value TextDocument.Saving File writing has begun after save() or saveAs() |
| 105 | \value TextDocument.Saved Writing has successfully finished |
| 106 | \value TextDocument.ReadError An error occurred while reading from \l source |
| 107 | \value TextDocument.WriteError An error occurred in save() or saveAs() |
| 108 | \value TextDocument.NonLocalFileError saveAs() was called with a URL pointing |
| 109 | to a remote resource rather than a local file |
| 110 | |
| 111 | Use this status to provide an update or respond to the status change in some way. |
| 112 | For example, you could: |
| 113 | |
| 114 | \list |
| 115 | \li Trigger a state change: |
| 116 | \qml |
| 117 | State { |
| 118 | name: 'loaded' |
| 119 | when: textEdit.textDocument.status == textEdit.textDocument.Loaded |
| 120 | } |
| 121 | \endqml |
| 122 | |
| 123 | \li Implement an \c onStatusChanged signal handler: |
| 124 | \qml |
| 125 | TextEdit { |
| 126 | onStatusChanged: { |
| 127 | if (textDocument.status === textDocument.Loaded) |
| 128 | console.log('Loaded') |
| 129 | } |
| 130 | } |
| 131 | \endqml |
| 132 | |
| 133 | \li Bind to the status value: |
| 134 | |
| 135 | \snippet qml/textEditStatusSwitch.qml 0 |
| 136 | |
| 137 | \endlist |
| 138 | |
| 139 | \sa errorString, source, save(), saveAs() |
| 140 | */ |
| 141 | QQuickTextDocument::Status QQuickTextDocument::status() const |
| 142 | { |
| 143 | Q_D(const QQuickTextDocument); |
| 144 | return d->status; |
| 145 | } |
| 146 | |
| 147 | /*! |
| 148 | \property QQuickTextDocument::errorString |
| 149 | \brief a human-readable string describing the error that occurred during loading or saving, if any |
| 150 | \since 6.7 |
| 151 | \preliminary |
| 152 | |
| 153 | By default this string is empty. |
| 154 | |
| 155 | \sa status, source, save(), saveAs() |
| 156 | */ |
| 157 | |
| 158 | /*! |
| 159 | \qmlproperty string QtQuick::TextDocument::errorString |
| 160 | \readonly |
| 161 | \since 6.7 |
| 162 | \preliminary |
| 163 | |
| 164 | This property holds a human-readable string describing the error that |
| 165 | occurred during loading or saving, if any; otherwise, an empty string. |
| 166 | |
| 167 | \sa status, source, save(), saveAs() |
| 168 | */ |
| 169 | QString QQuickTextDocument::errorString() const |
| 170 | { |
| 171 | Q_D(const QQuickTextDocument); |
| 172 | return d->errorString; |
| 173 | } |
| 174 | |
| 175 | void QQuickTextDocumentPrivate::setStatus(QQuickTextDocument::Status s, const QString &err) |
| 176 | { |
| 177 | Q_Q(QQuickTextDocument); |
| 178 | if (status == s) |
| 179 | return; |
| 180 | |
| 181 | status = s; |
| 182 | emit q->statusChanged(); |
| 183 | |
| 184 | if (errorString == err) |
| 185 | return; |
| 186 | errorString = err; |
| 187 | emit q->errorStringChanged(); |
| 188 | if (!err.isEmpty()) |
| 189 | qmlWarning(me: q) << err; |
| 190 | } |
| 191 | |
| 192 | /*! |
| 193 | \property QQuickTextDocument::source |
| 194 | \brief the URL from which to load document contents |
| 195 | \since 6.7 |
| 196 | \preliminary |
| 197 | |
| 198 | QQuickTextDocument can handle any text format supported by Qt, loaded from |
| 199 | any URL scheme supported by Qt. |
| 200 | |
| 201 | The \c source property cannot be changed while the document's \l modified |
| 202 | state is \c true. If the user has modified the document contents, you |
| 203 | should prompt the user whether to \l save(), or else discard changes by |
| 204 | setting \l modified to \c false before setting the \c source property to a |
| 205 | different URL. |
| 206 | |
| 207 | \sa QTextDocumentWriter::supportedDocumentFormats() |
| 208 | */ |
| 209 | |
| 210 | /*! |
| 211 | \qmlproperty url QtQuick::TextDocument::source |
| 212 | \since 6.7 |
| 213 | \preliminary |
| 214 | |
| 215 | QQuickTextDocument can handle any text format supported by Qt, loaded from |
| 216 | any URL scheme supported by Qt. |
| 217 | |
| 218 | The URL may be absolute, or relative to the URL of the component. |
| 219 | |
| 220 | The \c source property cannot be changed while the document's \l modified |
| 221 | state is \c true. If the user has modified the document contents, you |
| 222 | should prompt the user whether to \l save(), or else discard changes by |
| 223 | setting \c {modified = false} before setting the \l source property to a |
| 224 | different URL. |
| 225 | |
| 226 | \sa QTextDocumentWriter::supportedDocumentFormats() |
| 227 | */ |
| 228 | QUrl QQuickTextDocument::source() const |
| 229 | { |
| 230 | Q_D(const QQuickTextDocument); |
| 231 | return d->url; |
| 232 | } |
| 233 | |
| 234 | void QQuickTextDocument::setSource(const QUrl &url) |
| 235 | { |
| 236 | Q_D(QQuickTextDocument); |
| 237 | |
| 238 | if (url == d->url) |
| 239 | return; |
| 240 | |
| 241 | if (isModified()) { |
| 242 | qmlWarning(me: this) << "Existing document modified: you should save()," |
| 243 | "or call TextEdit.clear() before setting a different source" ; |
| 244 | return; |
| 245 | } |
| 246 | |
| 247 | d->url = url; |
| 248 | emit sourceChanged(); |
| 249 | d->load(); |
| 250 | } |
| 251 | |
| 252 | /*! |
| 253 | \property QQuickTextDocument::modified |
| 254 | \brief whether the document has been modified by the user |
| 255 | \since 6.7 |
| 256 | \preliminary |
| 257 | |
| 258 | This property holds whether the document has been modified by the user |
| 259 | since the last time it was loaded or saved. By default, this property is |
| 260 | \c false. |
| 261 | |
| 262 | As with \l QTextDocument::modified, you can set the modified property: |
| 263 | for example, set it to \c false to allow setting the \l source property |
| 264 | to a different URL (thus discarding the user's changes). |
| 265 | |
| 266 | \sa QTextDocument::modified |
| 267 | */ |
| 268 | |
| 269 | /*! |
| 270 | \qmlproperty bool QtQuick::TextDocument::modified |
| 271 | \since 6.7 |
| 272 | \preliminary |
| 273 | |
| 274 | This property holds whether the document has been modified by the user |
| 275 | since the last time it was loaded or saved. By default, this property is |
| 276 | \c false. |
| 277 | |
| 278 | As with \l QTextDocument::modified, you can set the modified property: |
| 279 | for example, set it to \c false to allow setting the \l source property |
| 280 | to a different URL (thus discarding the user's changes). |
| 281 | |
| 282 | \sa QTextDocument::modified |
| 283 | */ |
| 284 | bool QQuickTextDocument::isModified() const |
| 285 | { |
| 286 | const auto *doc = textDocument(); |
| 287 | return doc && doc->isModified(); |
| 288 | } |
| 289 | |
| 290 | void QQuickTextDocument::setModified(bool modified) |
| 291 | { |
| 292 | if (auto *doc = textDocument()) |
| 293 | doc->setModified(modified); |
| 294 | } |
| 295 | |
| 296 | void QQuickTextDocumentPrivate::load() |
| 297 | { |
| 298 | auto *doc = editor->document(); |
| 299 | if (!doc) { |
| 300 | setStatus(s: QQuickTextDocument::Status::ReadError, |
| 301 | err: QQuickTextDocument::tr(s: "Null document object: cannot load" )); |
| 302 | return; |
| 303 | } |
| 304 | const QQmlContext *context = qmlContext(editor); |
| 305 | const QUrl &resolvedUrl = context ? context->resolvedUrl(url) : url; |
| 306 | const QString filePath = QQmlFile::urlToLocalFileOrQrc(resolvedUrl); |
| 307 | QFile file(filePath); |
| 308 | if (file.exists()) { |
| 309 | #if QT_CONFIG(mimetype) |
| 310 | QMimeType mimeType = QMimeDatabase().mimeTypeForFile(fileName: filePath); |
| 311 | const bool isHtml = mimeType.inherits(mimeTypeName: "text/html"_L1 ); |
| 312 | const bool isMarkdown = mimeType.inherits(mimeTypeName: "text/markdown"_L1 ) |
| 313 | || mimeType.inherits(mimeTypeName: "text/x-web-markdown"_L1 ); //Tika database |
| 314 | #else |
| 315 | const bool isHtml = filePath.endsWith(".html"_L1 , Qt::CaseInsensitive) || |
| 316 | filePath.endsWith(".htm"_L1 , Qt::CaseInsensitive); |
| 317 | const bool isMarkdown = filePath.endsWith(".md"_L1 , Qt::CaseInsensitive) || |
| 318 | filePath.endsWith(".markdown"_L1 , Qt::CaseInsensitive); |
| 319 | #endif |
| 320 | if (isHtml) |
| 321 | detectedFormat = Qt::RichText; |
| 322 | else if (isMarkdown) |
| 323 | detectedFormat = Qt::MarkdownText; |
| 324 | else |
| 325 | detectedFormat = Qt::PlainText; |
| 326 | if (file.open(flags: QFile::ReadOnly | QFile::Text)) { |
| 327 | setStatus(s: QQuickTextDocument::Status::Loading, err: {}); |
| 328 | QByteArray data = file.readAll(); |
| 329 | doc->setBaseUrl(resolvedUrl.adjusted(options: QUrl::RemoveFilename)); |
| 330 | #if QT_CONFIG(textmarkdownreader) || QT_CONFIG(texthtmlparser) |
| 331 | const bool plainText = editor->textFormat() == QQuickTextEdit::PlainText; |
| 332 | #endif |
| 333 | #if QT_CONFIG(textmarkdownreader) |
| 334 | if (!plainText && isMarkdown) { |
| 335 | doc->setMarkdown(markdown: QString::fromUtf8(ba: data)); |
| 336 | } else |
| 337 | #endif |
| 338 | #if QT_CONFIG(texthtmlparser) |
| 339 | if (!plainText && isHtml) { |
| 340 | // If a user loads an HTML file, remember the encoding. |
| 341 | // If the user then calls save() later, the same encoding will be used. |
| 342 | encoding = QStringConverter::encodingForHtml(data); |
| 343 | if (encoding) { |
| 344 | QStringDecoder decoder(*encoding); |
| 345 | doc->setHtml(decoder(data)); |
| 346 | } else { |
| 347 | // fall back to utf8 |
| 348 | doc->setHtml(QString::fromUtf8(ba: data)); |
| 349 | } |
| 350 | } else |
| 351 | #endif |
| 352 | { |
| 353 | doc->setPlainText(QString::fromUtf8(ba: data)); |
| 354 | } |
| 355 | setStatus(s: QQuickTextDocument::Status::Loaded, err: {}); |
| 356 | qCDebug(lcTextDoc) << editor << "loaded" << filePath |
| 357 | << "as" << editor->textFormat() << "detected" << detectedFormat |
| 358 | #if QT_CONFIG(mimetype) |
| 359 | << "(file type" << mimeType << ')' |
| 360 | #endif |
| 361 | ; |
| 362 | doc->setModified(false); |
| 363 | return; |
| 364 | } |
| 365 | setStatus(s: QQuickTextDocument::Status::ReadError, |
| 366 | err: QQuickTextDocument::tr(s: "Failed to read: %1" ).arg(a: file.errorString())); |
| 367 | } else { |
| 368 | setStatus(s: QQuickTextDocument::Status::ReadError, |
| 369 | err: QQuickTextDocument::tr(s: "%1 does not exist" ).arg(a: filePath)); |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | void QQuickTextDocumentPrivate::writeTo(const QUrl &fileUrl) |
| 374 | { |
| 375 | auto *doc = editor->document(); |
| 376 | if (!doc) |
| 377 | return; |
| 378 | |
| 379 | const QString filePath = fileUrl.toLocalFile(); |
| 380 | const bool sameUrl = fileUrl == url; |
| 381 | if (!sameUrl) { |
| 382 | #if QT_CONFIG(mimetype) |
| 383 | const auto type = QMimeDatabase().mimeTypeForUrl(url: fileUrl); |
| 384 | if (type.inherits(mimeTypeName: "text/html"_L1 )) |
| 385 | detectedFormat = Qt::RichText; |
| 386 | else if (type.inherits(mimeTypeName: "text/markdown"_L1 )) |
| 387 | detectedFormat = Qt::MarkdownText; |
| 388 | else |
| 389 | detectedFormat = Qt::PlainText; |
| 390 | #else |
| 391 | if (filePath.endsWith(".html"_L1 , Qt::CaseInsensitive) || |
| 392 | filePath.endsWith(".htm"_L1 , Qt::CaseInsensitive)) |
| 393 | detectedFormat = Qt::RichText; |
| 394 | else if (filePath.endsWith(".md"_L1 , Qt::CaseInsensitive) || |
| 395 | filePath.endsWith(".markdown"_L1 , Qt::CaseInsensitive)) |
| 396 | detectedFormat = Qt::MarkdownText; |
| 397 | else |
| 398 | detectedFormat = Qt::PlainText; |
| 399 | #endif |
| 400 | } |
| 401 | QFile file(filePath); |
| 402 | if (!file.open(flags: QFile::WriteOnly | QFile::Truncate | |
| 403 | (detectedFormat == Qt::RichText ? QFile::NotOpen : QFile::Text))) { |
| 404 | setStatus(s: QQuickTextDocument::Status::WriteError, |
| 405 | err: QQuickTextDocument::tr(s: "Cannot save: %1" ).arg(a: file.errorString())); |
| 406 | return; |
| 407 | } |
| 408 | setStatus(s: QQuickTextDocument::Status::Saving, err: {}); |
| 409 | QByteArray raw; |
| 410 | |
| 411 | switch (detectedFormat) { |
| 412 | #if QT_CONFIG(textmarkdownwriter) |
| 413 | case Qt::MarkdownText: |
| 414 | raw = doc->toMarkdown().toUtf8(); |
| 415 | break; |
| 416 | #endif |
| 417 | #if QT_CONFIG(texthtmlparser) |
| 418 | case Qt::RichText: |
| 419 | if (sameUrl && encoding) { |
| 420 | QStringEncoder enc(*encoding); |
| 421 | raw = enc.encode(str: doc->toHtml()); |
| 422 | } else { |
| 423 | // default to UTF-8 unless the user is saving the same file as previously loaded |
| 424 | raw = doc->toHtml().toUtf8(); |
| 425 | } |
| 426 | break; |
| 427 | #endif |
| 428 | default: |
| 429 | raw = doc->toPlainText().toUtf8(); |
| 430 | break; |
| 431 | } |
| 432 | |
| 433 | file.write(data: raw); |
| 434 | file.close(); |
| 435 | setStatus(s: QQuickTextDocument::Status::Saved, err: {}); |
| 436 | doc->setModified(false); |
| 437 | } |
| 438 | |
| 439 | QTextDocument *QQuickTextDocumentPrivate::document() const |
| 440 | { |
| 441 | return editor->document(); |
| 442 | } |
| 443 | |
| 444 | void QQuickTextDocumentPrivate::setDocument(QTextDocument *doc) |
| 445 | { |
| 446 | Q_Q(QQuickTextDocument); |
| 447 | QTextDocument *oldDoc = editor->document(); |
| 448 | if (doc == oldDoc) |
| 449 | return; |
| 450 | |
| 451 | if (oldDoc) |
| 452 | oldDoc->disconnect(receiver: q); |
| 453 | if (doc) { |
| 454 | q->connect(sender: doc, signal: &QTextDocument::modificationChanged, |
| 455 | context: q, slot: &QQuickTextDocument::modifiedChanged); |
| 456 | } |
| 457 | editor->setDocument(doc); |
| 458 | emit q->textDocumentChanged(); |
| 459 | } |
| 460 | |
| 461 | /*! |
| 462 | Returns a pointer to the QTextDocument object. |
| 463 | */ |
| 464 | QTextDocument *QQuickTextDocument::textDocument() const |
| 465 | { |
| 466 | Q_D(const QQuickTextDocument); |
| 467 | return d->document(); |
| 468 | } |
| 469 | |
| 470 | /*! |
| 471 | \brief Sets the given \a document. |
| 472 | \since 6.7 |
| 473 | |
| 474 | The caller retains ownership of the document. |
| 475 | */ |
| 476 | void QQuickTextDocument::setTextDocument(QTextDocument *document) |
| 477 | { |
| 478 | d_func()->setDocument(document); |
| 479 | } |
| 480 | |
| 481 | /*! |
| 482 | \fn void QQuickTextDocument::textDocumentChanged() |
| 483 | \since 6.7 |
| 484 | |
| 485 | This signal is emitted when the underlying QTextDocument is |
| 486 | replaced with a different instance. |
| 487 | |
| 488 | \sa setTextDocument() |
| 489 | */ |
| 490 | |
| 491 | /*! |
| 492 | \preliminary |
| 493 | \fn void QQuickTextDocument::sourceChanged() |
| 494 | */ |
| 495 | |
| 496 | /*! |
| 497 | \preliminary |
| 498 | \fn void QQuickTextDocument::modifiedChanged() |
| 499 | */ |
| 500 | |
| 501 | /*! |
| 502 | \preliminary |
| 503 | \fn void QQuickTextDocument::statusChanged() |
| 504 | */ |
| 505 | |
| 506 | /*! |
| 507 | \preliminary |
| 508 | \fn void QQuickTextDocument::errorStringChanged() |
| 509 | */ |
| 510 | |
| 511 | /*! |
| 512 | \fn void QQuickTextDocument::save() |
| 513 | \since 6.7 |
| 514 | \preliminary |
| 515 | |
| 516 | Saves the contents to the same file and format specified by \l source. |
| 517 | |
| 518 | \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}. |
| 519 | |
| 520 | \sa source, saveAs() |
| 521 | */ |
| 522 | |
| 523 | /*! |
| 524 | \qmlmethod void QtQuick::TextDocument::save() |
| 525 | \brief Saves the contents to the same file and format specified by \l source. |
| 526 | \since 6.7 |
| 527 | \preliminary |
| 528 | |
| 529 | \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}. |
| 530 | |
| 531 | \sa source, saveAs() |
| 532 | */ |
| 533 | void QQuickTextDocument::save() |
| 534 | { |
| 535 | Q_D(QQuickTextDocument); |
| 536 | d->writeTo(fileUrl: d->url); |
| 537 | } |
| 538 | |
| 539 | /*! |
| 540 | \fn void QQuickTextDocument::saveAs(const QUrl &url) |
| 541 | \brief Saves the contents to the file and format specified by \a url. |
| 542 | \since 6.7 |
| 543 | \preliminary |
| 544 | |
| 545 | The file extension in \a url specifies the file format |
| 546 | (as determined by QMimeDatabase::mimeTypeForUrl()). |
| 547 | |
| 548 | \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}. |
| 549 | |
| 550 | \sa source, save() |
| 551 | */ |
| 552 | |
| 553 | /*! |
| 554 | \qmlmethod void QtQuick::TextDocument::saveAs(url url) |
| 555 | \brief Saves the contents to the file and format specified by \a url. |
| 556 | \since 6.7 |
| 557 | \preliminary |
| 558 | |
| 559 | The file extension in \a url specifies the file format |
| 560 | (as determined by QMimeDatabase::mimeTypeForUrl()). |
| 561 | |
| 562 | \note You can save only to a \l {QUrl::isLocalFile()}{file on a mounted filesystem}. |
| 563 | |
| 564 | \sa source, save() |
| 565 | */ |
| 566 | void QQuickTextDocument::saveAs(const QUrl &url) |
| 567 | { |
| 568 | Q_D(QQuickTextDocument); |
| 569 | if (!url.isLocalFile()) { |
| 570 | d->setStatus(s: QQuickTextDocument::Status::NonLocalFileError, |
| 571 | err: QQuickTextDocument::tr(s: "Can only save to local files" )); |
| 572 | return; |
| 573 | } |
| 574 | d->writeTo(fileUrl: url); |
| 575 | |
| 576 | if (url == d->url) |
| 577 | return; |
| 578 | |
| 579 | d->url = url; |
| 580 | emit sourceChanged(); |
| 581 | } |
| 582 | |
| 583 | QQuickTextImageHandler::QQuickTextImageHandler(QObject *parent) |
| 584 | : QObject(parent) |
| 585 | { |
| 586 | } |
| 587 | |
| 588 | QSizeF QQuickTextImageHandler::intrinsicSize( |
| 589 | QTextDocument *doc, int, const QTextFormat &format) |
| 590 | { |
| 591 | if (format.isImageFormat()) { |
| 592 | QTextImageFormat imageFormat = format.toImageFormat(); |
| 593 | int width = qRound(d: imageFormat.width()); |
| 594 | const bool hasWidth = imageFormat.hasProperty(propertyId: QTextFormat::ImageWidth) && width > 0; |
| 595 | const int height = qRound(d: imageFormat.height()); |
| 596 | const bool hasHeight = imageFormat.hasProperty(propertyId: QTextFormat::ImageHeight) && height > 0; |
| 597 | const auto maxWidth = imageFormat.maximumWidth(); |
| 598 | const bool hasMaxWidth = imageFormat.hasProperty(propertyId: QTextFormat::ImageMaxWidth) && maxWidth.type() != QTextLength::VariableLength; |
| 599 | |
| 600 | int effectiveMaxWidth = INT_MAX; |
| 601 | if (hasMaxWidth) { |
| 602 | if (maxWidth.type() == QTextLength::PercentageLength) { |
| 603 | effectiveMaxWidth = (doc->pageSize().width() - 2 * doc->documentMargin()) * maxWidth.value(maximumLength: 100) / 100; |
| 604 | } else { |
| 605 | effectiveMaxWidth = maxWidth.rawValue(); |
| 606 | } |
| 607 | |
| 608 | width = qMin(a: effectiveMaxWidth, b: width); |
| 609 | } |
| 610 | |
| 611 | QSizeF size(width, height); |
| 612 | if (!hasWidth || !hasHeight) { |
| 613 | QVariant res = doc->resource(type: QTextDocument::ImageResource, name: QUrl(imageFormat.name())); |
| 614 | QImage image = res.value<QImage>(); |
| 615 | if (image.isNull()) { |
| 616 | // autotests expect us to reserve a 16x16 space for a "broken image" icon, |
| 617 | // even though we don't actually display one |
| 618 | if (!hasWidth) |
| 619 | size.setWidth(16); |
| 620 | if (!hasHeight) |
| 621 | size.setHeight(16); |
| 622 | return size; |
| 623 | } |
| 624 | QSize imgSize = image.size(); |
| 625 | if (imgSize.width() > effectiveMaxWidth) { |
| 626 | // image is bigger than effectiveMaxWidth, scale it down |
| 627 | imgSize.setHeight(effectiveMaxWidth * imgSize.height() / (qreal) imgSize.width()); |
| 628 | imgSize.setWidth(effectiveMaxWidth); |
| 629 | } |
| 630 | |
| 631 | if (!hasWidth) { |
| 632 | if (!hasHeight) |
| 633 | size.setWidth(imgSize.width()); |
| 634 | else |
| 635 | size.setWidth(qMin(a: effectiveMaxWidth, b: qRound(d: height * (imgSize.width() / (qreal) imgSize.height())))); |
| 636 | } |
| 637 | if (!hasHeight) { |
| 638 | if (!hasWidth) |
| 639 | size.setHeight(imgSize.height()); |
| 640 | else |
| 641 | size.setHeight(qRound(d: width * (imgSize.height() / (qreal) imgSize.width()))); |
| 642 | } |
| 643 | } |
| 644 | return size; |
| 645 | } |
| 646 | return QSizeF(); |
| 647 | } |
| 648 | |
| 649 | QT_END_NAMESPACE |
| 650 | |
| 651 | #include "moc_qquicktextdocument.cpp" |
| 652 | #include "moc_qquicktextdocument_p.cpp" |
| 653 | |