diff options
Diffstat (limited to 'src/gui/text/qtextdocument.cpp')
-rw-r--r-- | src/gui/text/qtextdocument.cpp | 2929 |
1 files changed, 2929 insertions, 0 deletions
diff --git a/src/gui/text/qtextdocument.cpp b/src/gui/text/qtextdocument.cpp new file mode 100644 index 0000000..e84b324 --- /dev/null +++ b/src/gui/text/qtextdocument.cpp @@ -0,0 +1,2929 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qtextdocument.h" +#include <qtextformat.h> +#include "qtextdocumentlayout_p.h" +#include "qtextdocumentfragment.h" +#include "qtextdocumentfragment_p.h" +#include "qtexttable.h" +#include "qtextlist.h" +#include <qdebug.h> +#include <qregexp.h> +#include <qvarlengtharray.h> +#include <qtextcodec.h> +#include <qthread.h> +#include "qtexthtmlparser_p.h" +#include "qpainter.h" +#include "qprinter.h" +#include "qtextedit.h" +#include <qfile.h> +#include <qfileinfo.h> +#include <qdir.h> +#include <qapplication.h> +#include "qtextcontrol_p.h" +#include "private/qtextedit_p.h" + +#include "qtextdocument_p.h" +#include <private/qprinter_p.h> + +#include <limits.h> + +QT_BEGIN_NAMESPACE + +Q_CORE_EXPORT unsigned int qt_int_sqrt(unsigned int n); + +/*! + Returns true if the string \a text is likely to be rich text; + otherwise returns false. + + This function uses a fast and therefore simple heuristic. It + mainly checks whether there is something that looks like a tag + before the first line break. Although the result may be correct + for common cases, there is no guarantee. + + This function is defined in the \c <QTextDocument> header file. +*/ +bool Qt::mightBeRichText(const QString& text) +{ + if (text.isEmpty()) + return false; + int start = 0; + + while (start < text.length() && text.at(start).isSpace()) + ++start; + + // skip a leading <?xml ... ?> as for example with xhtml + if (text.mid(start, 5) == QLatin1String("<?xml")) { + while (start < text.length()) { + if (text.at(start) == QLatin1Char('?') + && start + 2 < text.length() + && text.at(start + 1) == QLatin1Char('>')) { + start += 2; + break; + } + ++start; + } + + while (start < text.length() && text.at(start).isSpace()) + ++start; + } + + if (text.mid(start, 5).toLower() == QLatin1String("<!doc")) + return true; + int open = start; + while (open < text.length() && text.at(open) != QLatin1Char('<') + && text.at(open) != QLatin1Char('\n')) { + if (text.at(open) == QLatin1Char('&') && text.mid(open+1,3) == QLatin1String("lt;")) + return true; // support desperate attempt of user to see <...> + ++open; + } + if (open < text.length() && text.at(open) == QLatin1Char('<')) { + const int close = text.indexOf(QLatin1Char('>'), open); + if (close > -1) { + QString tag; + for (int i = open+1; i < close; ++i) { + if (text[i].isDigit() || text[i].isLetter()) + tag += text[i]; + else if (!tag.isEmpty() && text[i].isSpace()) + break; + else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!'))) + return false; // that's not a tag + } +#ifndef QT_NO_TEXTHTMLPARSER + return QTextHtmlParser::lookupElement(tag.toLower()) != -1; +#else + return false; +#endif // QT_NO_TEXTHTMLPARSER + } + } + return false; +} + +/*! + Converts the plain text string \a plain to a HTML string with + HTML metacharacters \c{<}, \c{>}, and \c{&} replaced by HTML + entities. + + Example: + + \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 0 + + This function is defined in the \c <QTextDocument> header file. + + \sa convertFromPlainText(), mightBeRichText() +*/ +QString Qt::escape(const QString& plain) +{ + QString rich; + rich.reserve(int(plain.length() * 1.1)); + for (int i = 0; i < plain.length(); ++i) { + if (plain.at(i) == QLatin1Char('<')) + rich += QLatin1String("<"); + else if (plain.at(i) == QLatin1Char('>')) + rich += QLatin1String(">"); + else if (plain.at(i) == QLatin1Char('&')) + rich += QLatin1String("&"); + else + rich += plain.at(i); + } + return rich; +} + +/*! + \fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode) + + Converts the plain text string \a plain to an HTML-formatted + paragraph while preserving most of its look. + + \a mode defines how whitespace is handled. + + This function is defined in the \c <QTextDocument> header file. + + \sa escape(), mightBeRichText() +*/ +QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode) +{ + int col = 0; + QString rich; + rich += QLatin1String("<p>"); + for (int i = 0; i < plain.length(); ++i) { + if (plain[i] == QLatin1Char('\n')){ + int c = 1; + while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) { + i++; + c++; + } + if (c == 1) + rich += QLatin1String("<br>\n"); + else { + rich += QLatin1String("</p>\n"); + while (--c > 1) + rich += QLatin1String("<br>\n"); + rich += QLatin1String("<p>"); + } + col = 0; + } else { + if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){ + rich += QChar(0x00a0U); + ++col; + while (col % 8) { + rich += QChar(0x00a0U); + ++col; + } + } + else if (mode == Qt::WhiteSpacePre && plain[i].isSpace()) + rich += QChar(0x00a0U); + else if (plain[i] == QLatin1Char('<')) + rich += QLatin1String("<"); + else if (plain[i] == QLatin1Char('>')) + rich += QLatin1String(">"); + else if (plain[i] == QLatin1Char('&')) + rich += QLatin1String("&"); + else + rich += plain[i]; + ++col; + } + } + if (col != 0) + rich += QLatin1String("</p>"); + return rich; +} + +#ifndef QT_NO_TEXTCODEC +/*! + \internal + + This function is defined in the \c <QTextDocument> header file. +*/ +QTextCodec *Qt::codecForHtml(const QByteArray &ba) +{ + return QTextCodec::codecForHtml(ba); +} +#endif + +/*! + \class QTextDocument + \reentrant + + \brief The QTextDocument class holds formatted text that can be + viewed and edited using a QTextEdit. + + \ingroup text + \mainclass + + QTextDocument is a container for structured rich text documents, providing + support for styled text and various types of document elements, such as + lists, tables, frames, and images. + They can be created for use in a QTextEdit, or used independently. + + Each document element is described by an associated format object. Each + format object is treated as a unique object by QTextDocuments, and can be + passed to objectForFormat() to obtain the document element that it is + applied to. + + A QTextDocument can be edited programmatically using a QTextCursor, and + its contents can be examined by traversing the document structure. The + entire document structure is stored as a hierarchy of document elements + beneath the root frame, found with the rootFrame() function. Alternatively, + if you just want to iterate over the textual contents of the document you + can use begin(), end(), and findBlock() to retrieve text blocks that you + can examine and iterate over. + + The layout of a document is determined by the documentLayout(); + you can create your own QAbstractTextDocumentLayout subclass and + set it using setDocumentLayout() if you want to use your own + layout logic. The document's title and other meta-information can be + obtained by calling the metaInformation() function. For documents that + are exposed to users through the QTextEdit class, the document title + is also available via the QTextEdit::documentTitle() function. + + The toPlainText() and toHtml() convenience functions allow you to retrieve the + contents of the document as plain text and HTML. + The document's text can be searched using the find() functions. + + Undo/redo of operations performed on the document can be controlled using + the setUndoRedoEnabled() function. The undo/redo system can be controlled + by an editor widget through the undo() and redo() slots; the document also + provides contentsChanged(), undoAvailable(), and redoAvailable() signals + that inform connected editor widgets about the state of the undo/redo + system. + + \sa QTextCursor QTextEdit \link richtext.html Rich Text Processing\endlink +*/ + +/*! + \property QTextDocument::defaultFont + \brief the default font used to display the document's text +*/ + +/*! + \property QTextDocument::defaultTextOption + \brief the default text option will be set on all \l{QTextLayout}s in the document. + + When \l{QTextBlock}s are created, the defaultTextOption is set on their + QTextLayout. This allows setting global properties for the document such as the + default word wrap mode. + */ + +/*! + Constructs an empty QTextDocument with the given \a parent. +*/ +QTextDocument::QTextDocument(QObject *parent) + : QObject(*new QTextDocumentPrivate, parent) +{ + Q_D(QTextDocument); + d->init(); +} + +/*! + Constructs a QTextDocument containing the plain (unformatted) \a text + specified, and with the given \a parent. +*/ +QTextDocument::QTextDocument(const QString &text, QObject *parent) + : QObject(*new QTextDocumentPrivate, parent) +{ + Q_D(QTextDocument); + d->init(); + QTextCursor(this).insertText(text); +} + +/*! + \internal +*/ +QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent) + : QObject(dd, parent) +{ + Q_D(QTextDocument); + d->init(); +} + +/*! + Destroys the document. +*/ +QTextDocument::~QTextDocument() +{ +} + + +/*! + Creates a new QTextDocument that is a copy of this text document. \a + parent is the parent of the returned text document. +*/ +QTextDocument *QTextDocument::clone(QObject *parent) const +{ + Q_D(const QTextDocument); + QTextDocument *doc = new QTextDocument(parent); + QTextCursor(doc).insertFragment(QTextDocumentFragment(this)); + doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat()); + QTextDocumentPrivate *priv = doc->d_func(); + priv->title = d->title; + priv->url = d->url; + priv->pageSize = d->pageSize; + priv->indentWidth = d->indentWidth; + priv->defaultTextOption = d->defaultTextOption; + priv->setDefaultFont(d->defaultFont()); + priv->resources = d->resources; + priv->cachedResources.clear(); +#ifndef QT_NO_CSSPARSER + priv->defaultStyleSheet = d->defaultStyleSheet; + priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet; +#endif + return doc; +} + +/*! + Returns true if the document is empty; otherwise returns false. +*/ +bool QTextDocument::isEmpty() const +{ + Q_D(const QTextDocument); + /* because if we're empty we still have one single paragraph as + * one single fragment */ + return d->length() <= 1; +} + +/*! + Clears the document. +*/ +void QTextDocument::clear() +{ + Q_D(QTextDocument); + d->clear(); + d->resources.clear(); +} + +/*! + \since 4.2 + + Undoes the last editing operation on the document if undo is + available. The provided \a cursor is positioned at the end of the + location where the edition operation was undone. + + See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework} + documentation for details. + + \sa undoAvailable(), isUndoRedoEnabled() +*/ +void QTextDocument::undo(QTextCursor *cursor) +{ + Q_D(QTextDocument); + const int pos = d->undoRedo(true); + if (cursor && pos >= 0) { + *cursor = QTextCursor(this); + cursor->setPosition(pos); + } +} + +/*! + \since 4.2 + Redoes the last editing operation on the document if \link + QTextDocument::isRedoAvailable() redo is available\endlink. + + The provided \a cursor is positioned at the end of the location where + the edition operation was redone. +*/ +void QTextDocument::redo(QTextCursor *cursor) +{ + Q_D(QTextDocument); + const int pos = d->undoRedo(false); + if (cursor && pos >= 0) { + *cursor = QTextCursor(this); + cursor->setPosition(pos); + } +} + +/*! + \overload + +*/ +void QTextDocument::undo() +{ + Q_D(QTextDocument); + d->undoRedo(true); +} + +/*! + \overload + Redoes the last editing operation on the document if \link + QTextDocument::isRedoAvailable() redo is available\endlink. +*/ +void QTextDocument::redo() +{ + Q_D(QTextDocument); + d->undoRedo(false); +} + +/*! + \internal + + Appends a custom undo \a item to the undo stack. +*/ +void QTextDocument::appendUndoItem(QAbstractUndoItem *item) +{ + Q_D(QTextDocument); + d->appendUndoItem(item); +} + +/*! + \property QTextDocument::undoRedoEnabled + \brief whether undo/redo are enabled for this document + + This defaults to true. If disabled, the undo stack is cleared and + no items will be added to it. +*/ +void QTextDocument::setUndoRedoEnabled(bool enable) +{ + Q_D(QTextDocument); + d->enableUndoRedo(enable); +} + +bool QTextDocument::isUndoRedoEnabled() const +{ + Q_D(const QTextDocument); + return d->isUndoRedoEnabled(); +} + +/*! + \property QTextDocument::maximumBlockCount + \since 4.2 + \brief Specifies the limit for blocks in the document. + + Specifies the maximum number of blocks the document may have. If there are + more blocks in the document that specified with this property blocks are removed + from the beginning of the document. + + A negative or zero value specifies that the document may contain an unlimited + amount of blocks. + + The default value is 0. + + Note that setting this property will apply the limit immediately to the document + contents. + + Setting this property also disables the undo redo history. + + This property is undefined in documents with tables or frames. +*/ +int QTextDocument::maximumBlockCount() const +{ + Q_D(const QTextDocument); + return d->maximumBlockCount; +} + +void QTextDocument::setMaximumBlockCount(int maximum) +{ + Q_D(QTextDocument); + d->maximumBlockCount = maximum; + d->ensureMaximumBlockCount(); + setUndoRedoEnabled(false); +} + +/*! + \since 4.3 + + The default text option is used on all QTextLayout objects in the document. + This allows setting global properties for the document such as the default + word wrap mode. +*/ +QTextOption QTextDocument::defaultTextOption() const +{ + Q_D(const QTextDocument); + return d->defaultTextOption; +} + +/*! + \since 4.3 + + Sets the default text option. +*/ +void QTextDocument::setDefaultTextOption(const QTextOption &option) +{ + Q_D(QTextDocument); + d->defaultTextOption = option; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +/*! + \fn void QTextDocument::markContentsDirty(int position, int length) + + Marks the contents specified by the given \a position and \a length + as "dirty", informing the document that it needs to be laid out + again. +*/ +void QTextDocument::markContentsDirty(int from, int length) +{ + Q_D(QTextDocument); + if (!d->inContentsChange) + d->beginEditBlock(); + d->documentChange(from, length); + if (!d->inContentsChange) + d->endEditBlock(); +} + +/*! + \property QTextDocument::useDesignMetrics + \since 4.1 + \brief whether the document uses design metrics of fonts to improve the accuracy of text layout + + If this property is set to true, the layout will use design metrics. + Otherwise, the metrics of the paint device as set on + QAbstractTextDocumentLayout::setPaintDevice() will be used. + + Using design metrics makes a layout have a width that is no longer dependent on hinting + and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width + scales much more linearly based on paintdevice metrics than it would otherwise. + + By default, this property is false. +*/ + +void QTextDocument::setUseDesignMetrics(bool b) +{ + Q_D(QTextDocument); + if (b == d->defaultTextOption.useDesignMetrics()) + return; + d->defaultTextOption.setUseDesignMetrics(b); + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +bool QTextDocument::useDesignMetrics() const +{ + Q_D(const QTextDocument); + return d->defaultTextOption.useDesignMetrics(); +} + +/*! + \since 4.2 + + Draws the content of the document with painter \a p, clipped to \a rect. + If \a rect is a null rectangle (default) then the document is painted unclipped. +*/ +void QTextDocument::drawContents(QPainter *p, const QRectF &rect) +{ + p->save(); + QAbstractTextDocumentLayout::PaintContext ctx; + if (rect.isValid()) { + p->setClipRect(rect); + ctx.clip = rect; + } + documentLayout()->draw(p, ctx); + p->restore(); +} + +/*! + \property QTextDocument::textWidth + \since 4.2 + + The text width specifies the preferred width for text in the document. If + the text (or content in general) is wider than the specified with it is broken + into multiple lines and grows vertically. If the text cannot be broken into multiple + lines to fit into the specified text width it will be larger and the size() and the + idealWidth() property will reflect that. + + If the text width is set to -1 then the text will not be broken into multiple lines + unless it is enforced through an explicit line break or a new paragraph. + + The default value is -1. + + Setting the text width will also set the page height to -1, causing the document to + grow or shrink vertically in a continuous way. If you want the document layout to break + the text into multiple pages then you have to set the pageSize property instead. + + \sa size(), idealWidth(), pageSize() +*/ +void QTextDocument::setTextWidth(qreal width) +{ + Q_D(QTextDocument); + QSizeF sz = d->pageSize; + sz.setWidth(width); + sz.setHeight(-1); + setPageSize(sz); +} + +qreal QTextDocument::textWidth() const +{ + Q_D(const QTextDocument); + return d->pageSize.width(); +} + +/*! + \since 4.2 + + Returns the ideal width of the text document. The ideal width is the actually used width + of the document without optional alignments taken into account. It is always <= size().width(). + + \sa adjustSize(), textWidth +*/ +qreal QTextDocument::idealWidth() const +{ + if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(documentLayout())) + return lout->idealWidth(); + return textWidth(); +} + +/*! + \property QTextDocument::documentMargin + \since 4.5 + + The margin around the document. The default is 4. +*/ +qreal QTextDocument::documentMargin() const +{ + Q_D(const QTextDocument); + return d->documentMargin; +} + +void QTextDocument::setDocumentMargin(qreal margin) +{ + Q_D(QTextDocument); + if (d->documentMargin != margin) { + d->documentMargin = margin; + + QTextFrame* root = rootFrame(); + QTextFrameFormat format = root->frameFormat(); + format.setMargin(margin); + root->setFrameFormat(format); + + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); + } +} + + +/*! + \property QTextDocument::indentWidth + \since 4.4 + + Returns the width used for text list and text block indenting. + + The indent properties of QTextListFormat and QTextBlockFormat specify + multiples of this value. The default indent width is 40. +*/ +qreal QTextDocument::indentWidth() const +{ + Q_D(const QTextDocument); + return d->indentWidth; +} + + +/*! + \since 4.4 + + Sets the \a width used for text list and text block indenting. + + The indent properties of QTextListFormat and QTextBlockFormat specify + multiples of this value. The default indent width is 40 . + + \sa indentWidth() +*/ +void QTextDocument::setIndentWidth(qreal width) +{ + Q_D(QTextDocument); + if (d->indentWidth != width) { + d->indentWidth = width; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); + } +} + + + + +/*! + \since 4.2 + + Adjusts the document to a reasonable size. + + \sa idealWidth(), textWidth, size +*/ +void QTextDocument::adjustSize() +{ + // Pull this private function in from qglobal.cpp + QFont f = defaultFont(); + QFontMetrics fm(f); + int mw = fm.width(QLatin1Char('x')) * 80; + int w = mw; + setTextWidth(w); + QSizeF size = documentLayout()->documentSize(); + if (size.width() != 0) { + w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3)); + setTextWidth(qMin(w, mw)); + + size = documentLayout()->documentSize(); + if (w*3 < 5*size.height()) { + w = qt_int_sqrt((uint)(2 * size.height() * size.width())); + setTextWidth(qMin(w, mw)); + } + } + setTextWidth(idealWidth()); +} + +/*! + \property QTextDocument::size + \since 4.2 + + Returns the actual size of the document. + This is equivalent to documentLayout()->documentSize(); + + The size of the document can be changed either by setting + a text width or setting an entire page size. + + Note that the width is always >= pageSize().width(). + + By default, for a newly-created, empty document, this property contains + a configuration-dependent size. + + \sa setTextWidth(), setPageSize(), idealWidth() +*/ +QSizeF QTextDocument::size() const +{ + return documentLayout()->documentSize(); +} + +/*! + \property QTextDocument::blockCount + \since 4.2 + + Returns the number of text blocks in the document. + + The value of this property is undefined in documents with tables or frames. + + By default, if defined, this property contains a value of 1. + \sa lineCount(), characterCount() +*/ +int QTextDocument::blockCount() const +{ + Q_D(const QTextDocument); + return d->blockMap().numNodes(); +} + + +/*! + \since 4.5 + + Returns the number of lines of this document (if the layout supports + this). Otherwise, this is identical to the number of blocks. + + \sa blockCount(), characterCount() + */ +int QTextDocument::lineCount() const +{ + Q_D(const QTextDocument); + return d->blockMap().length(2); +} + +/*! + \since 4.5 + + Returns the number of characters of this document. + + \sa blockCount(), characterAt() + */ +int QTextDocument::characterCount() const +{ + Q_D(const QTextDocument); + return d->length(); +} + +/*! + \since 4.5 + + Returns the character at position \a pos, or a null character if the + position is out of range. + + \sa characterCount() + */ +QChar QTextDocument::characterAt(int pos) const +{ + Q_D(const QTextDocument); + if (pos < 0 || pos >= d->length()) + return QChar(); + QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos); + const QTextFragmentData * const frag = fragIt.value(); + const int offsetInFragment = qMax(0, pos - fragIt.position()); + return d->text.at(frag->stringPosition + offsetInFragment); +} + + +/*! + \property QTextDocument::defaultStyleSheet + \since 4.2 + + The default style sheet is applied to all newly HTML formatted text that is + inserted into the document, for example using setHtml() or QTextCursor::insertHtml(). + + The style sheet needs to be compliant to CSS 2.1 syntax. + + \bold{Note:} Changing the default style sheet does not have any effect to the existing content + of the document. + + \sa {Supported HTML Subset} +*/ + +#ifndef QT_NO_CSSPARSER +void QTextDocument::setDefaultStyleSheet(const QString &sheet) +{ + Q_D(QTextDocument); + d->defaultStyleSheet = sheet; + QCss::Parser parser(sheet); + d->parsedDefaultStyleSheet = QCss::StyleSheet(); + d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent; + parser.parse(&d->parsedDefaultStyleSheet); +} + +QString QTextDocument::defaultStyleSheet() const +{ + Q_D(const QTextDocument); + return d->defaultStyleSheet; +} +#endif // QT_NO_CSSPARSER + +/*! + \fn void QTextDocument::contentsChanged() + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. + + \sa contentsChange() +*/ + +/*! + \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded) + + This signal is emitted whenever the document's content changes; for + example, when text is inserted or deleted, or when formatting is applied. + + Information is provided about the \a position of the character in the + document where the change occurred, the number of characters removed + (\a charsRemoved), and the number of characters added (\a charsAdded). + + The signal is emitted before the document's layout manager is notified + about the change. This hook allows you to implement syntax highlighting + for the document. + + \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged() +*/ + + +/*! + \fn QTextDocument::undoAvailable(bool available); + + This signal is emitted whenever undo operations become available + (\a available is true) or unavailable (\a available is false). + + See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework} + documentation for details. + + \sa undo(), isUndoRedoEnabled() +*/ + +/*! + \fn QTextDocument::redoAvailable(bool available); + + This signal is emitted whenever redo operations become available + (\a available is true) or unavailable (\a available is false). +*/ + +/*! + \fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor); + + This signal is emitted whenever the position of a cursor changed + due to an editing operation. The cursor that changed is passed in + \a cursor. If you need a signal when the cursor is moved with the + arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()} signal in + QTextEdit. +*/ + +/*! + \fn QTextDocument::blockCountChanged(int newBlockCount); + \since 4.3 + + This signal is emitted when the total number of text blocks in the + document changes. The value passed in \a newBlockCount is the new + total. +*/ + +/*! + \fn QTextDocument::documentLayoutChanged(); + \since 4.4 + + This signal is emitted when a new document layout is set. + + \sa setDocumentLayout() + +*/ + + +/*! + Returns true if undo is available; otherwise returns false. +*/ +bool QTextDocument::isUndoAvailable() const +{ + Q_D(const QTextDocument); + return d->isUndoAvailable(); +} + +/*! + Returns true if redo is available; otherwise returns false. +*/ +bool QTextDocument::isRedoAvailable() const +{ + Q_D(const QTextDocument); + return d->isRedoAvailable(); +} + + +/*! \since 4.4 + + Returns the document's revision (if undo is enabled). + + The revision is guaranteed to increase when a document that is not + modified is edited. + + \sa QTextBlock::revision(), isModified() + */ +int QTextDocument::revision() const +{ + Q_D(const QTextDocument); + return d->undoState; +} + + + +/*! + Sets the document to use the given \a layout. The previous layout + is deleted. + + \sa documentLayoutChanged() +*/ +void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout) +{ + Q_D(QTextDocument); + d->setLayout(layout); +} + +/*! + Returns the document layout for this document. +*/ +QAbstractTextDocumentLayout *QTextDocument::documentLayout() const +{ + Q_D(const QTextDocument); + if (!d->lout) { + QTextDocument *that = const_cast<QTextDocument *>(this); + that->d_func()->setLayout(new QTextDocumentLayout(that)); + } + return d->lout; +} + + +/*! + Returns meta information about the document of the type specified by + \a info. + + \sa setMetaInformation() +*/ +QString QTextDocument::metaInformation(MetaInformation info) const +{ + Q_D(const QTextDocument); + switch (info) { + case DocumentTitle: + return d->title; + case DocumentUrl: + return d->url; + } + return QString(); +} + +/*! + Sets the document's meta information of the type specified by \a info + to the given \a string. + + \sa metaInformation() +*/ +void QTextDocument::setMetaInformation(MetaInformation info, const QString &string) +{ + Q_D(QTextDocument); + switch (info) { + case DocumentTitle: + d->title = string; + break; + case DocumentUrl: + d->url = string; + break; + } +} + +/*! + Returns the plain text contained in the document. If you want + formatting information use a QTextCursor instead. + + \sa toHtml() +*/ +QString QTextDocument::toPlainText() const +{ + Q_D(const QTextDocument); + QString txt = d->plainText(); + + QChar *uc = txt.data(); + QChar *e = uc + txt.size(); + + for (; uc != e; ++uc) { + switch (uc->unicode()) { + case 0xfdd0: // QTextBeginningOfFrame + case 0xfdd1: // QTextEndOfFrame + case QChar::ParagraphSeparator: + case QChar::LineSeparator: + *uc = QLatin1Char('\n'); + break; + case QChar::Nbsp: + *uc = QLatin1Char(' '); + break; + default: + ; + } + } + return txt; +} + +/*! + Replaces the entire contents of the document with the given plain + \a text. + + \sa setHtml() +*/ +void QTextDocument::setPlainText(const QString &text) +{ + Q_D(QTextDocument); + bool previousState = d->isUndoRedoEnabled(); + d->enableUndoRedo(false); + d->clear(); + QTextCursor(this).insertText(text); + d->enableUndoRedo(previousState); +} + +/*! + Replaces the entire contents of the document with the given + HTML-formatted text in the \a html string. + + The HTML formatting is respected as much as possible; for example, + "<b>bold</b> text" will produce text where the first word has a font + weight that gives it a bold appearance: "\bold{bold} text". + + \note It is the responsibility of the caller to make sure that the + text is correctly decoded when a QString containing HTML is created + and passed to setHtml(). + + \sa setPlainText(), {Supported HTML Subset} +*/ + +#ifndef QT_NO_TEXTHTMLPARSER + +void QTextDocument::setHtml(const QString &html) +{ + Q_D(QTextDocument); + bool previousState = d->isUndoRedoEnabled(); + d->enableUndoRedo(false); + d->clear(); + QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import(); + d->enableUndoRedo(previousState); +} + +#endif // QT_NO_TEXTHTMLPARSER + +/*! + \enum QTextDocument::FindFlag + + This enum describes the options available to QTextDocument's find function. The options + can be OR-ed together from the following list: + + \value FindBackward Search backwards instead of forwards. + \value FindCaseSensitively By default find works case insensitive. Specifying this option + changes the behaviour to a case sensitive find operation. + \value FindWholeWords Makes find match only complete words. +*/ + +/*! + \enum QTextDocument::MetaInformation + + This enum describes the different types of meta information that can be + added to a document. + + \value DocumentTitle The title of the document. + \value DocumentUrl The url of the document. The loadResource() function uses + this url as the base when loading relative resources. + + \sa metaInformation(), setMetaInformation() +*/ + +/*! + \fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const + + \overload + + Finds the next occurrence of the string, \a subString, in the document. + The search starts at the given \a position, and proceeds forwards + through the document unless specified otherwise in the search options. + The \a options control the type of search performed. + + Returns a cursor with the match selected if \a subString + was found; otherwise returns a null cursor. + + If the \a position is 0 (the default) the search begins from the beginning + of the document; otherwise it begins at the specified position. +*/ +QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const +{ + QRegExp expr(subString); + expr.setPatternSyntax(QRegExp::FixedString); + expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); + + return find(expr, from, options); +} + +/*! + \fn QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const + + Finds the next occurrence of the string, \a subString, in the document. + The search starts at the position of the given \a cursor, and proceeds + forwards through the document unless specified otherwise in the search + options. The \a options control the type of search performed. + + Returns a cursor with the match selected if \a subString was found; otherwise + returns a null cursor. + + If the given \a cursor has a selection, the search begins after the + selection; otherwise it begins at the cursor's position. + + By default the search is case-sensitive, and can match text anywhere in the + document. +*/ +QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &from, FindFlags options) const +{ + int pos = 0; + if (!from.isNull()) { + if (options & QTextDocument::FindBackward) + pos = from.selectionStart(); + else + pos = from.selectionEnd(); + } + QRegExp expr(subString); + expr.setPatternSyntax(QRegExp::FixedString); + expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); + + return find(expr, pos, options); +} + + +static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset, + QTextDocument::FindFlags options, QTextCursor &cursor) +{ + const QRegExp expr(expression); + QString text = block.text(); + text.replace(QChar::Nbsp, QLatin1Char(' ')); + + int idx = -1; + while (offset >=0 && offset <= text.length()) { + idx = (options & QTextDocument::FindBackward) ? + expr.lastIndexIn(text, offset) : expr.indexIn(text, offset); + if (idx == -1) + return false; + + if (options & QTextDocument::FindWholeWords) { + const int start = idx; + const int end = start + expr.matchedLength(); + if ((start != 0 && text.at(start - 1).isLetterOrNumber()) + || (end != text.length() && text.at(end).isLetterOrNumber())) { + //if this is not a whole word, continue the search in the string + offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1; + idx = -1; + continue; + } + } + //we have a hit, return the cursor for that. + break; + } + if (idx == -1) + return false; + cursor = QTextCursor(block.docHandle(), block.position() + idx); + cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor); + return true; +} + +/*! + \fn QTextCursor QTextDocument::find(const QRegExp & expr, int position, FindFlags options) const + + \overload + + Finds the next occurrence, matching the regular expression, \a expr, in the document. + The search starts at the given \a position, and proceeds forwards + through the document unless specified otherwise in the search options. + The \a options control the type of search performed. The FindCaseSensitively + option is ignored for this overload, use QRegExp::caseSensitivity instead. + + Returns a cursor with the match selected if a match was found; otherwise + returns a null cursor. + + If the \a position is 0 (the default) the search begins from the beginning + of the document; otherwise it begins at the specified position. +*/ +QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const +{ + Q_D(const QTextDocument); + + if (expr.isEmpty()) + return QTextCursor(); + + int pos = from; + //the cursor is positioned between characters, so for a backward search + //do not include the character given in the position. + if (options & FindBackward) { + --pos ; + if(pos < 0) + return QTextCursor(); + } + + QTextCursor cursor; + QTextBlock block = d->blocksFind(pos); + + if (!(options & FindBackward)) { + int blockOffset = qMax(0, pos - block.position()); + while (block.isValid()) { + if (findInBlock(block, expr, blockOffset, options, cursor)) + return cursor; + blockOffset = 0; + block = block.next(); + } + } else { + int blockOffset = pos - block.position(); + while (block.isValid()) { + if (findInBlock(block, expr, blockOffset, options, cursor)) + return cursor; + block = block.previous(); + blockOffset = block.length() - 1; + } + } + + return QTextCursor(); +} + +/*! + \fn QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const + + Finds the next occurrence, matching the regular expression, \a expr, in the document. + The search starts at the position of the given \a cursor, and proceeds + forwards through the document unless specified otherwise in the search + options. The \a options control the type of search performed. The FindCaseSensitively + option is ignored for this overload, use QRegExp::caseSensitivity instead. + + Returns a cursor with the match selected if a match was found; otherwise + returns a null cursor. + + If the given \a cursor has a selection, the search begins after the + selection; otherwise it begins at the cursor's position. + + By default the search is case-sensitive, and can match text anywhere in the + document. +*/ +QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &from, FindFlags options) const +{ + int pos = 0; + if (!from.isNull()) { + if (options & QTextDocument::FindBackward) + pos = from.selectionStart(); + else + pos = from.selectionEnd(); + } + return find(expr, pos, options); +} + + +/*! + \fn QTextObject *QTextDocument::createObject(const QTextFormat &format) + + Creates and returns a new document object (a QTextObject), based + on the given \a format. + + QTextObjects will always get created through this method, so you + must reimplement it if you use custom text objects inside your document. +*/ +QTextObject *QTextDocument::createObject(const QTextFormat &f) +{ + QTextObject *obj = 0; + if (f.isListFormat()) + obj = new QTextList(this); + else if (f.isTableFormat()) + obj = new QTextTable(this); + else if (f.isFrameFormat()) + obj = new QTextFrame(this); + + return obj; +} + +/*! + \internal + + Returns the frame that contains the text cursor position \a pos. +*/ +QTextFrame *QTextDocument::frameAt(int pos) const +{ + Q_D(const QTextDocument); + return d->frameAt(pos); +} + +/*! + Returns the document's root frame. +*/ +QTextFrame *QTextDocument::rootFrame() const +{ + Q_D(const QTextDocument); + return d->rootFrame(); +} + +/*! + Returns the text object associated with the given \a objectIndex. +*/ +QTextObject *QTextDocument::object(int objectIndex) const +{ + Q_D(const QTextDocument); + return d->objectForIndex(objectIndex); +} + +/*! + Returns the text object associated with the format \a f. +*/ +QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const +{ + Q_D(const QTextDocument); + return d->objectForFormat(f); +} + + +/*! + Returns the text block that contains the \a{pos}-th character. +*/ +QTextBlock QTextDocument::findBlock(int pos) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(pos)); +} + +/*! + \since 4.4 + Returns the text block with the specified \a blockNumber. + + \sa QTextBlock::blockNumber() +*/ +QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1)); +} + +/*! + \since 4.5 + Returns the text block that contains the specified \a lineNumber. + + \sa QTextBlock::firstLineNumber() +*/ +QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2)); +} + +/*! + Returns the document's first text block. + + \sa firstBlock() +*/ +QTextBlock QTextDocument::begin() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().begin().n); +} + +/*! + This function returns a block to test for the end of the document + while iterating over it. + + \snippet doc/src/snippets/textdocumentendsnippet.cpp 0 + + The block returned is invalid and represents the block after the + last block in the document. You can use lastBlock() to retrieve the + last valid block of the document. + + \sa lastBlock() +*/ +QTextBlock QTextDocument::end() const +{ + return QTextBlock(docHandle(), 0); +} + +/*! + \since 4.4 + Returns the document's first text block. +*/ +QTextBlock QTextDocument::firstBlock() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().begin().n); +} + +/*! + \since 4.4 + Returns the document's last (valid) text block. +*/ +QTextBlock QTextDocument::lastBlock() const +{ + Q_D(const QTextDocument); + return QTextBlock(docHandle(), d->blockMap().last().n); +} + +/*! + \property QTextDocument::pageSize + \brief the page size that should be used for laying out the document + + By default, for a newly-created, empty document, this property contains + an undefined size. + + \sa modificationChanged() +*/ + +void QTextDocument::setPageSize(const QSizeF &size) +{ + Q_D(QTextDocument); + d->pageSize = size; + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +QSizeF QTextDocument::pageSize() const +{ + Q_D(const QTextDocument); + return d->pageSize; +} + +/*! + returns the number of pages in this document. +*/ +int QTextDocument::pageCount() const +{ + return documentLayout()->pageCount(); +} + +/*! + Sets the default \a font to use in the document layout. +*/ +void QTextDocument::setDefaultFont(const QFont &font) +{ + Q_D(QTextDocument); + d->setDefaultFont(font); + if (d->lout) + d->lout->documentChanged(0, 0, d->length()); +} + +/*! + Returns the default font to be used in the document layout. +*/ +QFont QTextDocument::defaultFont() const +{ + Q_D(const QTextDocument); + return d->defaultFont(); +} + +/*! + \fn QTextDocument::modificationChanged(bool changed) + + This signal is emitted whenever the content of the document + changes in a way that affects the modification state. If \a + changed is true, the document has been modified; otherwise it is + false. + + For example, calling setModified(false) on a document and then + inserting text causes the signal to get emitted. If you undo that + operation, causing the document to return to its original + unmodified state, the signal will get emitted again. +*/ + +/*! + \property QTextDocument::modified + \brief whether the document has been modified by the user + + By default, this property is false. + + \sa modificationChanged() +*/ + +bool QTextDocument::isModified() const +{ + return docHandle()->isModified(); +} + +void QTextDocument::setModified(bool m) +{ + docHandle()->setModified(m); +} + +#ifndef QT_NO_PRINTER +static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos) +{ + painter->save(); + painter->translate(body.left(), body.top() - (index - 1) * body.height()); + QRectF view(0, (index - 1) * body.height(), body.width(), body.height()); + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + QAbstractTextDocumentLayout::PaintContext ctx; + + painter->setClipRect(view); + ctx.clip = view; + + // don't use the system palette text as default text color, on HP/UX + // for example that's white, and white text on white paper doesn't + // look that nice + ctx.palette.setColor(QPalette::Text, Qt::black); + + layout->draw(painter, ctx); + + if (!pageNumberPos.isNull()) { + painter->setClipping(false); + painter->setFont(QFont(doc->defaultFont())); + const QString pageString = QString::number(index); + + painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().width(pageString)), + qRound(pageNumberPos.y() + view.top()), + pageString); + } + + painter->restore(); +} + +extern int qt_defaultDpi(); + +/*! + Prints the document to the given \a printer. The QPrinter must be + set up before being used with this function. + + This is only a convenience method to print the whole document to the printer. + + If the document is already paginated through a specified height in the pageSize() + property it is printed as-is. + + If the document is not paginated, like for example a document used in a QTextEdit, + then a temporary copy of the document is created and the copy is broken into + multiple pages according to the size of the QPrinter's paperRect(). By default + a 2 cm margin is set around the document contents. In addition the current page + number is printed at the bottom of each page. + + Note that QPrinter::Selection is not supported as print range with this function since + the selection is a property of QTextCursor. If you have a QTextEdit associated with + your QTextDocument then you can use QTextEdit's print() function because QTextEdit has + access to the user's selection. + + \sa QTextEdit::print() +*/ + +void QTextDocument::print(QPrinter *printer) const +{ + Q_D(const QTextDocument); + + if (!printer || !printer->isValid()) + return; + + if (!d->title.isEmpty()) + printer->setDocName(d->title); + + bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull() + && d->pageSize.height() != INT_MAX; + + if (!documentPaginated && !printer->fullPage() && !printer->d_func()->hasCustomPageMargins) + printer->setPageMargins(23.53, 23.53, 23.53, 23.53, QPrinter::Millimeter); + + QPainter p(printer); + + // Check that there is a valid device to print to. + if (!p.isActive()) + return; + + const QTextDocument *doc = this; + QTextDocument *clonedDoc = 0; + (void)doc->documentLayout(); // make sure that there is a layout + + QRectF body = QRectF(QPointF(0, 0), d->pageSize); + QPointF pageNumberPos; + + if (documentPaginated) { + qreal sourceDpiX = qt_defaultDpi(); + qreal sourceDpiY = sourceDpiX; + + QPaintDevice *dev = doc->documentLayout()->paintDevice(); + if (dev) { + sourceDpiX = dev->logicalDpiX(); + sourceDpiY = dev->logicalDpiY(); + } + + const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX; + const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY; + + // scale to dpi + p.scale(dpiScaleX, dpiScaleY); + + QSizeF scaledPageSize = d->pageSize; + scaledPageSize.rwidth() *= dpiScaleX; + scaledPageSize.rheight() *= dpiScaleY; + + const QSizeF printerPageSize(printer->pageRect().size()); + + // scale to page + p.scale(printerPageSize.width() / scaledPageSize.width(), + printerPageSize.height() / scaledPageSize.height()); + } else { + doc = clone(const_cast<QTextDocument *>(this)); + clonedDoc = const_cast<QTextDocument *>(doc); + + for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock(); + srcBlock.isValid() && dstBlock.isValid(); + srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) { + dstBlock.layout()->setAdditionalFormats(srcBlock.layout()->additionalFormats()); + } + + QAbstractTextDocumentLayout *layout = doc->documentLayout(); + layout->setPaintDevice(p.device()); + + int dpiy = p.device()->logicalDpiY(); + int margin = 0; + if (printer->fullPage() && !printer->d_func()->hasCustomPageMargins) { + // for compatibility + margin = (int) ((2/2.54)*dpiy); // 2 cm margins + QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + fmt.setMargin(margin); + doc->rootFrame()->setFrameFormat(fmt); + } + + QRectF pageRect(printer->pageRect()); + body = QRectF(0, 0, pageRect.width(), pageRect.height()); + pageNumberPos = QPointF(body.width() - margin, + body.height() - margin + + QFontMetrics(doc->defaultFont(), p.device()).ascent() + + 5 * dpiy / 72.0); + clonedDoc->setPageSize(body.size()); + } + + int docCopies; + int pageCopies; + if (printer->collateCopies() == true){ + docCopies = 1; + pageCopies = printer->numCopies(); + } else { + docCopies = printer->numCopies(); + pageCopies = 1; + } + + int fromPage = printer->fromPage(); + int toPage = printer->toPage(); + bool ascending = true; + + if (fromPage == 0 && toPage == 0) { + fromPage = 1; + toPage = doc->pageCount(); + } + // paranoia check + fromPage = qMax(1, fromPage); + toPage = qMin(doc->pageCount(), toPage); + + if (printer->pageOrder() == QPrinter::LastPageFirst) { + int tmp = fromPage; + fromPage = toPage; + toPage = tmp; + ascending = false; + } + + for (int i = 0; i < docCopies; ++i) { + + int page = fromPage; + while (true) { + for (int j = 0; j < pageCopies; ++j) { + if (printer->printerState() == QPrinter::Aborted + || printer->printerState() == QPrinter::Error) + goto UserCanceled; + printPage(page, &p, doc, body, pageNumberPos); + if (j < pageCopies - 1) + printer->newPage(); + } + + if (page == toPage) + break; + + if (ascending) + ++page; + else + --page; + + printer->newPage(); + } + + if ( i < docCopies - 1) + printer->newPage(); + } + +UserCanceled: + delete clonedDoc; +} +#endif + +/*! + \enum QTextDocument::ResourceType + + This enum describes the types of resources that can be loaded by + QTextDocument's loadResource() function. + + \value HtmlResource The resource contains HTML. + \value ImageResource The resource contains image data. + Currently supported data types are QVariant::Pixmap and + QVariant::Image. If the corresponding variant is of type + QVariant::ByteArray then Qt attempts to load the image using + QImage::loadFromData. QVariant::Icon is currently not supported. + The icon needs to be converted to one of the supported types first, + for example using QIcon::pixmap. + \value StyleSheetResource The resource contains CSS. + \value UserResource The first available value for user defined + resource types. + + \sa loadResource() +*/ + +/*! + Returns data of the specified \a type from the resource with the + given \a name. + + This function is called by the rich text engine to request data that isn't + directly stored by QTextDocument, but still associated with it. For example, + images are referenced indirectly by the name attribute of a QTextImageFormat + object. + + Resources are cached internally in the document. If a resource can + not be found in the cache, loadResource is called to try to load + the resource. loadResource should then use addResource to add the + resource to the cache. + + \sa QTextDocument::ResourceType +*/ +QVariant QTextDocument::resource(int type, const QUrl &name) const +{ + Q_D(const QTextDocument); + QVariant r = d->resources.value(name); + if (!r.isValid()) { + r = d->cachedResources.value(name); + if (!r.isValid()) + r = const_cast<QTextDocument *>(this)->loadResource(type, name); + } + return r; +} + +/*! + Adds the resource \a resource to the resource cache, using \a + type and \a name as identifiers. \a type should be a value from + QTextDocument::ResourceType. + + For example, you can add an image as a resource in order to reference it + from within the document: + + \snippet snippets/textdocument-resources/main.cpp Adding a resource + + The image can be inserted into the document using the QTextCursor API: + + \snippet snippets/textdocument-resources/main.cpp Inserting an image with a cursor + + Alternatively, you can insert images using the HTML \c img tag: + + \snippet snippets/textdocument-resources/main.cpp Inserting an image using HTML +*/ +void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource) +{ + Q_UNUSED(type); + Q_D(QTextDocument); + d->resources.insert(name, resource); +} + +/*! + Loads data of the specified \a type from the resource with the + given \a name. + + This function is called by the rich text engine to request data that isn't + directly stored by QTextDocument, but still associated with it. For example, + images are referenced indirectly by the name attribute of a QTextImageFormat + object. + + When called by Qt, \a type is one of the values of + QTextDocument::ResourceType. + + If the QTextDocument is a child object of a QTextEdit, QTextBrowser, + or a QTextDocument itself then the default implementation tries + to retrieve the data from the parent. +*/ +QVariant QTextDocument::loadResource(int type, const QUrl &name) +{ + Q_D(QTextDocument); + QVariant r; + + QTextDocument *doc = qobject_cast<QTextDocument *>(parent()); + if (doc) { + r = doc->loadResource(type, name); + } +#ifndef QT_NO_TEXTEDIT + else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent())) { + QUrl resolvedName = edit->d_func()->resolveUrl(name); + r = edit->loadResource(type, resolvedName); + } +#endif +#ifndef QT_NO_TEXTCONTROL + else if (QTextControl *control = qobject_cast<QTextControl *>(parent())) { + r = control->loadResource(type, name); + } +#endif + + // if resource was not loaded try to load it here + if (!doc && r.isNull() && name.isRelative()) { + QUrl currentURL = d->url; + QUrl resourceUrl = name; + + // For the second case QUrl can merge "#someanchor" with "foo.html" + // correctly to "foo.html#someanchor" + if (!(currentURL.isRelative() + || (currentURL.scheme() == QLatin1String("file") + && !QFileInfo(currentURL.toLocalFile()).isAbsolute())) + || (name.hasFragment() && name.path().isEmpty())) { + resourceUrl = currentURL.resolved(name); + } else { + // this is our last resort when current url and new url are both relative + // we try to resolve against the current working directory in the local + // file system. + QFileInfo fi(currentURL.toLocalFile()); + if (fi.exists()) { + resourceUrl = + QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name); + } + } + + QString s = resourceUrl.toLocalFile(); + QFile f(s); + if (!s.isEmpty() && f.open(QFile::ReadOnly)) { + r = f.readAll(); + f.close(); + } + } + + if (!r.isNull()) { + if (type == ImageResource && r.type() == QVariant::ByteArray) { + if (qApp->thread() != QThread::currentThread()) { + // must use images in non-GUI threads + QImage image; + image.loadFromData(r.toByteArray()); + if (!image.isNull()) + r = image; + } else { + QPixmap pm; + pm.loadFromData(r.toByteArray()); + if (!pm.isNull()) + r = pm; + } + } + d->cachedResources.insert(name, r); + } + return r; +} + +static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to) +{ + QTextFormat diff = to; + + const QMap<int, QVariant> props = to.properties(); + for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end(); + it != end; ++it) + if (it.value() == from.property(it.key())) + diff.clearProperty(it.key()); + + return diff; +} + +QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc) + : doc(_doc), fragmentMarkers(false) +{ + const QFont defaultFont = doc->defaultFont(); + defaultCharFormat.setFont(defaultFont); + // don't export those for the default font since we cannot turn them off with CSS + defaultCharFormat.clearProperty(QTextFormat::FontUnderline); + defaultCharFormat.clearProperty(QTextFormat::FontOverline); + defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut); + defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle); +} + +/*! + Returns the document in HTML format. The conversion may not be + perfect, especially for complex documents, due to the limitations + of HTML. +*/ +QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode) +{ + html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" " + "\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" + "<html><head><meta name=\"qrichtext\" content=\"1\" />"); + html.reserve(doc->docHandle()->length()); + + fragmentMarkers = (mode == ExportFragment); + + if (!encoding.isEmpty()) + html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding)); + + QString title = doc->metaInformation(QTextDocument::DocumentTitle); + if (!title.isEmpty()) + html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>"); + html += QLatin1String("<style type=\"text/css\">\n"); + html += QLatin1String("p, li { white-space: pre-wrap; }\n"); + html += QLatin1String("</style>"); + html += QLatin1String("</head><body"); + + if (mode == ExportEntireDocument) { + html += QLatin1String(" style=\""); + + emitFontFamily(defaultCharFormat.fontFamily()); + + if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) { + html += QLatin1String(" font-size:"); + html += QString::number(defaultCharFormat.fontPointSize()); + html += QLatin1String("pt;"); + } + + html += QLatin1String(" font-weight:"); + html += QString::number(defaultCharFormat.fontWeight() * 8); + html += QLatin1Char(';'); + + html += QLatin1String(" font-style:"); + html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal")); + html += QLatin1Char(';'); + + // do not set text-decoration on the default font since those values are /always/ propagated + // and cannot be turned off with CSS + + html += QLatin1Char('\"'); + + const QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); + emitBackgroundAttribute(fmt); + + } else { + defaultCharFormat = QTextCharFormat(); + } + html += QLatin1Char('>'); + + QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat(); + rootFmt.clearProperty(QTextFormat::BackgroundBrush); + + QTextFrameFormat defaultFmt; + defaultFmt.setMargin(doc->documentMargin()); + + if (rootFmt == defaultFmt) + emitFrame(doc->rootFrame()->begin()); + else + emitTextFrame(doc->rootFrame()); + + html += QLatin1String("</body></html>"); + return html; +} + +void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value) +{ + html += QLatin1Char(' '); + html += QLatin1String(attribute); + html += QLatin1String("=\""); + html += value; + html += QLatin1Char('"'); +} + +bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) +{ + bool attributesEmitted = false; + + { + const QString family = format.fontFamily(); + if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) { + emitFontFamily(family); + attributesEmitted = true; + } + } + + if (format.hasProperty(QTextFormat::FontPointSize) + && format.fontPointSize() != defaultCharFormat.fontPointSize()) { + html += QLatin1String(" font-size:"); + html += QString::number(format.fontPointSize()); + html += QLatin1String("pt;"); + attributesEmitted = true; + } else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) { + static const char * const sizeNames[] = { + "small", "medium", "large", "x-large", "xx-large" + }; + const char *name = 0; + const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1; + if (idx >= 0 && idx <= 4) { + name = sizeNames[idx]; + } + if (name) { + html += QLatin1String(" font-size:"); + html += QLatin1String(name); + html += QLatin1Char(';'); + attributesEmitted = true; + } + } + + if (format.hasProperty(QTextFormat::FontWeight) + && format.fontWeight() != defaultCharFormat.fontWeight()) { + html += QLatin1String(" font-weight:"); + html += QString::number(format.fontWeight() * 8); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.hasProperty(QTextFormat::FontItalic) + && format.fontItalic() != defaultCharFormat.fontItalic()) { + html += QLatin1String(" font-style:"); + html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal")); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + QLatin1String decorationTag(" text-decoration:"); + html += decorationTag; + bool hasDecoration = false; + bool atLeastOneDecorationSet = false; + + if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle)) + && format.fontUnderline() != defaultCharFormat.fontUnderline()) { + hasDecoration = true; + if (format.fontUnderline()) { + html += QLatin1String(" underline"); + atLeastOneDecorationSet = true; + } + } + + if (format.hasProperty(QTextFormat::FontOverline) + && format.fontOverline() != defaultCharFormat.fontOverline()) { + hasDecoration = true; + if (format.fontOverline()) { + html += QLatin1String(" overline"); + atLeastOneDecorationSet = true; + } + } + + if (format.hasProperty(QTextFormat::FontStrikeOut) + && format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) { + hasDecoration = true; + if (format.fontStrikeOut()) { + html += QLatin1String(" line-through"); + atLeastOneDecorationSet = true; + } + } + + if (hasDecoration) { + if (!atLeastOneDecorationSet) + html += QLatin1String("none"); + html += QLatin1Char(';'); + attributesEmitted = true; + } else { + html.chop(qstrlen(decorationTag.latin1())); + } + + if (format.foreground() != defaultCharFormat.foreground() + && format.foreground().style() != Qt::NoBrush) { + html += QLatin1String(" color:"); + html += format.foreground().color().name(); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.background() != defaultCharFormat.background() + && format.background().style() == Qt::SolidPattern) { + html += QLatin1String(" background-color:"); + html += format.background().color().name(); + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.verticalAlignment() != defaultCharFormat.verticalAlignment() + && format.verticalAlignment() != QTextCharFormat::AlignNormal) + { + html += QLatin1String(" vertical-align:"); + + QTextCharFormat::VerticalAlignment valign = format.verticalAlignment(); + if (valign == QTextCharFormat::AlignSubScript) + html += QLatin1String("sub"); + else if (valign == QTextCharFormat::AlignSuperScript) + html += QLatin1String("super"); + else if (valign == QTextCharFormat::AlignMiddle) + html += QLatin1String("middle"); + else if (valign == QTextCharFormat::AlignTop) + html += QLatin1String("top"); + else if (valign == QTextCharFormat::AlignBottom) + html += QLatin1String("bottom"); + + html += QLatin1Char(';'); + attributesEmitted = true; + } + + if (format.fontCapitalization() != QFont::MixedCase) { + const QFont::Capitalization caps = format.fontCapitalization(); + if (caps == QFont::AllUppercase) + html += QLatin1String(" text-transform:uppercase;"); + else if (caps == QFont::AllLowercase) + html += QLatin1String(" text-transform:lowercase;"); + else if (caps == QFont::SmallCaps) + html += QLatin1String(" font-variant:small-caps;"); + attributesEmitted = true; + } + + if (format.fontWordSpacing() != 0.0) { + html += QLatin1String(" word-spacing:"); + html += QString::number(format.fontWordSpacing()); + html += QLatin1String("px;"); + attributesEmitted = true; + } + + return attributesEmitted; +} + +void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length) +{ + if (length.type() == QTextLength::VariableLength) // default + return; + + html += QLatin1Char(' '); + html += QLatin1String(attribute); + html += QLatin1String("=\""); + html += QString::number(length.rawValue()); + + if (length.type() == QTextLength::PercentageLength) + html += QLatin1String("%\""); + else + html += QLatin1String("\""); +} + +void QTextHtmlExporter::emitAlignment(Qt::Alignment align) +{ + if (align & Qt::AlignLeft) + return; + else if (align & Qt::AlignRight) + html += QLatin1String(" align=\"right\""); + else if (align & Qt::AlignHCenter) + html += QLatin1String(" align=\"center\""); + else if (align & Qt::AlignJustify) + html += QLatin1String(" align=\"justify\""); +} + +void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode) +{ + if (pos == QTextFrameFormat::InFlow) + return; + + if (mode == EmitStyleTag) + html += QLatin1String(" style=\"float:"); + else + html += QLatin1String(" float:"); + + if (pos == QTextFrameFormat::FloatLeft) + html += QLatin1String(" left;"); + else if (pos == QTextFrameFormat::FloatRight) + html += QLatin1String(" right;"); + else + Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type"); + + if (mode == EmitStyleTag) + html += QLatin1Char('\"'); +} + +void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style) +{ + Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset); + + html += QLatin1String(" border-style:"); + + switch (style) { + case QTextFrameFormat::BorderStyle_None: + html += QLatin1String("none"); + break; + case QTextFrameFormat::BorderStyle_Dotted: + html += QLatin1String("dotted"); + break; + case QTextFrameFormat::BorderStyle_Dashed: + html += QLatin1String("dashed"); + break; + case QTextFrameFormat::BorderStyle_Solid: + html += QLatin1String("solid"); + break; + case QTextFrameFormat::BorderStyle_Double: + html += QLatin1String("double"); + break; + case QTextFrameFormat::BorderStyle_DotDash: + html += QLatin1String("dot-dash"); + break; + case QTextFrameFormat::BorderStyle_DotDotDash: + html += QLatin1String("dot-dot-dash"); + break; + case QTextFrameFormat::BorderStyle_Groove: + html += QLatin1String("groove"); + break; + case QTextFrameFormat::BorderStyle_Ridge: + html += QLatin1String("ridge"); + break; + case QTextFrameFormat::BorderStyle_Inset: + html += QLatin1String("inset"); + break; + case QTextFrameFormat::BorderStyle_Outset: + html += QLatin1String("outset"); + break; + default: + Q_ASSERT(false); + break; + }; + + html += QLatin1Char(';'); +} + +void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy) +{ + if (policy & QTextFormat::PageBreak_AlwaysBefore) + html += QLatin1String(" page-break-before:always;"); + + if (policy & QTextFormat::PageBreak_AlwaysAfter) + html += QLatin1String(" page-break-after:always;"); +} + +void QTextHtmlExporter::emitFontFamily(const QString &family) +{ + html += QLatin1String(" font-family:"); + + QLatin1Char quote('\''); + if (family.contains(quote)) + quote = QLatin1Char('\"'); + + html += quote; + html += family; + html += quote; + html += QLatin1Char(';'); +} + +void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right) +{ + html += QLatin1String(" margin-top:"); + html += top; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-bottom:"); + html += bottom; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-left:"); + html += left; + html += QLatin1String("px;"); + + html += QLatin1String(" margin-right:"); + html += right; + html += QLatin1String("px;"); +} + +void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) +{ + const QTextCharFormat format = fragment.charFormat(); + + bool closeAnchor = false; + + if (format.isAnchor()) { + const QString name = format.anchorName(); + if (!name.isEmpty()) { + html += QLatin1String("<a name=\""); + html += name; + html += QLatin1String("\"></a>"); + } + const QString href = format.anchorHref(); + if (!href.isEmpty()) { + html += QLatin1String("<a href=\""); + html += href; + html += QLatin1String("\">"); + closeAnchor = true; + } + } + + QString txt = fragment.text(); + const bool isObject = txt.contains(QChar::ObjectReplacementCharacter); + const bool isImage = isObject && format.isImageFormat(); + + QLatin1String styleTag("<span style=\""); + html += styleTag; + + bool attributesEmitted = false; + if (!isImage) + attributesEmitted = emitCharFormatStyle(format); + if (attributesEmitted) + html += QLatin1String("\">"); + else + html.chop(qstrlen(styleTag.latin1())); + + if (isObject) { + for (int i = 0; isImage && i < txt.length(); ++i) { + QTextImageFormat imgFmt = format.toImageFormat(); + + html += QLatin1String("<img"); + + if (imgFmt.hasProperty(QTextFormat::ImageName)) + emitAttribute("src", imgFmt.name()); + + if (imgFmt.hasProperty(QTextFormat::ImageWidth)) + emitAttribute("width", QString::number(imgFmt.width())); + + if (imgFmt.hasProperty(QTextFormat::ImageHeight)) + emitAttribute("height", QString::number(imgFmt.height())); + + if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle) + html += QLatin1String(" style=\"vertical-align: middle;\""); + else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop) + html += QLatin1String(" style=\"vertical-align: top;\""); + + if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt))) + emitFloatStyle(imageFrame->frameFormat().position()); + + html += QLatin1String(" />"); + } + } else { + Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter)); + + txt = Qt::escape(txt); + + // split for [\n{LineSeparator}] + QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]"); + forcedLineBreakRegExp[3] = QChar::LineSeparator; + + const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp)); + for (int i = 0; i < lines.count(); ++i) { + if (i > 0) + html += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co. + html += lines.at(i); + } + } + + if (attributesEmitted) + html += QLatin1String("</span>"); + + if (closeAnchor) + html += QLatin1String("</a>"); +} + +static bool isOrderedList(int style) +{ + return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha + || style == QTextListFormat::ListUpperAlpha; +} + +void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block) +{ + QTextBlockFormat format = block.blockFormat(); + emitAlignment(format.alignment()); + + Qt::LayoutDirection dir = format.layoutDirection(); + if (dir == Qt::LeftToRight) { + // assume default to not bloat the html too much + // html += QLatin1String(" dir='ltr'"); + } else { + html += QLatin1String(" dir='rtl'"); + } + + QLatin1String style(" style=\""); + html += style; + + const bool emptyBlock = block.begin().atEnd(); + if (emptyBlock) { + html += QLatin1String("-qt-paragraph-type:empty;"); + } + + emitMargins(QString::number(format.topMargin()), + QString::number(format.bottomMargin()), + QString::number(format.leftMargin()), + QString::number(format.rightMargin())); + + html += QLatin1String(" -qt-block-indent:"); + html += QString::number(format.indent()); + html += QLatin1Char(';'); + + html += QLatin1String(" text-indent:"); + html += QString::number(format.textIndent()); + html += QLatin1String("px;"); + + if (block.userState() != -1) { + html += QLatin1String(" -qt-user-state:"); + html += QString::number(block.userState()); + html += QLatin1Char(';'); + } + + emitPageBreakPolicy(format.pageBreakPolicy()); + + QTextCharFormat diff; + if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag + const QTextCharFormat blockCharFmt = block.charFormat(); + diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat(); + } + + diff.clearProperty(QTextFormat::BackgroundBrush); + if (format.hasProperty(QTextFormat::BackgroundBrush)) { + QBrush bg = format.background(); + if (bg.style() != Qt::NoBrush) + diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush)); + } + + if (!diff.properties().isEmpty()) + emitCharFormatStyle(diff); + + html += QLatin1Char('"'); + +} + +void QTextHtmlExporter::emitBlock(const QTextBlock &block) +{ + if (block.begin().atEnd()) { + // ### HACK, remove once QTextFrame::Iterator is fixed + int p = block.position(); + if (p > 0) + --p; + QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p); + QChar ch = doc->docHandle()->buffer().at(frag->stringPosition); + if (ch == QTextBeginningOfFrame + || ch == QTextEndOfFrame) + return; + } + + html += QLatin1Char('\n'); + + // save and later restore, in case we 'change' the default format by + // emitting block char format information + QTextCharFormat oldDefaultCharFormat = defaultCharFormat; + + QTextList *list = block.textList(); + if (list) { + if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate + const QTextListFormat format = list->format(); + const int style = format.style(); + switch (style) { + case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break; + case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break; + case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break; + case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break; + case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break; + case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break; + default: html += QLatin1String("<ul"); // ### should not happen + } + + if (format.hasProperty(QTextFormat::ListIndent)) { + html += QLatin1String(" style=\"-qt-list-indent: "); + html += QString::number(format.indent()); + html += QLatin1String(";\""); + } + + html += QLatin1Char('>'); + } + + html += QLatin1String("<li"); + + const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat(); + if (!blockFmt.properties().isEmpty()) { + html += QLatin1String(" style=\""); + emitCharFormatStyle(blockFmt); + html += QLatin1Char('\"'); + + defaultCharFormat.merge(block.charFormat()); + } + } + + const QTextBlockFormat blockFormat = block.blockFormat(); + if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { + html += QLatin1String("<hr"); + + QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth); + if (width.type() != QTextLength::VariableLength) + emitTextLength("width", width); + else + html += QLatin1Char(' '); + + html += QLatin1String("/>"); + return; + } + + const bool pre = blockFormat.nonBreakableLines(); + if (pre) { + if (list) + html += QLatin1Char('>'); + html += QLatin1String("<pre"); + } else if (!list) { + html += QLatin1String("<p"); + } + + emitBlockAttributes(block); + + html += QLatin1Char('>'); + + QTextBlock::Iterator it = block.begin(); + if (fragmentMarkers && !it.atEnd() && block == doc->begin()) + html += QLatin1String("<!--StartFragment-->"); + + for (; !it.atEnd(); ++it) + emitFragment(it.fragment()); + + if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length()) + html += QLatin1String("<!--EndFragment-->"); + + if (pre) + html += QLatin1String("</pre>"); + else if (list) + html += QLatin1String("</li>"); + else + html += QLatin1String("</p>"); + + if (list) { + if (list->itemNumber(block) == list->count() - 1) { // last item? close list + if (isOrderedList(list->format().style())) + html += QLatin1String("</ol>"); + else + html += QLatin1String("</ul>"); + } + } + + defaultCharFormat = oldDefaultCharFormat; +} + +extern bool qHasPixmapTexture(const QBrush& brush); + +QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap) +{ + QString url; + if (!doc) + return url; + + if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent())) + return findUrlForImage(parent, cacheKey, isPixmap); + + if (doc && doc->docHandle()) { + QTextDocumentPrivate *priv = doc->docHandle(); + QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin(); + for (; it != priv->cachedResources.constEnd(); ++it) { + + const QVariant &v = it.value(); + if (v.type() == QVariant::Image && !isPixmap) { + if (qvariant_cast<QImage>(v).cacheKey() == cacheKey) + break; + } + + if (v.type() == QVariant::Pixmap && isPixmap) { + if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey) + break; + } + } + + if (it != priv->cachedResources.constEnd()) + url = it.key().toString(); + } + + return url; +} + +void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv) +{ + if (!priv) + return; + + cachedResources.unite(priv->cachedResources); +} + +void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format) +{ + if (format.hasProperty(QTextFormat::BackgroundImageUrl)) { + QString url = format.property(QTextFormat::BackgroundImageUrl).toString(); + emitAttribute("background", url); + } else { + const QBrush &brush = format.background(); + if (brush.style() == Qt::SolidPattern) { + emitAttribute("bgcolor", brush.color().name()); + } else if (brush.style() == Qt::TexturePattern) { + const bool isPixmap = qHasPixmapTexture(brush); + const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey(); + + const QString url = findUrlForImage(doc, cacheKey, isPixmap); + + if (!url.isEmpty()) + emitAttribute("background", url); + } + } +} + +void QTextHtmlExporter::emitTable(const QTextTable *table) +{ + QTextTableFormat format = table->format(); + + html += QLatin1String("\n<table"); + + if (format.hasProperty(QTextFormat::FrameBorder)) + emitAttribute("border", QString::number(format.border())); + + emitFrameStyle(format, TableFrame); + + emitAlignment(format.alignment()); + emitTextLength("width", format.width()); + + if (format.hasProperty(QTextFormat::TableCellSpacing)) + emitAttribute("cellspacing", QString::number(format.cellSpacing())); + if (format.hasProperty(QTextFormat::TableCellPadding)) + emitAttribute("cellpadding", QString::number(format.cellPadding())); + + emitBackgroundAttribute(format); + + html += QLatin1Char('>'); + + const int rows = table->rows(); + const int columns = table->columns(); + + QVector<QTextLength> columnWidths = format.columnWidthConstraints(); + if (columnWidths.isEmpty()) { + columnWidths.resize(columns); + columnWidths.fill(QTextLength()); + } + Q_ASSERT(columnWidths.count() == columns); + + QVarLengthArray<bool> widthEmittedForColumn(columns); + for (int i = 0; i < columns; ++i) + widthEmittedForColumn[i] = false; + + const int headerRowCount = qMin(format.headerRowCount(), rows); + if (headerRowCount > 0) + html += QLatin1String("<thead>"); + + for (int row = 0; row < rows; ++row) { + html += QLatin1String("\n<tr>"); + + for (int col = 0; col < columns; ++col) { + const QTextTableCell cell = table->cellAt(row, col); + + // for col/rowspans + if (cell.row() != row) + continue; + + if (cell.column() != col) + continue; + + html += QLatin1String("\n<td"); + + if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) { + emitTextLength("width", columnWidths.at(col)); + widthEmittedForColumn[col] = true; + } + + if (cell.columnSpan() > 1) + emitAttribute("colspan", QString::number(cell.columnSpan())); + + if (cell.rowSpan() > 1) + emitAttribute("rowspan", QString::number(cell.rowSpan())); + + const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); + emitBackgroundAttribute(cellFormat); + + QTextCharFormat oldDefaultCharFormat = defaultCharFormat; + + QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment(); + + QString styleString; + if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) { + styleString += QLatin1String(" vertical-align:"); + switch (valign) { + case QTextCharFormat::AlignMiddle: + styleString += QLatin1String("middle"); + break; + case QTextCharFormat::AlignTop: + styleString += QLatin1String("top"); + break; + case QTextCharFormat::AlignBottom: + styleString += QLatin1String("bottom"); + break; + default: + break; + } + styleString += QLatin1Char(';'); + + QTextCharFormat temp; + temp.setVerticalAlignment(valign); + defaultCharFormat.merge(temp); + } + + if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding)) + styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding)) + styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding)) + styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';'); + if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding)) + styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';'); + + if (!styleString.isEmpty()) + html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"'); + + html += QLatin1Char('>'); + + emitFrame(cell.begin()); + + html += QLatin1String("</td>"); + + defaultCharFormat = oldDefaultCharFormat; + } + + html += QLatin1String("</tr>"); + if (headerRowCount > 0 && row == headerRowCount - 1) + html += QLatin1String("</thead>"); + } + + html += QLatin1String("</table>"); +} + +void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt) +{ + if (!frameIt.atEnd()) { + QTextFrame::Iterator next = frameIt; + ++next; + if (next.atEnd() + && frameIt.currentFrame() == 0 + && frameIt.parentFrame() != doc->rootFrame() + && frameIt.currentBlock().begin().atEnd()) + return; + } + + for (QTextFrame::Iterator it = frameIt; + !it.atEnd(); ++it) { + if (QTextFrame *f = it.currentFrame()) { + if (QTextTable *table = qobject_cast<QTextTable *>(f)) { + emitTable(table); + } else { + emitTextFrame(f); + } + } else if (it.currentBlock().isValid()) { + emitBlock(it.currentBlock()); + } + } +} + +void QTextHtmlExporter::emitTextFrame(const QTextFrame *f) +{ + FrameType frameType = f->parentFrame() ? TextFrame : RootFrame; + + html += QLatin1String("\n<table"); + QTextFrameFormat format = f->frameFormat(); + + if (format.hasProperty(QTextFormat::FrameBorder)) + emitAttribute("border", QString::number(format.border())); + + emitFrameStyle(format, frameType); + + emitTextLength("width", format.width()); + emitTextLength("height", format.height()); + + // root frame's bcolor goes in the <body> tag + if (frameType != RootFrame) + emitBackgroundAttribute(format); + + html += QLatin1Char('>'); + html += QLatin1String("\n<tr>\n<td style=\"border: none;\">"); + emitFrame(f->begin()); + html += QLatin1String("</td></tr></table>"); +} + +void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType) +{ + QLatin1String styleAttribute(" style=\""); + html += styleAttribute; + const int originalHtmlLength = html.length(); + + if (frameType == TextFrame) + html += QLatin1String("-qt-table-type: frame;"); + else if (frameType == RootFrame) + html += QLatin1String("-qt-table-type: root;"); + + const QTextFrameFormat defaultFormat; + + emitFloatStyle(format.position(), OmitStyleTag); + emitPageBreakPolicy(format.pageBreakPolicy()); + + if (format.borderBrush() != defaultFormat.borderBrush()) { + html += QLatin1String(" border-color:"); + html += format.borderBrush().color().name(); + html += QLatin1Char(';'); + } + + if (format.borderStyle() != defaultFormat.borderStyle()) + emitBorderStyle(format.borderStyle()); + + if (format.hasProperty(QTextFormat::FrameMargin) + || format.hasProperty(QTextFormat::FrameLeftMargin) + || format.hasProperty(QTextFormat::FrameRightMargin) + || format.hasProperty(QTextFormat::FrameTopMargin) + || format.hasProperty(QTextFormat::FrameBottomMargin)) + emitMargins(QString::number(format.topMargin()), + QString::number(format.bottomMargin()), + QString::number(format.leftMargin()), + QString::number(format.rightMargin())); + + if (html.length() == originalHtmlLength) // nothing emitted? + html.chop(qstrlen(styleAttribute.latin1())); + else + html += QLatin1Char('\"'); +} + +/*! + Returns a string containing an HTML representation of the document. + + The \a encoding parameter specifies the value for the charset attribute + in the html header. For example if 'utf-8' is specified then the + beginning of the generated html will look like this: + \snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 1 + + If no encoding is specified then no such meta information is generated. + + If you later on convert the returned html string into a byte array for + transmission over a network or when saving to disk you should specify + the encoding you're going to use for the conversion to a byte array here. + + \sa {Supported HTML Subset} +*/ +#ifndef QT_NO_TEXTHTMLPARSER +QString QTextDocument::toHtml(const QByteArray &encoding) const +{ + return QTextHtmlExporter(this).toHtml(encoding); +} +#endif // QT_NO_TEXTHTMLPARSER + +/*! + Returns a vector of text formats for all the formats used in the document. +*/ +QVector<QTextFormat> QTextDocument::allFormats() const +{ + Q_D(const QTextDocument); + return d->formatCollection()->formats; +} + + +/*! + \internal + + So that not all classes have to be friends of each other... +*/ +QTextDocumentPrivate *QTextDocument::docHandle() const +{ + Q_D(const QTextDocument); + return const_cast<QTextDocumentPrivate *>(d); +} + +/*! + \since 4.4 + \fn QTextDocument::undoCommandAdded() + + This signal is emitted every time a new level of undo is added to the QTextDocument. +*/ + +QT_END_NAMESPACE |