| 1 |
/**************************************************************************** |
| 2 |
** |
| 3 |
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). |
| 4 |
** All rights reserved. |
| 5 |
** Contact: Nokia Corporation (qt-info@nokia.com) |
| 6 |
** |
| 7 |
** This file is part of the QtGui module of the Qt Toolkit. |
| 8 |
** |
| 9 |
** $QT_BEGIN_LICENSE:LGPL$ |
| 10 |
** GNU Lesser General Public License Usage |
| 11 |
** This file may be used under the terms of the GNU Lesser General Public |
| 12 |
** License version 2.1 as published by the Free Software Foundation and |
| 13 |
** appearing in the file LICENSE.LGPL included in the packaging of this |
| 14 |
** file. Please review the following information to ensure the GNU Lesser |
| 15 |
** General Public License version 2.1 requirements will be met: |
| 16 |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
| 17 |
** |
| 18 |
** In addition, as a special exception, Nokia gives you certain additional |
| 19 |
** rights. These rights are described in the Nokia Qt LGPL Exception |
| 20 |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
| 21 |
** |
| 22 |
** GNU General Public License Usage |
| 23 |
** Alternatively, this file may be used under the terms of the GNU General |
| 24 |
** Public License version 3.0 as published by the Free Software Foundation |
| 25 |
** and appearing in the file LICENSE.GPL included in the packaging of this |
| 26 |
** file. Please review the following information to ensure the GNU General |
| 27 |
** Public License version 3.0 requirements will be met: |
| 28 |
** http://www.gnu.org/copyleft/gpl.html. |
| 29 |
** |
| 30 |
** Other Usage |
| 31 |
** Alternatively, this file may be used in accordance with the terms and |
| 32 |
** conditions contained in a signed written agreement between you and Nokia. |
| 33 |
** |
| 34 |
** |
| 35 |
** |
| 36 |
** |
| 37 |
** |
| 38 |
** $QT_END_LICENSE$ |
| 39 |
** |
| 40 |
****************************************************************************/ |
| 41 |
|
| 42 |
#include "qtextdocument.h" |
| 43 |
#include <qtextformat.h> |
| 44 |
#include "qtextdocumentlayout_p.h" |
| 45 |
#include "qtextdocumentfragment.h" |
| 46 |
#include "qtextdocumentfragment_p.h" |
| 47 |
#include "qtexttable.h" |
| 48 |
#include "qtextlist.h" |
| 49 |
#include <qdebug.h> |
| 50 |
#include <qregexp.h> |
| 51 |
#include <qvarlengtharray.h> |
| 52 |
#include <qtextcodec.h> |
| 53 |
#include <qthread.h> |
| 54 |
#include "qtexthtmlparser_p.h" |
| 55 |
#include "qpainter.h" |
| 56 |
#include "qprinter.h" |
| 57 |
#include "qtextedit.h" |
| 58 |
#include <qfile.h> |
| 59 |
#include <qfileinfo.h> |
| 60 |
#include <qdir.h> |
| 61 |
#include <qapplication.h> |
| 62 |
#include "qtextcontrol_p.h" |
| 63 |
#include "qfont_p.h" |
| 64 |
#include "private/qtextedit_p.h" |
| 65 |
#include "private/qdataurl_p.h" |
| 66 |
|
| 67 |
#include "qtextdocument_p.h" |
| 68 |
#include <private/qprinter_p.h> |
| 69 |
#include <private/qabstracttextdocumentlayout_p.h> |
| 70 |
|
| 71 |
#include <limits.h> |
| 72 |
|
| 73 |
QT_BEGIN_NAMESPACE |
| 74 |
|
| 75 |
Q_CORE_EXPORT unsigned int qt_int_sqrt(unsigned int n); |
| 76 |
|
| 77 |
/*! |
| 78 |
Returns true if the string \a text is likely to be rich text; |
| 79 |
otherwise returns false. |
| 80 |
|
| 81 |
This function uses a fast and therefore simple heuristic. It |
| 82 |
mainly checks whether there is something that looks like a tag |
| 83 |
before the first line break. Although the result may be correct |
| 84 |
for common cases, there is no guarantee. |
| 85 |
|
| 86 |
This function is defined in the \c <QTextDocument> header file. |
| 87 |
*/ |
| 88 |
bool Qt::mightBeRichText(const QString& text) |
| 89 |
{ |
| 90 |
if (text.isEmpty()) |
| 91 |
return false; |
| 92 |
int start = 0; |
| 93 |
|
| 94 |
while (start < text.length() && text.at(start).isSpace()) |
| 95 |
++start; |
| 96 |
|
| 97 |
// skip a leading <?xml ... ?> as for example with xhtml |
| 98 |
if (text.mid(start, 5) == QLatin1String("<?xml")) { |
| 99 |
while (start < text.length()) { |
| 100 |
if (text.at(start) == QLatin1Char('?') |
| 101 |
&& start + 2 < text.length() |
| 102 |
&& text.at(start + 1) == QLatin1Char('>')) { |
| 103 |
start += 2; |
| 104 |
break; |
| 105 |
} |
| 106 |
++start; |
| 107 |
} |
| 108 |
|
| 109 |
while (start < text.length() && text.at(start).isSpace()) |
| 110 |
++start; |
| 111 |
} |
| 112 |
|
| 113 |
if (text.mid(start, 5).toLower() == QLatin1String("<!doc")) |
| 114 |
return true; |
| 115 |
int open = start; |
| 116 |
while (open < text.length() && text.at(open) != QLatin1Char('<') |
| 117 |
&& text.at(open) != QLatin1Char('\n')) { |
| 118 |
if (text.at(open) == QLatin1Char('&') && text.mid(open+1,3) == QLatin1String("lt;")) |
| 119 |
return true; // support desperate attempt of user to see <...> |
| 120 |
++open; |
| 121 |
} |
| 122 |
if (open < text.length() && text.at(open) == QLatin1Char('<')) { |
| 123 |
const int close = text.indexOf(QLatin1Char('>'), open); |
| 124 |
if (close > -1) { |
| 125 |
QString tag; |
| 126 |
for (int i = open+1; i < close; ++i) { |
| 127 |
if (text[i].isDigit() || text[i].isLetter()) |
| 128 |
tag += text[i]; |
| 129 |
else if (!tag.isEmpty() && text[i].isSpace()) |
| 130 |
break; |
| 131 |
else if (!tag.isEmpty() && text[i] == QLatin1Char('/') && i + 1 == close) |
| 132 |
break; |
| 133 |
else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != QLatin1Char('!'))) |
| 134 |
return false; // that's not a tag |
| 135 |
} |
| 136 |
#ifndef QT_NO_TEXTHTMLPARSER |
| 137 |
return QTextHtmlParser::lookupElement(tag.toLower()) != -1; |
| 138 |
#else |
| 139 |
return false; |
| 140 |
#endif // QT_NO_TEXTHTMLPARSER |
| 141 |
} |
| 142 |
} |
| 143 |
return false; |
| 144 |
} |
| 145 |
|
| 146 |
/*! |
| 147 |
Converts the plain text string \a plain to a HTML string with |
| 148 |
HTML metacharacters \c{<}, \c{>}, \c{&}, and \c{"} replaced by HTML |
| 149 |
entities. |
| 150 |
|
| 151 |
Example: |
| 152 |
|
| 153 |
\snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 0 |
| 154 |
|
| 155 |
This function is defined in the \c <QTextDocument> header file. |
| 156 |
|
| 157 |
\sa convertFromPlainText(), mightBeRichText() |
| 158 |
*/ |
| 159 |
QString Qt::escape(const QString& plain) |
| 160 |
{ |
| 161 |
QString rich; |
| 162 |
rich.reserve(int(plain.length() * qreal(1.1))); |
| 163 |
for (int i = 0; i < plain.length(); ++i) { |
| 164 |
if (plain.at(i) == QLatin1Char('<')) |
| 165 |
rich += QLatin1String("<"); |
| 166 |
else if (plain.at(i) == QLatin1Char('>')) |
| 167 |
rich += QLatin1String(">"); |
| 168 |
else if (plain.at(i) == QLatin1Char('&')) |
| 169 |
rich += QLatin1String("&"); |
| 170 |
else if (plain.at(i) == QLatin1Char('"')) |
| 171 |
rich += QLatin1String("""); |
| 172 |
else |
| 173 |
rich += plain.at(i); |
| 174 |
} |
| 175 |
return rich; |
| 176 |
} |
| 177 |
|
| 178 |
/*! |
| 179 |
\fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode) |
| 180 |
|
| 181 |
Converts the plain text string \a plain to an HTML-formatted |
| 182 |
paragraph while preserving most of its look. |
| 183 |
|
| 184 |
\a mode defines how whitespace is handled. |
| 185 |
|
| 186 |
This function is defined in the \c <QTextDocument> header file. |
| 187 |
|
| 188 |
\sa escape(), mightBeRichText() |
| 189 |
*/ |
| 190 |
QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode) |
| 191 |
{ |
| 192 |
int col = 0; |
| 193 |
QString rich; |
| 194 |
rich += QLatin1String("<p>"); |
| 195 |
for (int i = 0; i < plain.length(); ++i) { |
| 196 |
if (plain[i] == QLatin1Char('\n')){ |
| 197 |
int c = 1; |
| 198 |
while (i+1 < plain.length() && plain[i+1] == QLatin1Char('\n')) { |
| 199 |
i++; |
| 200 |
c++; |
| 201 |
} |
| 202 |
if (c == 1) |
| 203 |
rich += QLatin1String("<br>\n"); |
| 204 |
else { |
| 205 |
rich += QLatin1String("</p>\n"); |
| 206 |
while (--c > 1) |
| 207 |
rich += QLatin1String("<br>\n"); |
| 208 |
rich += QLatin1String("<p>"); |
| 209 |
} |
| 210 |
col = 0; |
| 211 |
} else { |
| 212 |
if (mode == Qt::WhiteSpacePre && plain[i] == QLatin1Char('\t')){ |
| 213 |
rich += QChar(0x00a0U); |
| 214 |
++col; |
| 215 |
while (col % 8) { |
| 216 |
rich += QChar(0x00a0U); |
| 217 |
++col; |
| 218 |
} |
| 219 |
} |
| 220 |
else if (mode == Qt::WhiteSpacePre && plain[i].isSpace()) |
| 221 |
rich += QChar(0x00a0U); |
| 222 |
else if (plain[i] == QLatin1Char('<')) |
| 223 |
rich += QLatin1String("<"); |
| 224 |
else if (plain[i] == QLatin1Char('>')) |
| 225 |
rich += QLatin1String(">"); |
| 226 |
else if (plain[i] == QLatin1Char('&')) |
| 227 |
rich += QLatin1String("&"); |
| 228 |
else |
| 229 |
rich += plain[i]; |
| 230 |
++col; |
| 231 |
} |
| 232 |
} |
| 233 |
if (col != 0) |
| 234 |
rich += QLatin1String("</p>"); |
| 235 |
return rich; |
| 236 |
} |
| 237 |
|
| 238 |
#ifndef QT_NO_TEXTCODEC |
| 239 |
/*! |
| 240 |
\internal |
| 241 |
|
| 242 |
This function is defined in the \c <QTextDocument> header file. |
| 243 |
*/ |
| 244 |
QTextCodec *Qt::codecForHtml(const QByteArray &ba) |
| 245 |
{ |
| 246 |
return QTextCodec::codecForHtml(ba); |
| 247 |
} |
| 248 |
#endif |
| 249 |
|
| 250 |
/*! |
| 251 |
\class QTextDocument |
| 252 |
\reentrant |
| 253 |
|
| 254 |
\brief The QTextDocument class holds formatted text that can be |
| 255 |
viewed and edited using a QTextEdit. |
| 256 |
|
| 257 |
\ingroup richtext-processing |
| 258 |
|
| 259 |
|
| 260 |
QTextDocument is a container for structured rich text documents, providing |
| 261 |
support for styled text and various types of document elements, such as |
| 262 |
lists, tables, frames, and images. |
| 263 |
They can be created for use in a QTextEdit, or used independently. |
| 264 |
|
| 265 |
Each document element is described by an associated format object. Each |
| 266 |
format object is treated as a unique object by QTextDocuments, and can be |
| 267 |
passed to objectForFormat() to obtain the document element that it is |
| 268 |
applied to. |
| 269 |
|
| 270 |
A QTextDocument can be edited programmatically using a QTextCursor, and |
| 271 |
its contents can be examined by traversing the document structure. The |
| 272 |
entire document structure is stored as a hierarchy of document elements |
| 273 |
beneath the root frame, found with the rootFrame() function. Alternatively, |
| 274 |
if you just want to iterate over the textual contents of the document you |
| 275 |
can use begin(), end(), and findBlock() to retrieve text blocks that you |
| 276 |
can examine and iterate over. |
| 277 |
|
| 278 |
The layout of a document is determined by the documentLayout(); |
| 279 |
you can create your own QAbstractTextDocumentLayout subclass and |
| 280 |
set it using setDocumentLayout() if you want to use your own |
| 281 |
layout logic. The document's title and other meta-information can be |
| 282 |
obtained by calling the metaInformation() function. For documents that |
| 283 |
are exposed to users through the QTextEdit class, the document title |
| 284 |
is also available via the QTextEdit::documentTitle() function. |
| 285 |
|
| 286 |
The toPlainText() and toHtml() convenience functions allow you to retrieve the |
| 287 |
contents of the document as plain text and HTML. |
| 288 |
The document's text can be searched using the find() functions. |
| 289 |
|
| 290 |
Undo/redo of operations performed on the document can be controlled using |
| 291 |
the setUndoRedoEnabled() function. The undo/redo system can be controlled |
| 292 |
by an editor widget through the undo() and redo() slots; the document also |
| 293 |
provides contentsChanged(), undoAvailable(), and redoAvailable() signals |
| 294 |
that inform connected editor widgets about the state of the undo/redo |
| 295 |
system. The following are the undo/redo operations of a QTextDocument: |
| 296 |
|
| 297 |
\list |
| 298 |
\o Insertion or removal of characters. A sequence of insertions or removals |
| 299 |
within the same text block are regarded as a single undo/redo operation. |
| 300 |
\o Insertion or removal of text blocks. Sequences of insertion or removals |
| 301 |
in a single operation (e.g., by selecting and then deleting text) are |
| 302 |
regarded as a single undo/redo operation. |
| 303 |
\o Text character format changes. |
| 304 |
\o Text block format changes. |
| 305 |
\o Text block group format changes. |
| 306 |
\endlist |
| 307 |
|
| 308 |
\sa QTextCursor, QTextEdit, \link richtext.html Rich Text Processing\endlink , {Text Object Example} |
| 309 |
*/ |
| 310 |
|
| 311 |
/*! |
| 312 |
\property QTextDocument::defaultFont |
| 313 |
\brief the default font used to display the document's text |
| 314 |
*/ |
| 315 |
|
| 316 |
/*! |
| 317 |
\property QTextDocument::defaultTextOption |
| 318 |
\brief the default text option will be set on all \l{QTextLayout}s in the document. |
| 319 |
|
| 320 |
When \l{QTextBlock}s are created, the defaultTextOption is set on their |
| 321 |
QTextLayout. This allows setting global properties for the document such as the |
| 322 |
default word wrap mode. |
| 323 |
*/ |
| 324 |
|
| 325 |
/*! |
| 326 |
Constructs an empty QTextDocument with the given \a parent. |
| 327 |
*/ |
| 328 |
QTextDocument::QTextDocument(QObject *parent) |
| 329 |
: QObject(*new QTextDocumentPrivate, parent) |
| 330 |
{ |
| 331 |
Q_D(QTextDocument); |
| 332 |
d->init(); |
| 333 |
} |
| 334 |
|
| 335 |
/*! |
| 336 |
Constructs a QTextDocument containing the plain (unformatted) \a text |
| 337 |
specified, and with the given \a parent. |
| 338 |
*/ |
| 339 |
QTextDocument::QTextDocument(const QString &text, QObject *parent) |
| 340 |
: QObject(*new QTextDocumentPrivate, parent) |
| 341 |
{ |
| 342 |
Q_D(QTextDocument); |
| 343 |
d->init(); |
| 344 |
QTextCursor(this).insertText(text); |
| 345 |
} |
| 346 |
|
| 347 |
/*! |
| 348 |
\internal |
| 349 |
*/ |
| 350 |
QTextDocument::QTextDocument(QTextDocumentPrivate &dd, QObject *parent) |
| 351 |
: QObject(dd, parent) |
| 352 |
{ |
| 353 |
Q_D(QTextDocument); |
| 354 |
d->init(); |
| 355 |
} |
| 356 |
|
| 357 |
/*! |
| 358 |
Destroys the document. |
| 359 |
*/ |
| 360 |
QTextDocument::~QTextDocument() |
| 361 |
{ |
| 362 |
} |
| 363 |
|
| 364 |
|
| 365 |
/*! |
| 366 |
Creates a new QTextDocument that is a copy of this text document. \a |
| 367 |
parent is the parent of the returned text document. |
| 368 |
*/ |
| 369 |
QTextDocument *QTextDocument::clone(QObject *parent) const |
| 370 |
{ |
| 371 |
Q_D(const QTextDocument); |
| 372 |
QTextDocument *doc = new QTextDocument(parent); |
| 373 |
QTextCursor(doc).insertFragment(QTextDocumentFragment(this)); |
| 374 |
doc->rootFrame()->setFrameFormat(rootFrame()->frameFormat()); |
| 375 |
QTextDocumentPrivate *priv = doc->d_func(); |
| 376 |
priv->title = d->title; |
| 377 |
priv->url = d->url; |
| 378 |
priv->pageSize = d->pageSize; |
| 379 |
priv->indentWidth = d->indentWidth; |
| 380 |
priv->defaultTextOption = d->defaultTextOption; |
| 381 |
priv->setDefaultFont(d->defaultFont()); |
| 382 |
priv->resources = d->resources; |
| 383 |
priv->cachedResources.clear(); |
| 384 |
#ifndef QT_NO_CSSPARSER |
| 385 |
priv->defaultStyleSheet = d->defaultStyleSheet; |
| 386 |
priv->parsedDefaultStyleSheet = d->parsedDefaultStyleSheet; |
| 387 |
#endif |
| 388 |
return doc; |
| 389 |
} |
| 390 |
|
| 391 |
/*! |
| 392 |
Returns true if the document is empty; otherwise returns false. |
| 393 |
*/ |
| 394 |
bool QTextDocument::isEmpty() const |
| 395 |
{ |
| 396 |
Q_D(const QTextDocument); |
| 397 |
/* because if we're empty we still have one single paragraph as |
| 398 |
* one single fragment */ |
| 399 |
return d->length() <= 1; |
| 400 |
} |
| 401 |
|
| 402 |
/*! |
| 403 |
Clears the document. |
| 404 |
*/ |
| 405 |
void QTextDocument::clear() |
| 406 |
{ |
| 407 |
Q_D(QTextDocument); |
| 408 |
d->clear(); |
| 409 |
d->resources.clear(); |
| 410 |
} |
| 411 |
|
| 412 |
/*! |
| 413 |
\since 4.2 |
| 414 |
|
| 415 |
Undoes the last editing operation on the document if undo is |
| 416 |
available. The provided \a cursor is positioned at the end of the |
| 417 |
location where the edition operation was undone. |
| 418 |
|
| 419 |
See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework} |
| 420 |
documentation for details. |
| 421 |
|
| 422 |
\sa undoAvailable(), isUndoRedoEnabled() |
| 423 |
*/ |
| 424 |
void QTextDocument::undo(QTextCursor *cursor) |
| 425 |
{ |
| 426 |
Q_D(QTextDocument); |
| 427 |
const int pos = d->undoRedo(true); |
| 428 |
if (cursor && pos >= 0) { |
| 429 |
*cursor = QTextCursor(this); |
| 430 |
cursor->setPosition(pos); |
| 431 |
} |
| 432 |
} |
| 433 |
|
| 434 |
/*! |
| 435 |
\since 4.2 |
| 436 |
Redoes the last editing operation on the document if \link |
| 437 |
QTextDocument::isRedoAvailable() redo is available\endlink. |
| 438 |
|
| 439 |
The provided \a cursor is positioned at the end of the location where |
| 440 |
the edition operation was redone. |
| 441 |
*/ |
| 442 |
void QTextDocument::redo(QTextCursor *cursor) |
| 443 |
{ |
| 444 |
Q_D(QTextDocument); |
| 445 |
const int pos = d->undoRedo(false); |
| 446 |
if (cursor && pos >= 0) { |
| 447 |
*cursor = QTextCursor(this); |
| 448 |
cursor->setPosition(pos); |
| 449 |
} |
| 450 |
} |
| 451 |
|
| 452 |
/*! \enum QTextDocument::Stacks |
| 453 |
|
| 454 |
\value UndoStack The undo stack. |
| 455 |
\value RedoStack The redo stack. |
| 456 |
\value UndoAndRedoStacks Both the undo and redo stacks. |
| 457 |
*/ |
| 458 |
|
| 459 |
/*! |
| 460 |
\since 4.7 |
| 461 |
Clears the stacks specified by \a stacksToClear. |
| 462 |
|
| 463 |
This method clears any commands on the undo stack, the redo stack, |
| 464 |
or both (the default). If commands are cleared, the appropriate |
| 465 |
signals are emitted, QTextDocument::undoAvailable() or |
| 466 |
QTextDocument::redoAvailable(). |
| 467 |
|
| 468 |
\sa QTextDocument::undoAvailable() QTextDocument::redoAvailable() |
| 469 |
*/ |
| 470 |
void QTextDocument::clearUndoRedoStacks(Stacks stacksToClear) |
| 471 |
{ |
| 472 |
Q_D(QTextDocument); |
| 473 |
d->clearUndoRedoStacks(stacksToClear, true); |
| 474 |
} |
| 475 |
|
| 476 |
/*! |
| 477 |
\overload |
| 478 |
|
| 479 |
*/ |
| 480 |
void QTextDocument::undo() |
| 481 |
{ |
| 482 |
Q_D(QTextDocument); |
| 483 |
d->undoRedo(true); |
| 484 |
} |
| 485 |
|
| 486 |
/*! |
| 487 |
\overload |
| 488 |
Redoes the last editing operation on the document if \link |
| 489 |
QTextDocument::isRedoAvailable() redo is available\endlink. |
| 490 |
*/ |
| 491 |
void QTextDocument::redo() |
| 492 |
{ |
| 493 |
Q_D(QTextDocument); |
| 494 |
d->undoRedo(false); |
| 495 |
} |
| 496 |
|
| 497 |
/*! |
| 498 |
\internal |
| 499 |
|
| 500 |
Appends a custom undo \a item to the undo stack. |
| 501 |
*/ |
| 502 |
void QTextDocument::appendUndoItem(QAbstractUndoItem *item) |
| 503 |
{ |
| 504 |
Q_D(QTextDocument); |
| 505 |
d->appendUndoItem(item); |
| 506 |
} |
| 507 |
|
| 508 |
/*! |
| 509 |
\property QTextDocument::undoRedoEnabled |
| 510 |
\brief whether undo/redo are enabled for this document |
| 511 |
|
| 512 |
This defaults to true. If disabled, the undo stack is cleared and |
| 513 |
no items will be added to it. |
| 514 |
*/ |
| 515 |
void QTextDocument::setUndoRedoEnabled(bool enable) |
| 516 |
{ |
| 517 |
Q_D(QTextDocument); |
| 518 |
d->enableUndoRedo(enable); |
| 519 |
} |
| 520 |
|
| 521 |
bool QTextDocument::isUndoRedoEnabled() const |
| 522 |
{ |
| 523 |
Q_D(const QTextDocument); |
| 524 |
return d->isUndoRedoEnabled(); |
| 525 |
} |
| 526 |
|
| 527 |
/*! |
| 528 |
\property QTextDocument::maximumBlockCount |
| 529 |
\since 4.2 |
| 530 |
\brief Specifies the limit for blocks in the document. |
| 531 |
|
| 532 |
Specifies the maximum number of blocks the document may have. If there are |
| 533 |
more blocks in the document that specified with this property blocks are removed |
| 534 |
from the beginning of the document. |
| 535 |
|
| 536 |
A negative or zero value specifies that the document may contain an unlimited |
| 537 |
amount of blocks. |
| 538 |
|
| 539 |
The default value is 0. |
| 540 |
|
| 541 |
Note that setting this property will apply the limit immediately to the document |
| 542 |
contents. |
| 543 |
|
| 544 |
Setting this property also disables the undo redo history. |
| 545 |
|
| 546 |
This property is undefined in documents with tables or frames. |
| 547 |
*/ |
| 548 |
int QTextDocument::maximumBlockCount() const |
| 549 |
{ |
| 550 |
Q_D(const QTextDocument); |
| 551 |
return d->maximumBlockCount; |
| 552 |
} |
| 553 |
|
| 554 |
void QTextDocument::setMaximumBlockCount(int maximum) |
| 555 |
{ |
| 556 |
Q_D(QTextDocument); |
| 557 |
d->maximumBlockCount = maximum; |
| 558 |
d->ensureMaximumBlockCount(); |
| 559 |
setUndoRedoEnabled(false); |
| 560 |
} |
| 561 |
|
| 562 |
/*! |
| 563 |
\since 4.3 |
| 564 |
|
| 565 |
The default text option is used on all QTextLayout objects in the document. |
| 566 |
This allows setting global properties for the document such as the default |
| 567 |
word wrap mode. |
| 568 |
*/ |
| 569 |
QTextOption QTextDocument::defaultTextOption() const |
| 570 |
{ |
| 571 |
Q_D(const QTextDocument); |
| 572 |
return d->defaultTextOption; |
| 573 |
} |
| 574 |
|
| 575 |
/*! |
| 576 |
\since 4.3 |
| 577 |
|
| 578 |
Sets the default text option. |
| 579 |
*/ |
| 580 |
void QTextDocument::setDefaultTextOption(const QTextOption &option) |
| 581 |
{ |
| 582 |
Q_D(QTextDocument); |
| 583 |
d->defaultTextOption = option; |
| 584 |
if (d->lout) |
| 585 |
d->lout->documentChanged(0, 0, d->length()); |
| 586 |
} |
| 587 |
|
| 588 |
/*! |
| 589 |
\since 4.8 |
| 590 |
|
| 591 |
The default cursor movement style is used by all QTextCursor objects |
| 592 |
created from the document. The default is Qt::LogicalMoveStyle. |
| 593 |
*/ |
| 594 |
Qt::CursorMoveStyle QTextDocument::defaultCursorMoveStyle() const |
| 595 |
{ |
| 596 |
Q_D(const QTextDocument); |
| 597 |
return d->defaultCursorMoveStyle; |
| 598 |
} |
| 599 |
|
| 600 |
/*! |
| 601 |
\since 4.8 |
| 602 |
|
| 603 |
Sets the default cursor movement style to the given \a style. |
| 604 |
*/ |
| 605 |
void QTextDocument::setDefaultCursorMoveStyle(Qt::CursorMoveStyle style) |
| 606 |
{ |
| 607 |
Q_D(QTextDocument); |
| 608 |
d->defaultCursorMoveStyle = style; |
| 609 |
} |
| 610 |
|
| 611 |
/*! |
| 612 |
\fn void QTextDocument::markContentsDirty(int position, int length) |
| 613 |
|
| 614 |
Marks the contents specified by the given \a position and \a length |
| 615 |
as "dirty", informing the document that it needs to be laid out |
| 616 |
again. |
| 617 |
*/ |
| 618 |
void QTextDocument::markContentsDirty(int from, int length) |
| 619 |
{ |
| 620 |
Q_D(QTextDocument); |
| 621 |
d->documentChange(from, length); |
| 622 |
if (!d->inContentsChange) { |
| 623 |
if (d->lout) { |
| 624 |
d->lout->documentChanged(d->docChangeFrom, d->docChangeOldLength, d->docChangeLength); |
| 625 |
d->docChangeFrom = -1; |
| 626 |
} |
| 627 |
} |
| 628 |
} |
| 629 |
|
| 630 |
/*! |
| 631 |
\property QTextDocument::useDesignMetrics |
| 632 |
\since 4.1 |
| 633 |
\brief whether the document uses design metrics of fonts to improve the accuracy of text layout |
| 634 |
|
| 635 |
If this property is set to true, the layout will use design metrics. |
| 636 |
Otherwise, the metrics of the paint device as set on |
| 637 |
QAbstractTextDocumentLayout::setPaintDevice() will be used. |
| 638 |
|
| 639 |
Using design metrics makes a layout have a width that is no longer dependent on hinting |
| 640 |
and pixel-rounding. This means that WYSIWYG text layout becomes possible because the width |
| 641 |
scales much more linearly based on paintdevice metrics than it would otherwise. |
| 642 |
|
| 643 |
By default, this property is false. |
| 644 |
*/ |
| 645 |
|
| 646 |
void QTextDocument::setUseDesignMetrics(bool b) |
| 647 |
{ |
| 648 |
Q_D(QTextDocument); |
| 649 |
if (b == d->defaultTextOption.useDesignMetrics()) |
| 650 |
return; |
| 651 |
d->defaultTextOption.setUseDesignMetrics(b); |
| 652 |
if (d->lout) |
| 653 |
d->lout->documentChanged(0, 0, d->length()); |
| 654 |
} |
| 655 |
|
| 656 |
bool QTextDocument::useDesignMetrics() const |
| 657 |
{ |
| 658 |
Q_D(const QTextDocument); |
| 659 |
return d->defaultTextOption.useDesignMetrics(); |
| 660 |
} |
| 661 |
|
| 662 |
/*! |
| 663 |
\since 4.2 |
| 664 |
|
| 665 |
Draws the content of the document with painter \a p, clipped to \a rect. |
| 666 |
If \a rect is a null rectangle (default) then the document is painted unclipped. |
| 667 |
*/ |
| 668 |
void QTextDocument::drawContents(QPainter *p, const QRectF &rect) |
| 669 |
{ |
| 670 |
p->save(); |
| 671 |
QAbstractTextDocumentLayout::PaintContext ctx; |
| 672 |
if (rect.isValid()) { |
| 673 |
p->setClipRect(rect); |
| 674 |
ctx.clip = rect; |
| 675 |
} |
| 676 |
documentLayout()->draw(p, ctx); |
| 677 |
p->restore(); |
| 678 |
} |
| 679 |
|
| 680 |
/*! |
| 681 |
\property QTextDocument::textWidth |
| 682 |
\since 4.2 |
| 683 |
|
| 684 |
The text width specifies the preferred width for text in the document. If |
| 685 |
the text (or content in general) is wider than the specified with it is broken |
| 686 |
into multiple lines and grows vertically. If the text cannot be broken into multiple |
| 687 |
lines to fit into the specified text width it will be larger and the size() and the |
| 688 |
idealWidth() property will reflect that. |
| 689 |
|
| 690 |
If the text width is set to -1 then the text will not be broken into multiple lines |
| 691 |
unless it is enforced through an explicit line break or a new paragraph. |
| 692 |
|
| 693 |
The default value is -1. |
| 694 |
|
| 695 |
Setting the text width will also set the page height to -1, causing the document to |
| 696 |
grow or shrink vertically in a continuous way. If you want the document layout to break |
| 697 |
the text into multiple pages then you have to set the pageSize property instead. |
| 698 |
|
| 699 |
\sa size(), idealWidth(), pageSize() |
| 700 |
*/ |
| 701 |
void QTextDocument::setTextWidth(qreal width) |
| 702 |
{ |
| 703 |
Q_D(QTextDocument); |
| 704 |
QSizeF sz = d->pageSize; |
| 705 |
sz.setWidth(width); |
| 706 |
sz.setHeight(-1); |
| 707 |
setPageSize(sz); |
| 708 |
} |
| 709 |
|
| 710 |
qreal QTextDocument::textWidth() const |
| 711 |
{ |
| 712 |
Q_D(const QTextDocument); |
| 713 |
return d->pageSize.width(); |
| 714 |
} |
| 715 |
|
| 716 |
/*! |
| 717 |
\since 4.2 |
| 718 |
|
| 719 |
Returns the ideal width of the text document. The ideal width is the actually used width |
| 720 |
of the document without optional alignments taken into account. It is always <= size().width(). |
| 721 |
|
| 722 |
\sa adjustSize(), textWidth |
| 723 |
*/ |
| 724 |
qreal QTextDocument::idealWidth() const |
| 725 |
{ |
| 726 |
if (QTextDocumentLayout *lout = qobject_cast<QTextDocumentLayout *>(documentLayout())) |
| 727 |
return lout->idealWidth(); |
| 728 |
return textWidth(); |
| 729 |
} |
| 730 |
|
| 731 |
/*! |
| 732 |
\property QTextDocument::documentMargin |
| 733 |
\since 4.5 |
| 734 |
|
| 735 |
The margin around the document. The default is 4. |
| 736 |
*/ |
| 737 |
qreal QTextDocument::documentMargin() const |
| 738 |
{ |
| 739 |
Q_D(const QTextDocument); |
| 740 |
return d->documentMargin; |
| 741 |
} |
| 742 |
|
| 743 |
void QTextDocument::setDocumentMargin(qreal margin) |
| 744 |
{ |
| 745 |
Q_D(QTextDocument); |
| 746 |
if (d->documentMargin != margin) { |
| 747 |
d->documentMargin = margin; |
| 748 |
|
| 749 |
QTextFrame* root = rootFrame(); |
| 750 |
QTextFrameFormat format = root->frameFormat(); |
| 751 |
format.setMargin(margin); |
| 752 |
root->setFrameFormat(format); |
| 753 |
|
| 754 |
if (d->lout) |
| 755 |
d->lout->documentChanged(0, 0, d->length()); |
| 756 |
} |
| 757 |
} |
| 758 |
|
| 759 |
|
| 760 |
/*! |
| 761 |
\property QTextDocument::indentWidth |
| 762 |
\since 4.4 |
| 763 |
|
| 764 |
Returns the width used for text list and text block indenting. |
| 765 |
|
| 766 |
The indent properties of QTextListFormat and QTextBlockFormat specify |
| 767 |
multiples of this value. The default indent width is 40. |
| 768 |
*/ |
| 769 |
qreal QTextDocument::indentWidth() const |
| 770 |
{ |
| 771 |
Q_D(const QTextDocument); |
| 772 |
return d->indentWidth; |
| 773 |
} |
| 774 |
|
| 775 |
|
| 776 |
/*! |
| 777 |
\since 4.4 |
| 778 |
|
| 779 |
Sets the \a width used for text list and text block indenting. |
| 780 |
|
| 781 |
The indent properties of QTextListFormat and QTextBlockFormat specify |
| 782 |
multiples of this value. The default indent width is 40 . |
| 783 |
|
| 784 |
\sa indentWidth() |
| 785 |
*/ |
| 786 |
void QTextDocument::setIndentWidth(qreal width) |
| 787 |
{ |
| 788 |
Q_D(QTextDocument); |
| 789 |
if (d->indentWidth != width) { |
| 790 |
d->indentWidth = width; |
| 791 |
if (d->lout) |
| 792 |
d->lout->documentChanged(0, 0, d->length()); |
| 793 |
} |
| 794 |
} |
| 795 |
|
| 796 |
|
| 797 |
|
| 798 |
|
| 799 |
/*! |
| 800 |
\since 4.2 |
| 801 |
|
| 802 |
Adjusts the document to a reasonable size. |
| 803 |
|
| 804 |
\sa idealWidth(), textWidth, size |
| 805 |
*/ |
| 806 |
void QTextDocument::adjustSize() |
| 807 |
{ |
| 808 |
// Pull this private function in from qglobal.cpp |
| 809 |
QFont f = defaultFont(); |
| 810 |
QFontMetrics fm(f); |
| 811 |
int mw = fm.width(QLatin1Char('x')) * 80; |
| 812 |
int w = mw; |
| 813 |
setTextWidth(w); |
| 814 |
QSizeF size = documentLayout()->documentSize(); |
| 815 |
if (size.width() != 0) { |
| 816 |
w = qt_int_sqrt((uint)(5 * size.height() * size.width() / 3)); |
| 817 |
setTextWidth(qMin(w, mw)); |
| 818 |
|
| 819 |
size = documentLayout()->documentSize(); |
| 820 |
if (w*3 < 5*size.height()) { |
| 821 |
w = qt_int_sqrt((uint)(2 * size.height() * size.width())); |
| 822 |
setTextWidth(qMin(w, mw)); |
| 823 |
} |
| 824 |
} |
| 825 |
setTextWidth(idealWidth()); |
| 826 |
} |
| 827 |
|
| 828 |
/*! |
| 829 |
\property QTextDocument::size |
| 830 |
\since 4.2 |
| 831 |
|
| 832 |
Returns the actual size of the document. |
| 833 |
This is equivalent to documentLayout()->documentSize(); |
| 834 |
|
| 835 |
The size of the document can be changed either by setting |
| 836 |
a text width or setting an entire page size. |
| 837 |
|
| 838 |
Note that the width is always >= pageSize().width(). |
| 839 |
|
| 840 |
By default, for a newly-created, empty document, this property contains |
| 841 |
a configuration-dependent size. |
| 842 |
|
| 843 |
\sa setTextWidth(), setPageSize(), idealWidth() |
| 844 |
*/ |
| 845 |
QSizeF QTextDocument::size() const |
| 846 |
{ |
| 847 |
return documentLayout()->documentSize(); |
| 848 |
} |
| 849 |
|
| 850 |
/*! |
| 851 |
\property QTextDocument::blockCount |
| 852 |
\since 4.2 |
| 853 |
|
| 854 |
Returns the number of text blocks in the document. |
| 855 |
|
| 856 |
The value of this property is undefined in documents with tables or frames. |
| 857 |
|
| 858 |
By default, if defined, this property contains a value of 1. |
| 859 |
\sa lineCount(), characterCount() |
| 860 |
*/ |
| 861 |
int QTextDocument::blockCount() const |
| 862 |
{ |
| 863 |
Q_D(const QTextDocument); |
| 864 |
return d->blockMap().numNodes(); |
| 865 |
} |
| 866 |
|
| 867 |
|
| 868 |
/*! |
| 869 |
\since 4.5 |
| 870 |
|
| 871 |
Returns the number of lines of this document (if the layout supports |
| 872 |
this). Otherwise, this is identical to the number of blocks. |
| 873 |
|
| 874 |
\sa blockCount(), characterCount() |
| 875 |
*/ |
| 876 |
int QTextDocument::lineCount() const |
| 877 |
{ |
| 878 |
Q_D(const QTextDocument); |
| 879 |
return d->blockMap().length(2); |
| 880 |
} |
| 881 |
|
| 882 |
/*! |
| 883 |
\since 4.5 |
| 884 |
|
| 885 |
Returns the number of characters of this document. |
| 886 |
|
| 887 |
\sa blockCount(), characterAt() |
| 888 |
*/ |
| 889 |
int QTextDocument::characterCount() const |
| 890 |
{ |
| 891 |
Q_D(const QTextDocument); |
| 892 |
return d->length(); |
| 893 |
} |
| 894 |
|
| 895 |
/*! |
| 896 |
\since 4.5 |
| 897 |
|
| 898 |
Returns the character at position \a pos, or a null character if the |
| 899 |
position is out of range. |
| 900 |
|
| 901 |
\sa characterCount() |
| 902 |
*/ |
| 903 |
QChar QTextDocument::characterAt(int pos) const |
| 904 |
{ |
| 905 |
Q_D(const QTextDocument); |
| 906 |
if (pos < 0 || pos >= d->length()) |
| 907 |
return QChar(); |
| 908 |
QTextDocumentPrivate::FragmentIterator fragIt = d->find(pos); |
| 909 |
const QTextFragmentData * const frag = fragIt.value(); |
| 910 |
const int offsetInFragment = qMax(0, pos - fragIt.position()); |
| 911 |
return d->text.at(frag->stringPosition + offsetInFragment); |
| 912 |
} |
| 913 |
|
| 914 |
|
| 915 |
/*! |
| 916 |
\property QTextDocument::defaultStyleSheet |
| 917 |
\since 4.2 |
| 918 |
|
| 919 |
The default style sheet is applied to all newly HTML formatted text that is |
| 920 |
inserted into the document, for example using setHtml() or QTextCursor::insertHtml(). |
| 921 |
|
| 922 |
The style sheet needs to be compliant to CSS 2.1 syntax. |
| 923 |
|
| 924 |
\bold{Note:} Changing the default style sheet does not have any effect to the existing content |
| 925 |
of the document. |
| 926 |
|
| 927 |
\sa {Supported HTML Subset} |
| 928 |
*/ |
| 929 |
|
| 930 |
#ifndef QT_NO_CSSPARSER |
| 931 |
void QTextDocument::setDefaultStyleSheet(const QString &sheet) |
| 932 |
{ |
| 933 |
Q_D(QTextDocument); |
| 934 |
d->defaultStyleSheet = sheet; |
| 935 |
QCss::Parser parser(sheet); |
| 936 |
d->parsedDefaultStyleSheet = QCss::StyleSheet(); |
| 937 |
d->parsedDefaultStyleSheet.origin = QCss::StyleSheetOrigin_UserAgent; |
| 938 |
parser.parse(&d->parsedDefaultStyleSheet); |
| 939 |
} |
| 940 |
|
| 941 |
QString QTextDocument::defaultStyleSheet() const |
| 942 |
{ |
| 943 |
Q_D(const QTextDocument); |
| 944 |
return d->defaultStyleSheet; |
| 945 |
} |
| 946 |
#endif // QT_NO_CSSPARSER |
| 947 |
|
| 948 |
/*! |
| 949 |
\fn void QTextDocument::contentsChanged() |
| 950 |
|
| 951 |
This signal is emitted whenever the document's content changes; for |
| 952 |
example, when text is inserted or deleted, or when formatting is applied. |
| 953 |
|
| 954 |
\sa contentsChange() |
| 955 |
*/ |
| 956 |
|
| 957 |
/*! |
| 958 |
\fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded) |
| 959 |
|
| 960 |
This signal is emitted whenever the document's content changes; for |
| 961 |
example, when text is inserted or deleted, or when formatting is applied. |
| 962 |
|
| 963 |
Information is provided about the \a position of the character in the |
| 964 |
document where the change occurred, the number of characters removed |
| 965 |
(\a charsRemoved), and the number of characters added (\a charsAdded). |
| 966 |
|
| 967 |
The signal is emitted before the document's layout manager is notified |
| 968 |
about the change. This hook allows you to implement syntax highlighting |
| 969 |
for the document. |
| 970 |
|
| 971 |
\sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged() |
| 972 |
*/ |
| 973 |
|
| 974 |
|
| 975 |
/*! |
| 976 |
\fn QTextDocument::undoAvailable(bool available); |
| 977 |
|
| 978 |
This signal is emitted whenever undo operations become available |
| 979 |
(\a available is true) or unavailable (\a available is false). |
| 980 |
|
| 981 |
See the \l {Overview of Qt's Undo Framework}{Qt Undo Framework} |
| 982 |
documentation for details. |
| 983 |
|
| 984 |
\sa undo(), isUndoRedoEnabled() |
| 985 |
*/ |
| 986 |
|
| 987 |
/*! |
| 988 |
\fn QTextDocument::redoAvailable(bool available); |
| 989 |
|
| 990 |
This signal is emitted whenever redo operations become available |
| 991 |
(\a available is true) or unavailable (\a available is false). |
| 992 |
*/ |
| 993 |
|
| 994 |
/*! |
| 995 |
\fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor); |
| 996 |
|
| 997 |
This signal is emitted whenever the position of a cursor changed |
| 998 |
due to an editing operation. The cursor that changed is passed in |
| 999 |
\a cursor. If you need a signal when the cursor is moved with the |
| 1000 |
arrow keys you can use the \l{QTextEdit::}{cursorPositionChanged()} signal in |
| 1001 |
QTextEdit. |
| 1002 |
*/ |
| 1003 |
|
| 1004 |
/*! |
| 1005 |
\fn QTextDocument::blockCountChanged(int newBlockCount); |
| 1006 |
\since 4.3 |
| 1007 |
|
| 1008 |
This signal is emitted when the total number of text blocks in the |
| 1009 |
document changes. The value passed in \a newBlockCount is the new |
| 1010 |
total. |
| 1011 |
*/ |
| 1012 |
|
| 1013 |
/*! |
| 1014 |
\fn QTextDocument::documentLayoutChanged(); |
| 1015 |
\since 4.4 |
| 1016 |
|
| 1017 |
This signal is emitted when a new document layout is set. |
| 1018 |
|
| 1019 |
\sa setDocumentLayout() |
| 1020 |
|
| 1021 |
*/ |
| 1022 |
|
| 1023 |
|
| 1024 |
/*! |
| 1025 |
Returns true if undo is available; otherwise returns false. |
| 1026 |
|
| 1027 |
\sa isRedoAvailable(), availableUndoSteps() |
| 1028 |
*/ |
| 1029 |
bool QTextDocument::isUndoAvailable() const |
| 1030 |
{ |
| 1031 |
Q_D(const QTextDocument); |
| 1032 |
return d->isUndoAvailable(); |
| 1033 |
} |
| 1034 |
|
| 1035 |
/*! |
| 1036 |
Returns true if redo is available; otherwise returns false. |
| 1037 |
|
| 1038 |
\sa isUndoAvailable(), availableRedoSteps() |
| 1039 |
*/ |
| 1040 |
bool QTextDocument::isRedoAvailable() const |
| 1041 |
{ |
| 1042 |
Q_D(const QTextDocument); |
| 1043 |
return d->isRedoAvailable(); |
| 1044 |
} |
| 1045 |
|
| 1046 |
/*! \since 4.6 |
| 1047 |
|
| 1048 |
Returns the number of available undo steps. |
| 1049 |
|
| 1050 |
\sa isUndoAvailable() |
| 1051 |
*/ |
| 1052 |
int QTextDocument::availableUndoSteps() const |
| 1053 |
{ |
| 1054 |
Q_D(const QTextDocument); |
| 1055 |
return d->availableUndoSteps(); |
| 1056 |
} |
| 1057 |
|
| 1058 |
/*! \since 4.6 |
| 1059 |
|
| 1060 |
Returns the number of available redo steps. |
| 1061 |
|
| 1062 |
\sa isRedoAvailable() |
| 1063 |
*/ |
| 1064 |
int QTextDocument::availableRedoSteps() const |
| 1065 |
{ |
| 1066 |
Q_D(const QTextDocument); |
| 1067 |
return d->availableRedoSteps(); |
| 1068 |
} |
| 1069 |
|
| 1070 |
/*! \since 4.4 |
| 1071 |
|
| 1072 |
Returns the document's revision (if undo is enabled). |
| 1073 |
|
| 1074 |
The revision is guaranteed to increase when a document that is not |
| 1075 |
modified is edited. |
| 1076 |
|
| 1077 |
\sa QTextBlock::revision(), isModified() |
| 1078 |
*/ |
| 1079 |
int QTextDocument::revision() const |
| 1080 |
{ |
| 1081 |
Q_D(const QTextDocument); |
| 1082 |
return d->revision; |
| 1083 |
} |
| 1084 |
|
| 1085 |
|
| 1086 |
|
| 1087 |
/*! |
| 1088 |
Sets the document to use the given \a layout. The previous layout |
| 1089 |
is deleted. |
| 1090 |
|
| 1091 |
\sa documentLayoutChanged() |
| 1092 |
*/ |
| 1093 |
void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout) |
| 1094 |
{ |
| 1095 |
Q_D(QTextDocument); |
| 1096 |
d->setLayout(layout); |
| 1097 |
} |
| 1098 |
|
| 1099 |
/*! |
| 1100 |
Returns the document layout for this document. |
| 1101 |
*/ |
| 1102 |
QAbstractTextDocumentLayout *QTextDocument::documentLayout() const |
| 1103 |
{ |
| 1104 |
Q_D(const QTextDocument); |
| 1105 |
if (!d->lout) { |
| 1106 |
QTextDocument *that = const_cast<QTextDocument *>(this); |
| 1107 |
that->d_func()->setLayout(new QTextDocumentLayout(that)); |
| 1108 |
} |
| 1109 |
return d->lout; |
| 1110 |
} |
| 1111 |
|
| 1112 |
|
| 1113 |
/*! |
| 1114 |
Returns meta information about the document of the type specified by |
| 1115 |
\a info. |
| 1116 |
|
| 1117 |
\sa setMetaInformation() |
| 1118 |
*/ |
| 1119 |
QString QTextDocument::metaInformation(MetaInformation info) const |
| 1120 |
{ |
| 1121 |
Q_D(const QTextDocument); |
| 1122 |
switch (info) { |
| 1123 |
case DocumentTitle: |
| 1124 |
return d->title; |
| 1125 |
case DocumentUrl: |
| 1126 |
return d->url; |
| 1127 |
} |
| 1128 |
return QString(); |
| 1129 |
} |
| 1130 |
|
| 1131 |
/*! |
| 1132 |
Sets the document's meta information of the type specified by \a info |
| 1133 |
to the given \a string. |
| 1134 |
|
| 1135 |
\sa metaInformation() |
| 1136 |
*/ |
| 1137 |
void QTextDocument::setMetaInformation(MetaInformation info, const QString &string) |
| 1138 |
{ |
| 1139 |
Q_D(QTextDocument); |
| 1140 |
switch (info) { |
| 1141 |
case DocumentTitle: |
| 1142 |
d->title = string; |
| 1143 |
break; |
| 1144 |
case DocumentUrl: |
| 1145 |
d->url = string; |
| 1146 |
break; |
| 1147 |
} |
| 1148 |
} |
| 1149 |
|
| 1150 |
/*! |
| 1151 |
Returns the plain text contained in the document. If you want |
| 1152 |
formatting information use a QTextCursor instead. |
| 1153 |
|
| 1154 |
\sa toHtml() |
| 1155 |
*/ |
| 1156 |
QString QTextDocument::toPlainText() const |
| 1157 |
{ |
| 1158 |
Q_D(const QTextDocument); |
| 1159 |
QString txt = d->plainText(); |
| 1160 |
|
| 1161 |
QChar *uc = txt.data(); |
| 1162 |
QChar *e = uc + txt.size(); |
| 1163 |
|
| 1164 |
for (; uc != e; ++uc) { |
| 1165 |
switch (uc->unicode()) { |
| 1166 |
case 0xfdd0: // QTextBeginningOfFrame |
| 1167 |
case 0xfdd1: // QTextEndOfFrame |
| 1168 |
case QChar::ParagraphSeparator: |
| 1169 |
case QChar::LineSeparator: |
| 1170 |
*uc = QLatin1Char('\n'); |
| 1171 |
break; |
| 1172 |
case QChar::Nbsp: |
| 1173 |
*uc = QLatin1Char(' '); |
| 1174 |
break; |
| 1175 |
default: |
| 1176 |
; |
| 1177 |
} |
| 1178 |
} |
| 1179 |
return txt; |
| 1180 |
} |
| 1181 |
|
| 1182 |
/*! |
| 1183 |
Replaces the entire contents of the document with the given plain |
| 1184 |
\a text. |
| 1185 |
|
| 1186 |
\sa setHtml() |
| 1187 |
*/ |
| 1188 |
void QTextDocument::setPlainText(const QString &text) |
| 1189 |
{ |
| 1190 |
Q_D(QTextDocument); |
| 1191 |
bool previousState = d->isUndoRedoEnabled(); |
| 1192 |
d->enableUndoRedo(false); |
| 1193 |
d->beginEditBlock(); |
| 1194 |
d->clear(); |
| 1195 |
QTextCursor(this).insertText(text); |
| 1196 |
d->endEditBlock(); |
| 1197 |
d->enableUndoRedo(previousState); |
| 1198 |
} |
| 1199 |
|
| 1200 |
/*! |
| 1201 |
Replaces the entire contents of the document with the given |
| 1202 |
HTML-formatted text in the \a html string. |
| 1203 |
|
| 1204 |
The HTML formatting is respected as much as possible; for example, |
| 1205 |
"<b>bold</b> text" will produce text where the first word has a font |
| 1206 |
weight that gives it a bold appearance: "\bold{bold} text". |
| 1207 |
|
| 1208 |
\note It is the responsibility of the caller to make sure that the |
| 1209 |
text is correctly decoded when a QString containing HTML is created |
| 1210 |
and passed to setHtml(). |
| 1211 |
|
| 1212 |
\sa setPlainText(), {Supported HTML Subset} |
| 1213 |
*/ |
| 1214 |
|
| 1215 |
#ifndef QT_NO_TEXTHTMLPARSER |
| 1216 |
|
| 1217 |
void QTextDocument::setHtml(const QString &html) |
| 1218 |
{ |
| 1219 |
Q_D(QTextDocument); |
| 1220 |
bool previousState = d->isUndoRedoEnabled(); |
| 1221 |
d->enableUndoRedo(false); |
| 1222 |
d->beginEditBlock(); |
| 1223 |
d->clear(); |
| 1224 |
QTextHtmlImporter(this, html, QTextHtmlImporter::ImportToDocument).import(); |
| 1225 |
d->endEditBlock(); |
| 1226 |
d->enableUndoRedo(previousState); |
| 1227 |
} |
| 1228 |
|
| 1229 |
#endif // QT_NO_TEXTHTMLPARSER |
| 1230 |
|
| 1231 |
/*! |
| 1232 |
\enum QTextDocument::FindFlag |
| 1233 |
|
| 1234 |
This enum describes the options available to QTextDocument's find function. The options |
| 1235 |
can be OR-ed together from the following list: |
| 1236 |
|
| 1237 |
\value FindBackward Search backwards instead of forwards. |
| 1238 |
\value FindCaseSensitively By default find works case insensitive. Specifying this option |
| 1239 |
changes the behaviour to a case sensitive find operation. |
| 1240 |
\value FindWholeWords Makes find match only complete words. |
| 1241 |
*/ |
| 1242 |
|
| 1243 |
/*! |
| 1244 |
\enum QTextDocument::MetaInformation |
| 1245 |
|
| 1246 |
This enum describes the different types of meta information that can be |
| 1247 |
added to a document. |
| 1248 |
|
| 1249 |
\value DocumentTitle The title of the document. |
| 1250 |
\value DocumentUrl The url of the document. The loadResource() function uses |
| 1251 |
this url as the base when loading relative resources. |
| 1252 |
|
| 1253 |
\sa metaInformation(), setMetaInformation() |
| 1254 |
*/ |
| 1255 |
|
| 1256 |
/*! |
| 1257 |
\fn QTextCursor QTextDocument::find(const QString &subString, int position, FindFlags options) const |
| 1258 |
|
| 1259 |
\overload |
| 1260 |
|
| 1261 |
Finds the next occurrence of the string, \a subString, in the document. |
| 1262 |
The search starts at the given \a position, and proceeds forwards |
| 1263 |
through the document unless specified otherwise in the search options. |
| 1264 |
The \a options control the type of search performed. |
| 1265 |
|
| 1266 |
Returns a cursor with the match selected if \a subString |
| 1267 |
was found; otherwise returns a null cursor. |
| 1268 |
|
| 1269 |
If the \a position is 0 (the default) the search begins from the beginning |
| 1270 |
of the document; otherwise it begins at the specified position. |
| 1271 |
*/ |
| 1272 |
QTextCursor QTextDocument::find(const QString &subString, int from, FindFlags options) const |
| 1273 |
{ |
| 1274 |
QRegExp expr(subString); |
| 1275 |
expr.setPatternSyntax(QRegExp::FixedString); |
| 1276 |
expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); |
| 1277 |
|
| 1278 |
return find(expr, from, options); |
| 1279 |
} |
| 1280 |
|
| 1281 |
/*! |
| 1282 |
\fn QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &cursor, FindFlags options) const |
| 1283 |
|
| 1284 |
Finds the next occurrence of the string, \a subString, in the document. |
| 1285 |
The search starts at the position of the given \a cursor, and proceeds |
| 1286 |
forwards through the document unless specified otherwise in the search |
| 1287 |
options. The \a options control the type of search performed. |
| 1288 |
|
| 1289 |
Returns a cursor with the match selected if \a subString was found; otherwise |
| 1290 |
returns a null cursor. |
| 1291 |
|
| 1292 |
If the given \a cursor has a selection, the search begins after the |
| 1293 |
selection; otherwise it begins at the cursor's position. |
| 1294 |
|
| 1295 |
By default the search is case-sensitive, and can match text anywhere in the |
| 1296 |
document. |
| 1297 |
*/ |
| 1298 |
QTextCursor QTextDocument::find(const QString &subString, const QTextCursor &from, FindFlags options) const |
| 1299 |
{ |
| 1300 |
int pos = 0; |
| 1301 |
if (!from.isNull()) { |
| 1302 |
if (options & QTextDocument::FindBackward) |
| 1303 |
pos = from.selectionStart(); |
| 1304 |
else |
| 1305 |
pos = from.selectionEnd(); |
| 1306 |
} |
| 1307 |
QRegExp expr(subString); |
| 1308 |
expr.setPatternSyntax(QRegExp::FixedString); |
| 1309 |
expr.setCaseSensitivity((options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive); |
| 1310 |
|
| 1311 |
return find(expr, pos, options); |
| 1312 |
} |
| 1313 |
|
| 1314 |
|
| 1315 |
static bool findInBlock(const QTextBlock &block, const QRegExp &expression, int offset, |
| 1316 |
QTextDocument::FindFlags options, QTextCursor &cursor) |
| 1317 |
{ |
| 1318 |
const QRegExp expr(expression); |
| 1319 |
QString text = block.text(); |
| 1320 |
text.replace(QChar::Nbsp, QLatin1Char(' ')); |
| 1321 |
|
| 1322 |
int idx = -1; |
| 1323 |
while (offset >=0 && offset <= text.length()) { |
| 1324 |
idx = (options & QTextDocument::FindBackward) ? |
| 1325 |
expr.lastIndexIn(text, offset) : expr.indexIn(text, offset); |
| 1326 |
if (idx == -1) |
| 1327 |
return false; |
| 1328 |
|
| 1329 |
if (options & QTextDocument::FindWholeWords) { |
| 1330 |
const int start = idx; |
| 1331 |
const int end = start + expr.matchedLength(); |
| 1332 |
if ((start != 0 && text.at(start - 1).isLetterOrNumber()) |
| 1333 |
|| (end != text.length() && text.at(end).isLetterOrNumber())) { |
| 1334 |
//if this is not a whole word, continue the search in the string |
| 1335 |
offset = (options & QTextDocument::FindBackward) ? idx-1 : end+1; |
| 1336 |
idx = -1; |
| 1337 |
continue; |
| 1338 |
} |
| 1339 |
} |
| 1340 |
//we have a hit, return the cursor for that. |
| 1341 |
break; |
| 1342 |
} |
| 1343 |
if (idx == -1) |
| 1344 |
return false; |
| 1345 |
cursor = QTextCursor(block.docHandle(), block.position() + idx); |
| 1346 |
cursor.setPosition(cursor.position() + expr.matchedLength(), QTextCursor::KeepAnchor); |
| 1347 |
return true; |
| 1348 |
} |
| 1349 |
|
| 1350 |
/*! |
| 1351 |
\fn QTextCursor QTextDocument::find(const QRegExp & expr, int position, FindFlags options) const |
| 1352 |
|
| 1353 |
\overload |
| 1354 |
|
| 1355 |
Finds the next occurrence, matching the regular expression, \a expr, in the document. |
| 1356 |
The search starts at the given \a position, and proceeds forwards |
| 1357 |
through the document unless specified otherwise in the search options. |
| 1358 |
The \a options control the type of search performed. The FindCaseSensitively |
| 1359 |
option is ignored for this overload, use QRegExp::caseSensitivity instead. |
| 1360 |
|
| 1361 |
Returns a cursor with the match selected if a match was found; otherwise |
| 1362 |
returns a null cursor. |
| 1363 |
|
| 1364 |
If the \a position is 0 (the default) the search begins from the beginning |
| 1365 |
of the document; otherwise it begins at the specified position. |
| 1366 |
*/ |
| 1367 |
QTextCursor QTextDocument::find(const QRegExp & expr, int from, FindFlags options) const |
| 1368 |
{ |
| 1369 |
Q_D(const QTextDocument); |
| 1370 |
|
| 1371 |
if (expr.isEmpty()) |
| 1372 |
return QTextCursor(); |
| 1373 |
|
| 1374 |
int pos = from; |
| 1375 |
//the cursor is positioned between characters, so for a backward search |
| 1376 |
//do not include the character given in the position. |
| 1377 |
if (options & FindBackward) { |
| 1378 |
--pos ; |
| 1379 |
if(pos < 0) |
| 1380 |
return QTextCursor(); |
| 1381 |
} |
| 1382 |
|
| 1383 |
QTextCursor cursor; |
| 1384 |
QTextBlock block = d->blocksFind(pos); |
| 1385 |
|
| 1386 |
if (!(options & FindBackward)) { |
| 1387 |
int blockOffset = qMax(0, pos - block.position()); |
| 1388 |
while (block.isValid()) { |
| 1389 |
if (findInBlock(block, expr, blockOffset, options, cursor)) |
| 1390 |
return cursor; |
| 1391 |
blockOffset = 0; |
| 1392 |
block = block.next(); |
| 1393 |
} |
| 1394 |
} else { |
| 1395 |
int blockOffset = pos - block.position(); |
| 1396 |
while (block.isValid()) { |
| 1397 |
if (findInBlock(block, expr, blockOffset, options, cursor)) |
| 1398 |
return cursor; |
| 1399 |
block = block.previous(); |
| 1400 |
blockOffset = block.length() - 1; |
| 1401 |
} |
| 1402 |
} |
| 1403 |
|
| 1404 |
return QTextCursor(); |
| 1405 |
} |
| 1406 |
|
| 1407 |
/*! |
| 1408 |
\fn QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &cursor, FindFlags options) const |
| 1409 |
|
| 1410 |
Finds the next occurrence, matching the regular expression, \a expr, in the document. |
| 1411 |
The search starts at the position of the given \a cursor, and proceeds |
| 1412 |
forwards through the document unless specified otherwise in the search |
| 1413 |
options. The \a options control the type of search performed. The FindCaseSensitively |
| 1414 |
option is ignored for this overload, use QRegExp::caseSensitivity instead. |
| 1415 |
|
| 1416 |
Returns a cursor with the match selected if a match was found; otherwise |
| 1417 |
returns a null cursor. |
| 1418 |
|
| 1419 |
If the given \a cursor has a selection, the search begins after the |
| 1420 |
selection; otherwise it begins at the cursor's position. |
| 1421 |
|
| 1422 |
By default the search is case-sensitive, and can match text anywhere in the |
| 1423 |
document. |
| 1424 |
*/ |
| 1425 |
QTextCursor QTextDocument::find(const QRegExp &expr, const QTextCursor &from, FindFlags options) const |
| 1426 |
{ |
| 1427 |
int pos = 0; |
| 1428 |
if (!from.isNull()) { |
| 1429 |
if (options & QTextDocument::FindBackward) |
| 1430 |
pos = from.selectionStart(); |
| 1431 |
else |
| 1432 |
pos = from.selectionEnd(); |
| 1433 |
} |
| 1434 |
return find(expr, pos, options); |
| 1435 |
} |
| 1436 |
|
| 1437 |
|
| 1438 |
/*! |
| 1439 |
\fn QTextObject *QTextDocument::createObject(const QTextFormat &format) |
| 1440 |
|
| 1441 |
Creates and returns a new document object (a QTextObject), based |
| 1442 |
on the given \a format. |
| 1443 |
|
| 1444 |
QTextObjects will always get created through this method, so you |
| 1445 |
must reimplement it if you use custom text objects inside your document. |
| 1446 |
*/ |
| 1447 |
QTextObject *QTextDocument::createObject(const QTextFormat &f) |
| 1448 |
{ |
| 1449 |
QTextObject *obj = 0; |
| 1450 |
if (f.isListFormat()) |
| 1451 |
obj = new QTextList(this); |
| 1452 |
else if (f.isTableFormat()) |
| 1453 |
obj = new QTextTable(this); |
| 1454 |
else if (f.isFrameFormat()) |
| 1455 |
obj = new QTextFrame(this); |
| 1456 |
|
| 1457 |
return obj; |
| 1458 |
} |
| 1459 |
|
| 1460 |
/*! |
| 1461 |
\internal |
| 1462 |
|
| 1463 |
Returns the frame that contains the text cursor position \a pos. |
| 1464 |
*/ |
| 1465 |
QTextFrame *QTextDocument::frameAt(int pos) const |
| 1466 |
{ |
| 1467 |
Q_D(const QTextDocument); |
| 1468 |
return d->frameAt(pos); |
| 1469 |
} |
| 1470 |
|
| 1471 |
/*! |
| 1472 |
Returns the document's root frame. |
| 1473 |
*/ |
| 1474 |
QTextFrame *QTextDocument::rootFrame() const |
| 1475 |
{ |
| 1476 |
Q_D(const QTextDocument); |
| 1477 |
return d->rootFrame(); |
| 1478 |
} |
| 1479 |
|
| 1480 |
/*! |
| 1481 |
Returns the text object associated with the given \a objectIndex. |
| 1482 |
*/ |
| 1483 |
QTextObject *QTextDocument::object(int objectIndex) const |
| 1484 |
{ |
| 1485 |
Q_D(const QTextDocument); |
| 1486 |
return d->objectForIndex(objectIndex); |
| 1487 |
} |
| 1488 |
|
| 1489 |
/*! |
| 1490 |
Returns the text object associated with the format \a f. |
| 1491 |
*/ |
| 1492 |
QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const |
| 1493 |
{ |
| 1494 |
Q_D(const QTextDocument); |
| 1495 |
return d->objectForFormat(f); |
| 1496 |
} |
| 1497 |
|
| 1498 |
|
| 1499 |
/*! |
| 1500 |
Returns the text block that contains the \a{pos}-th character. |
| 1501 |
*/ |
| 1502 |
QTextBlock QTextDocument::findBlock(int pos) const |
| 1503 |
{ |
| 1504 |
Q_D(const QTextDocument); |
| 1505 |
return QTextBlock(docHandle(), d->blockMap().findNode(pos)); |
| 1506 |
} |
| 1507 |
|
| 1508 |
/*! |
| 1509 |
\since 4.4 |
| 1510 |
Returns the text block with the specified \a blockNumber. |
| 1511 |
|
| 1512 |
\sa QTextBlock::blockNumber() |
| 1513 |
*/ |
| 1514 |
QTextBlock QTextDocument::findBlockByNumber(int blockNumber) const |
| 1515 |
{ |
| 1516 |
Q_D(const QTextDocument); |
| 1517 |
return QTextBlock(docHandle(), d->blockMap().findNode(blockNumber, 1)); |
| 1518 |
} |
| 1519 |
|
| 1520 |
/*! |
| 1521 |
\since 4.5 |
| 1522 |
Returns the text block that contains the specified \a lineNumber. |
| 1523 |
|
| 1524 |
\sa QTextBlock::firstLineNumber() |
| 1525 |
*/ |
| 1526 |
QTextBlock QTextDocument::findBlockByLineNumber(int lineNumber) const |
| 1527 |
{ |
| 1528 |
Q_D(const QTextDocument); |
| 1529 |
return QTextBlock(docHandle(), d->blockMap().findNode(lineNumber, 2)); |
| 1530 |
} |
| 1531 |
|
| 1532 |
/*! |
| 1533 |
Returns the document's first text block. |
| 1534 |
|
| 1535 |
\sa firstBlock() |
| 1536 |
*/ |
| 1537 |
QTextBlock QTextDocument::begin() const |
| 1538 |
{ |
| 1539 |
Q_D(const QTextDocument); |
| 1540 |
return QTextBlock(docHandle(), d->blockMap().begin().n); |
| 1541 |
} |
| 1542 |
|
| 1543 |
/*! |
| 1544 |
This function returns a block to test for the end of the document |
| 1545 |
while iterating over it. |
| 1546 |
|
| 1547 |
\snippet doc/src/snippets/textdocumentendsnippet.cpp 0 |
| 1548 |
|
| 1549 |
The block returned is invalid and represents the block after the |
| 1550 |
last block in the document. You can use lastBlock() to retrieve the |
| 1551 |
last valid block of the document. |
| 1552 |
|
| 1553 |
\sa lastBlock() |
| 1554 |
*/ |
| 1555 |
QTextBlock QTextDocument::end() const |
| 1556 |
{ |
| 1557 |
return QTextBlock(docHandle(), 0); |
| 1558 |
} |
| 1559 |
|
| 1560 |
/*! |
| 1561 |
\since 4.4 |
| 1562 |
Returns the document's first text block. |
| 1563 |
*/ |
| 1564 |
QTextBlock QTextDocument::firstBlock() const |
| 1565 |
{ |
| 1566 |
Q_D(const QTextDocument); |
| 1567 |
return QTextBlock(docHandle(), d->blockMap().begin().n); |
| 1568 |
} |
| 1569 |
|
| 1570 |
/*! |
| 1571 |
\since 4.4 |
| 1572 |
Returns the document's last (valid) text block. |
| 1573 |
*/ |
| 1574 |
QTextBlock QTextDocument::lastBlock() const |
| 1575 |
{ |
| 1576 |
Q_D(const QTextDocument); |
| 1577 |
return QTextBlock(docHandle(), d->blockMap().last().n); |
| 1578 |
} |
| 1579 |
|
| 1580 |
/*! |
| 1581 |
\property QTextDocument::pageSize |
| 1582 |
\brief the page size that should be used for laying out the document |
| 1583 |
|
| 1584 |
By default, for a newly-created, empty document, this property contains |
| 1585 |
an undefined size. |
| 1586 |
|
| 1587 |
\sa modificationChanged() |
| 1588 |
*/ |
| 1589 |
|
| 1590 |
void QTextDocument::setPageSize(const QSizeF &size) |
| 1591 |
{ |
| 1592 |
Q_D(QTextDocument); |
| 1593 |
d->pageSize = size; |
| 1594 |
if (d->lout) |
| 1595 |
d->lout->documentChanged(0, 0, d->length()); |
| 1596 |
} |
| 1597 |
|
| 1598 |
QSizeF QTextDocument::pageSize() const |
| 1599 |
{ |
| 1600 |
Q_D(const QTextDocument); |
| 1601 |
return d->pageSize; |
| 1602 |
} |
| 1603 |
|
| 1604 |
/*! |
| 1605 |
returns the number of pages in this document. |
| 1606 |
*/ |
| 1607 |
int QTextDocument::pageCount() const |
| 1608 |
{ |
| 1609 |
return documentLayout()->pageCount(); |
| 1610 |
} |
| 1611 |
|
| 1612 |
/*! |
| 1613 |
Sets the default \a font to use in the document layout. |
| 1614 |
*/ |
| 1615 |
void QTextDocument::setDefaultFont(const QFont &font) |
| 1616 |
{ |
| 1617 |
Q_D(QTextDocument); |
| 1618 |
d->setDefaultFont(font); |
| 1619 |
if (d->lout) |
| 1620 |
d->lout->documentChanged(0, 0, d->length()); |
| 1621 |
} |
| 1622 |
|
| 1623 |
/*! |
| 1624 |
Returns the default font to be used in the document layout. |
| 1625 |
*/ |
| 1626 |
QFont QTextDocument::defaultFont() const |
| 1627 |
{ |
| 1628 |
Q_D(const QTextDocument); |
| 1629 |
return d->defaultFont(); |
| 1630 |
} |
| 1631 |
|
| 1632 |
/*! |
| 1633 |
\fn QTextDocument::modificationChanged(bool changed) |
| 1634 |
|
| 1635 |
This signal is emitted whenever the content of the document |
| 1636 |
changes in a way that affects the modification state. If \a |
| 1637 |
changed is true, the document has been modified; otherwise it is |
| 1638 |
false. |
| 1639 |
|
| 1640 |
For example, calling setModified(false) on a document and then |
| 1641 |
inserting text causes the signal to get emitted. If you undo that |
| 1642 |
operation, causing the document to return to its original |
| 1643 |
unmodified state, the signal will get emitted again. |
| 1644 |
*/ |
| 1645 |
|
| 1646 |
/*! |
| 1647 |
\property QTextDocument::modified |
| 1648 |
\brief whether the document has been modified by the user |
| 1649 |
|
| 1650 |
By default, this property is false. |
| 1651 |
|
| 1652 |
\sa modificationChanged() |
| 1653 |
*/ |
| 1654 |
|
| 1655 |
bool QTextDocument::isModified() const |
| 1656 |
{ |
| 1657 |
return docHandle()->isModified(); |
| 1658 |
} |
| 1659 |
|
| 1660 |
void QTextDocument::setModified(bool m) |
| 1661 |
{ |
| 1662 |
docHandle()->setModified(m); |
| 1663 |
} |
| 1664 |
|
| 1665 |
#ifndef QT_NO_PRINTER |
| 1666 |
static void printPage(int index, QPainter *painter, const QTextDocument *doc, const QRectF &body, const QPointF &pageNumberPos) |
| 1667 |
{ |
| 1668 |
painter->save(); |
| 1669 |
painter->translate(body.left(), body.top() - (index - 1) * body.height()); |
| 1670 |
QRectF view(0, (index - 1) * body.height(), body.width(), body.height()); |
| 1671 |
|
| 1672 |
QAbstractTextDocumentLayout *layout = doc->documentLayout(); |
| 1673 |
QAbstractTextDocumentLayout::PaintContext ctx; |
| 1674 |
|
| 1675 |
painter->setClipRect(view); |
| 1676 |
ctx.clip = view; |
| 1677 |
|
| 1678 |
// don't use the system palette text as default text color, on HP/UX |
| 1679 |
// for example that's white, and white text on white paper doesn't |
| 1680 |
// look that nice |
| 1681 |
ctx.palette.setColor(QPalette::Text, Qt::black); |
| 1682 |
|
| 1683 |
layout->draw(painter, ctx); |
| 1684 |
|
| 1685 |
if (!pageNumberPos.isNull()) { |
| 1686 |
painter->setClipping(false); |
| 1687 |
painter->setFont(QFont(doc->defaultFont())); |
| 1688 |
const QString pageString = QString::number(index); |
| 1689 |
|
| 1690 |
painter->drawText(qRound(pageNumberPos.x() - painter->fontMetrics().width(pageString)), |
| 1691 |
qRound(pageNumberPos.y() + view.top()), |
| 1692 |
pageString); |
| 1693 |
} |
| 1694 |
|
| 1695 |
painter->restore(); |
| 1696 |
} |
| 1697 |
|
| 1698 |
/*! |
| 1699 |
Prints the document to the given \a printer. The QPrinter must be |
| 1700 |
set up before being used with this function. |
| 1701 |
|
| 1702 |
This is only a convenience method to print the whole document to the printer. |
| 1703 |
|
| 1704 |
If the document is already paginated through a specified height in the pageSize() |
| 1705 |
property it is printed as-is. |
| 1706 |
|
| 1707 |
If the document is not paginated, like for example a document used in a QTextEdit, |
| 1708 |
then a temporary copy of the document is created and the copy is broken into |
| 1709 |
multiple pages according to the size of the QPrinter's paperRect(). By default |
| 1710 |
a 2 cm margin is set around the document contents. In addition the current page |
| 1711 |
number is printed at the bottom of each page. |
| 1712 |
|
| 1713 |
Note that QPrinter::Selection is not supported as print range with this function since |
| 1714 |
the selection is a property of QTextCursor. If you have a QTextEdit associated with |
| 1715 |
your QTextDocument then you can use QTextEdit's print() function because QTextEdit has |
| 1716 |
access to the user's selection. |
| 1717 |
|
| 1718 |
\sa QTextEdit::print() |
| 1719 |
*/ |
| 1720 |
|
| 1721 |
void QTextDocument::print(QPrinter *printer) const |
| 1722 |
{ |
| 1723 |
Q_D(const QTextDocument); |
| 1724 |
|
| 1725 |
if (!printer || !printer->isValid()) |
| 1726 |
return; |
| 1727 |
|
| 1728 |
if (!d->title.isEmpty()) |
| 1729 |
printer->setDocName(d->title); |
| 1730 |
|
| 1731 |
bool documentPaginated = d->pageSize.isValid() && !d->pageSize.isNull() |
| 1732 |
&& d->pageSize.height() != INT_MAX; |
| 1733 |
|
| 1734 |
if (!documentPaginated && !printer->fullPage() && !printer->d_func()->hasCustomPageMargins) |
| 1735 |
printer->setPageMargins(23.53, 23.53, 23.53, 23.53, QPrinter::Millimeter); |
| 1736 |
|
| 1737 |
QPainter p(printer); |
| 1738 |
|
| 1739 |
// Check that there is a valid device to print to. |
| 1740 |
if (!p.isActive()) |
| 1741 |
return; |
| 1742 |
|
| 1743 |
const QTextDocument *doc = this; |
| 1744 |
QScopedPointer<QTextDocument> clonedDoc; |
| 1745 |
(void)doc->documentLayout(); // make sure that there is a layout |
| 1746 |
|
| 1747 |
QRectF body = QRectF(QPointF(0, 0), d->pageSize); |
| 1748 |
QPointF pageNumberPos; |
| 1749 |
|
| 1750 |
if (documentPaginated) { |
| 1751 |
qreal sourceDpiX = qt_defaultDpi(); |
| 1752 |
qreal sourceDpiY = sourceDpiX; |
| 1753 |
|
| 1754 |
QPaintDevice *dev = doc->documentLayout()->paintDevice(); |
| 1755 |
if (dev) { |
| 1756 |
sourceDpiX = dev->logicalDpiX(); |
| 1757 |
sourceDpiY = dev->logicalDpiY(); |
| 1758 |
} |
| 1759 |
|
| 1760 |
const qreal dpiScaleX = qreal(printer->logicalDpiX()) / sourceDpiX; |
| 1761 |
const qreal dpiScaleY = qreal(printer->logicalDpiY()) / sourceDpiY; |
| 1762 |
|
| 1763 |
// scale to dpi |
| 1764 |
p.scale(dpiScaleX, dpiScaleY); |
| 1765 |
|
| 1766 |
QSizeF scaledPageSize = d->pageSize; |
| 1767 |
scaledPageSize.rwidth() *= dpiScaleX; |
| 1768 |
scaledPageSize.rheight() *= dpiScaleY; |
| 1769 |
|
| 1770 |
const QSizeF printerPageSize(printer->pageRect().size()); |
| 1771 |
|
| 1772 |
// scale to page |
| 1773 |
p.scale(printerPageSize.width() / scaledPageSize.width(), |
| 1774 |
printerPageSize.height() / scaledPageSize.height()); |
| 1775 |
} else { |
| 1776 |
doc = clone(const_cast<QTextDocument *>(this)); |
| 1777 |
clonedDoc.reset(const_cast<QTextDocument *>(doc)); |
| 1778 |
|
| 1779 |
for (QTextBlock srcBlock = firstBlock(), dstBlock = clonedDoc->firstBlock(); |
| 1780 |
srcBlock.isValid() && dstBlock.isValid(); |
| 1781 |
srcBlock = srcBlock.next(), dstBlock = dstBlock.next()) { |
| 1782 |
dstBlock.layout()->setAdditionalFormats(srcBlock.layout()->additionalFormats()); |
| 1783 |
} |
| 1784 |
|
| 1785 |
QAbstractTextDocumentLayout *layout = doc->documentLayout(); |
| 1786 |
layout->setPaintDevice(p.device()); |
| 1787 |
|
| 1788 |
// copy the custom object handlers |
| 1789 |
layout->d_func()->handlers = documentLayout()->d_func()->handlers; |
| 1790 |
|
| 1791 |
int dpiy = p.device()->logicalDpiY(); |
| 1792 |
int margin = 0; |
| 1793 |
if (printer->fullPage() && !printer->d_func()->hasCustomPageMargins) { |
| 1794 |
// for compatibility |
| 1795 |
margin = (int) ((2/2.54)*dpiy); // 2 cm margins |
| 1796 |
QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); |
| 1797 |
fmt.setMargin(margin); |
| 1798 |
doc->rootFrame()->setFrameFormat(fmt); |
| 1799 |
} |
| 1800 |
|
| 1801 |
QRectF pageRect(printer->pageRect()); |
| 1802 |
body = QRectF(0, 0, pageRect.width(), pageRect.height()); |
| 1803 |
pageNumberPos = QPointF(body.width() - margin, |
| 1804 |
body.height() - margin |
| 1805 |
+ QFontMetrics(doc->defaultFont(), p.device()).ascent() |
| 1806 |
+ 5 * dpiy / 72.0); |
| 1807 |
clonedDoc->setPageSize(body.size()); |
| 1808 |
} |
| 1809 |
|
| 1810 |
int docCopies; |
| 1811 |
int pageCopies; |
| 1812 |
if (printer->collateCopies() == true){ |
| 1813 |
docCopies = 1; |
| 1814 |
pageCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount(); |
| 1815 |
} else { |
| 1816 |
docCopies = printer->supportsMultipleCopies() ? 1 : printer->copyCount(); |
| 1817 |
pageCopies = 1; |
| 1818 |
} |
| 1819 |
|
| 1820 |
int fromPage = printer->fromPage(); |
| 1821 |
int toPage = printer->toPage(); |
| 1822 |
bool ascending = true; |
| 1823 |
|
| 1824 |
if (fromPage == 0 && toPage == 0) { |
| 1825 |
fromPage = 1; |
| 1826 |
toPage = doc->pageCount(); |
| 1827 |
} |
| 1828 |
// paranoia check |
| 1829 |
fromPage = qMax(1, fromPage); |
| 1830 |
toPage = qMin(doc->pageCount(), toPage); |
| 1831 |
|
| 1832 |
if (toPage < fromPage) { |
| 1833 |
// if the user entered a page range outside the actual number |
| 1834 |
// of printable pages, just return |
| 1835 |
return; |
| 1836 |
} |
| 1837 |
|
| 1838 |
if (printer->pageOrder() == QPrinter::LastPageFirst) { |
| 1839 |
int tmp = fromPage; |
| 1840 |
fromPage = toPage; |
| 1841 |
toPage = tmp; |
| 1842 |
ascending = false; |
| 1843 |
} |
| 1844 |
|
| 1845 |
for (int i = 0; i < docCopies; ++i) { |
| 1846 |
|
| 1847 |
int page = fromPage; |
| 1848 |
while (true) { |
| 1849 |
for (int j = 0; j < pageCopies; ++j) { |
| 1850 |
if (printer->printerState() == QPrinter::Aborted |
| 1851 |
|| printer->printerState() == QPrinter::Error) |
| 1852 |
return; |
| 1853 |
printPage(page, &p, doc, body, pageNumberPos); |
| 1854 |
if (j < pageCopies - 1) |
| 1855 |
printer->newPage(); |
| 1856 |
} |
| 1857 |
|
| 1858 |
if (page == toPage) |
| 1859 |
break; |
| 1860 |
|
| 1861 |
if (ascending) |
| 1862 |
++page; |
| 1863 |
else |
| 1864 |
--page; |
| 1865 |
|
| 1866 |
printer->newPage(); |
| 1867 |
} |
| 1868 |
|
| 1869 |
if ( i < docCopies - 1) |
| 1870 |
printer->newPage(); |
| 1871 |
} |
| 1872 |
} |
| 1873 |
#endif |
| 1874 |
|
| 1875 |
/*! |
| 1876 |
\enum QTextDocument::ResourceType |
| 1877 |
|
| 1878 |
This enum describes the types of resources that can be loaded by |
| 1879 |
QTextDocument's loadResource() function. |
| 1880 |
|
| 1881 |
\value HtmlResource The resource contains HTML. |
| 1882 |
\value ImageResource The resource contains image data. |
| 1883 |
Currently supported data types are QVariant::Pixmap and |
| 1884 |
QVariant::Image. If the corresponding variant is of type |
| 1885 |
QVariant::ByteArray then Qt attempts to load the image using |
| 1886 |
QImage::loadFromData. QVariant::Icon is currently not supported. |
| 1887 |
The icon needs to be converted to one of the supported types first, |
| 1888 |
for example using QIcon::pixmap. |
| 1889 |
\value StyleSheetResource The resource contains CSS. |
| 1890 |
\value UserResource The first available value for user defined |
| 1891 |
resource types. |
| 1892 |
|
| 1893 |
\sa loadResource() |
| 1894 |
*/ |
| 1895 |
|
| 1896 |
/*! |
| 1897 |
Returns data of the specified \a type from the resource with the |
| 1898 |
given \a name. |
| 1899 |
|
| 1900 |
This function is called by the rich text engine to request data that isn't |
| 1901 |
directly stored by QTextDocument, but still associated with it. For example, |
| 1902 |
images are referenced indirectly by the name attribute of a QTextImageFormat |
| 1903 |
object. |
| 1904 |
|
| 1905 |
Resources are cached internally in the document. If a resource can |
| 1906 |
not be found in the cache, loadResource is called to try to load |
| 1907 |
the resource. loadResource should then use addResource to add the |
| 1908 |
resource to the cache. |
| 1909 |
|
| 1910 |
\sa QTextDocument::ResourceType |
| 1911 |
*/ |
| 1912 |
QVariant QTextDocument::resource(int type, const QUrl &name) const |
| 1913 |
{ |
| 1914 |
Q_D(const QTextDocument); |
| 1915 |
QVariant r = d->resources.value(name); |
| 1916 |
if (!r.isValid()) { |
| 1917 |
r = d->cachedResources.value(name); |
| 1918 |
if (!r.isValid()) |
| 1919 |
r = const_cast<QTextDocument *>(this)->loadResource(type, name); |
| 1920 |
} |
| 1921 |
return r; |
| 1922 |
} |
| 1923 |
|
| 1924 |
/*! |
| 1925 |
Adds the resource \a resource to the resource cache, using \a |
| 1926 |
type and \a name as identifiers. \a type should be a value from |
| 1927 |
QTextDocument::ResourceType. |
| 1928 |
|
| 1929 |
For example, you can add an image as a resource in order to reference it |
| 1930 |
from within the document: |
| 1931 |
|
| 1932 |
\snippet snippets/textdocument-resources/main.cpp Adding a resource |
| 1933 |
|
| 1934 |
The image can be inserted into the document using the QTextCursor API: |
| 1935 |
|
| 1936 |
\snippet snippets/textdocument-resources/main.cpp Inserting an image with a cursor |
| 1937 |
|
| 1938 |
Alternatively, you can insert images using the HTML \c img tag: |
| 1939 |
|
| 1940 |
\snippet snippets/textdocument-resources/main.cpp Inserting an image using HTML |
| 1941 |
*/ |
| 1942 |
void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource) |
| 1943 |
{ |
| 1944 |
Q_UNUSED(type); |
| 1945 |
Q_D(QTextDocument); |
| 1946 |
d->resources.insert(name, resource); |
| 1947 |
} |
| 1948 |
|
| 1949 |
/*! |
| 1950 |
Loads data of the specified \a type from the resource with the |
| 1951 |
given \a name. |
| 1952 |
|
| 1953 |
This function is called by the rich text engine to request data that isn't |
| 1954 |
directly stored by QTextDocument, but still associated with it. For example, |
| 1955 |
images are referenced indirectly by the name attribute of a QTextImageFormat |
| 1956 |
object. |
| 1957 |
|
| 1958 |
When called by Qt, \a type is one of the values of |
| 1959 |
QTextDocument::ResourceType. |
| 1960 |
|
| 1961 |
If the QTextDocument is a child object of a QTextEdit, QTextBrowser, |
| 1962 |
or a QTextDocument itself then the default implementation tries |
| 1963 |
to retrieve the data from the parent. |
| 1964 |
*/ |
| 1965 |
QVariant QTextDocument::loadResource(int type, const QUrl &name) |
| 1966 |
{ |
| 1967 |
Q_D(QTextDocument); |
| 1968 |
QVariant r; |
| 1969 |
|
| 1970 |
QTextDocument *doc = qobject_cast<QTextDocument *>(parent()); |
| 1971 |
if (doc) { |
| 1972 |
r = doc->loadResource(type, name); |
| 1973 |
} |
| 1974 |
#ifndef QT_NO_TEXTEDIT |
| 1975 |
else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent())) { |
| 1976 |
QUrl resolvedName = edit->d_func()->resolveUrl(name); |
| 1977 |
r = edit->loadResource(type, resolvedName); |
| 1978 |
} |
| 1979 |
#endif |
| 1980 |
#ifndef QT_NO_TEXTCONTROL |
| 1981 |
else if (QTextControl *control = qobject_cast<QTextControl *>(parent())) { |
| 1982 |
r = control->loadResource(type, name); |
| 1983 |
} |
| 1984 |
#endif |
| 1985 |
|
| 1986 |
// handle data: URLs |
| 1987 |
if (r.isNull() && name.scheme().compare(QLatin1String("data"), Qt::CaseInsensitive) == 0) |
| 1988 |
r = qDecodeDataUrl(name).second; |
| 1989 |
|
| 1990 |
// if resource was not loaded try to load it here |
| 1991 |
if (!doc && r.isNull() && name.isRelative()) { |
| 1992 |
QUrl currentURL = d->url; |
| 1993 |
QUrl resourceUrl = name; |
| 1994 |
|
| 1995 |
// For the second case QUrl can merge "#someanchor" with "foo.html" |
| 1996 |
// correctly to "foo.html#someanchor" |
| 1997 |
if (!(currentURL.isRelative() |
| 1998 |
|| (currentURL.scheme() == QLatin1String("file") |
| 1999 |
&& !QFileInfo(currentURL.toLocalFile()).isAbsolute())) |
| 2000 |
|| (name.hasFragment() && name.path().isEmpty())) { |
| 2001 |
resourceUrl = currentURL.resolved(name); |
| 2002 |
} else { |
| 2003 |
// this is our last resort when current url and new url are both relative |
| 2004 |
// we try to resolve against the current working directory in the local |
| 2005 |
// file system. |
| 2006 |
QFileInfo fi(currentURL.toLocalFile()); |
| 2007 |
if (fi.exists()) { |
| 2008 |
resourceUrl = |
| 2009 |
QUrl::fromLocalFile(fi.absolutePath() + QDir::separator()).resolved(name); |
| 2010 |
} else if (currentURL.isEmpty()) { |
| 2011 |
resourceUrl.setScheme(QLatin1String("file")); |
| 2012 |
} |
| 2013 |
} |
| 2014 |
|
| 2015 |
QString s = resourceUrl.toLocalFile(); |
| 2016 |
QFile f(s); |
| 2017 |
if (!s.isEmpty() && f.open(QFile::ReadOnly)) { |
| 2018 |
r = f.readAll(); |
| 2019 |
f.close(); |
| 2020 |
} |
| 2021 |
} |
| 2022 |
|
| 2023 |
if (!r.isNull()) { |
| 2024 |
if (type == ImageResource && r.type() == QVariant::ByteArray) { |
| 2025 |
if (qApp->thread() != QThread::currentThread()) { |
| 2026 |
// must use images in non-GUI threads |
| 2027 |
QImage image; |
| 2028 |
image.loadFromData(r.toByteArray()); |
| 2029 |
if (!image.isNull()) |
| 2030 |
r = image; |
| 2031 |
} else { |
| 2032 |
QPixmap pm; |
| 2033 |
pm.loadFromData(r.toByteArray()); |
| 2034 |
if (!pm.isNull()) |
| 2035 |
r = pm; |
| 2036 |
} |
| 2037 |
} |
| 2038 |
d->cachedResources.insert(name, r); |
| 2039 |
} |
| 2040 |
return r; |
| 2041 |
} |
| 2042 |
|
| 2043 |
static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to) |
| 2044 |
{ |
| 2045 |
QTextFormat diff = to; |
| 2046 |
|
| 2047 |
const QMap<int, QVariant> props = to.properties(); |
| 2048 |
for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end(); |
| 2049 |
it != end; ++it) |
| 2050 |
if (it.value() == from.property(it.key())) |
| 2051 |
diff.clearProperty(it.key()); |
| 2052 |
|
| 2053 |
return diff; |
| 2054 |
} |
| 2055 |
|
| 2056 |
QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc) |
| 2057 |
: doc(_doc), fragmentMarkers(false) |
| 2058 |
{ |
| 2059 |
const QFont defaultFont = doc->defaultFont(); |
| 2060 |
defaultCharFormat.setFont(defaultFont); |
| 2061 |
// don't export those for the default font since we cannot turn them off with CSS |
| 2062 |
defaultCharFormat.clearProperty(QTextFormat::FontUnderline); |
| 2063 |
defaultCharFormat.clearProperty(QTextFormat::FontOverline); |
| 2064 |
defaultCharFormat.clearProperty(QTextFormat::FontStrikeOut); |
| 2065 |
defaultCharFormat.clearProperty(QTextFormat::TextUnderlineStyle); |
| 2066 |
} |
| 2067 |
|
| 2068 |
/*! |
| 2069 |
Returns the document in HTML format. The conversion may not be |
| 2070 |
perfect, especially for complex documents, due to the limitations |
| 2071 |
of HTML. |
| 2072 |
*/ |
| 2073 |
QString QTextHtmlExporter::toHtml(const QByteArray &encoding, ExportMode mode) |
| 2074 |
{ |
| 2075 |
html = QLatin1String("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" " |
| 2076 |
"\"http://www.w3.org/TR/REC-html40/strict.dtd\">\n" |
| 2077 |
"<html><head><meta name=\"qrichtext\" content=\"1\" />"); |
| 2078 |
html.reserve(doc->docHandle()->length()); |
| 2079 |
|
| 2080 |
fragmentMarkers = (mode == ExportFragment); |
| 2081 |
|
| 2082 |
if (!encoding.isEmpty()) |
| 2083 |
html += QString::fromLatin1("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding)); |
| 2084 |
|
| 2085 |
QString title = doc->metaInformation(QTextDocument::DocumentTitle); |
| 2086 |
if (!title.isEmpty()) |
| 2087 |
html += QString::fromLatin1("<title>") + title + QString::fromLatin1("</title>"); |
| 2088 |
html += QLatin1String("<style type=\"text/css\">\n"); |
| 2089 |
html += QLatin1String("p, li { white-space: pre-wrap; }\n"); |
| 2090 |
html += QLatin1String("</style>"); |
| 2091 |
html += QLatin1String("</head><body"); |
| 2092 |
|
| 2093 |
if (mode == ExportEntireDocument) { |
| 2094 |
html += QLatin1String(" style=\""); |
| 2095 |
|
| 2096 |
emitFontFamily(defaultCharFormat.fontFamily()); |
| 2097 |
|
| 2098 |
if (defaultCharFormat.hasProperty(QTextFormat::FontPointSize)) { |
| 2099 |
html += QLatin1String(" font-size:"); |
| 2100 |
html += QString::number(defaultCharFormat.fontPointSize()); |
| 2101 |
html += QLatin1String("pt;"); |
| 2102 |
} else if (defaultCharFormat.hasProperty(QTextFormat::FontPixelSize)) { |
| 2103 |
html += QLatin1String(" font-size:"); |
| 2104 |
html += QString::number(defaultCharFormat.intProperty(QTextFormat::FontPixelSize)); |
| 2105 |
html += QLatin1String("px;"); |
| 2106 |
} |
| 2107 |
|
| 2108 |
html += QLatin1String(" font-weight:"); |
| 2109 |
html += QString::number(defaultCharFormat.fontWeight() * 8); |
| 2110 |
html += QLatin1Char(';'); |
| 2111 |
|
| 2112 |
html += QLatin1String(" font-style:"); |
| 2113 |
html += (defaultCharFormat.fontItalic() ? QLatin1String("italic") : QLatin1String("normal")); |
| 2114 |
html += QLatin1Char(';'); |
| 2115 |
|
| 2116 |
// do not set text-decoration on the default font since those values are /always/ propagated |
| 2117 |
// and cannot be turned off with CSS |
| 2118 |
|
| 2119 |
html += QLatin1Char('\"'); |
| 2120 |
|
| 2121 |
const QTextFrameFormat fmt = doc->rootFrame()->frameFormat(); |
| 2122 |
emitBackgroundAttribute(fmt); |
| 2123 |
|
| 2124 |
} else { |
| 2125 |
defaultCharFormat = QTextCharFormat(); |
| 2126 |
} |
| 2127 |
html += QLatin1Char('>'); |
| 2128 |
|
| 2129 |
QTextFrameFormat rootFmt = doc->rootFrame()->frameFormat(); |
| 2130 |
rootFmt.clearProperty(QTextFormat::BackgroundBrush); |
| 2131 |
|
| 2132 |
QTextFrameFormat defaultFmt; |
| 2133 |
defaultFmt.setMargin(doc->documentMargin()); |
| 2134 |
|
| 2135 |
if (rootFmt == defaultFmt) |
| 2136 |
emitFrame(doc->rootFrame()->begin()); |
| 2137 |
else |
| 2138 |
emitTextFrame(doc->rootFrame()); |
| 2139 |
|
| 2140 |
html += QLatin1String("</body></html>"); |
| 2141 |
return html; |
| 2142 |
} |
| 2143 |
|
| 2144 |
void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value) |
| 2145 |
{ |
| 2146 |
html += QLatin1Char(' '); |
| 2147 |
html += QLatin1String(attribute); |
| 2148 |
html += QLatin1String("=\""); |
| 2149 |
html += Qt::escape(value); |
| 2150 |
html += QLatin1Char('"'); |
| 2151 |
} |
| 2152 |
|
| 2153 |
bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format) |
| 2154 |
{ |
| 2155 |
bool attributesEmitted = false; |
| 2156 |
|
| 2157 |
{ |
| 2158 |
const QString family = format.fontFamily(); |
| 2159 |
if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) { |
| 2160 |
emitFontFamily(family); |
| 2161 |
attributesEmitted = true; |
| 2162 |
} |
| 2163 |
} |
| 2164 |
|
| 2165 |
if (format.hasProperty(QTextFormat::FontPointSize) |
| 2166 |
&& format.fontPointSize() != defaultCharFormat.fontPointSize()) { |
| 2167 |
html += QLatin1String(" font-size:"); |
| 2168 |
html += QString::number(format.fontPointSize()); |
| 2169 |
html += QLatin1String("pt;"); |
| 2170 |
attributesEmitted = true; |
| 2171 |
} else if (format.hasProperty(QTextFormat::FontSizeAdjustment)) { |
| 2172 |
static const char * const sizeNames[] = { |
| 2173 |
"small", "medium", "large", "x-large", "xx-large" |
| 2174 |
}; |
| 2175 |
const char *name = 0; |
| 2176 |
const int idx = format.intProperty(QTextFormat::FontSizeAdjustment) + 1; |
| 2177 |
if (idx >= 0 && idx <= 4) { |
| 2178 |
name = sizeNames[idx]; |
| 2179 |
} |
| 2180 |
if (name) { |
| 2181 |
html += QLatin1String(" font-size:"); |
| 2182 |
html += QLatin1String(name); |
| 2183 |
html += QLatin1Char(';'); |
| 2184 |
attributesEmitted = true; |
| 2185 |
} |
| 2186 |
} else if (format.hasProperty(QTextFormat::FontPixelSize)) { |
| 2187 |
html += QLatin1String(" font-size:"); |
| 2188 |
html += QString::number(format.intProperty(QTextFormat::FontPixelSize)); |
| 2189 |
html += QLatin1String("px;"); |
| 2190 |
} |
| 2191 |
|
| 2192 |
if (format.hasProperty(QTextFormat::FontWeight) |
| 2193 |
&& format.fontWeight() != defaultCharFormat.fontWeight()) { |
| 2194 |
html += QLatin1String(" font-weight:"); |
| 2195 |
html += QString::number(format.fontWeight() * 8); |
| 2196 |
html += QLatin1Char(';'); |
| 2197 |
attributesEmitted = true; |
| 2198 |
} |
| 2199 |
|
| 2200 |
if (format.hasProperty(QTextFormat::FontItalic) |
| 2201 |
&& format.fontItalic() != defaultCharFormat.fontItalic()) { |
| 2202 |
html += QLatin1String(" font-style:"); |
| 2203 |
html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal")); |
| 2204 |
html += QLatin1Char(';'); |
| 2205 |
attributesEmitted = true; |
| 2206 |
} |
| 2207 |
|
| 2208 |
QLatin1String decorationTag(" text-decoration:"); |
| 2209 |
html += decorationTag; |
| 2210 |
bool hasDecoration = false; |
| 2211 |
bool atLeastOneDecorationSet = false; |
| 2212 |
|
| 2213 |
if ((format.hasProperty(QTextFormat::FontUnderline) || format.hasProperty(QTextFormat::TextUnderlineStyle)) |
| 2214 |
&& format.fontUnderline() != defaultCharFormat.fontUnderline()) { |
| 2215 |
hasDecoration = true; |
| 2216 |
if (format.fontUnderline()) { |
| 2217 |
html += QLatin1String(" underline"); |
| 2218 |
atLeastOneDecorationSet = true; |
| 2219 |
} |
| 2220 |
} |
| 2221 |
|
| 2222 |
if (format.hasProperty(QTextFormat::FontOverline) |
| 2223 |
&& format.fontOverline() != defaultCharFormat.fontOverline()) { |
| 2224 |
hasDecoration = true; |
| 2225 |
if (format.fontOverline()) { |
| 2226 |
html += QLatin1String(" overline"); |
| 2227 |
atLeastOneDecorationSet = true; |
| 2228 |
} |
| 2229 |
} |
| 2230 |
|
| 2231 |
if (format.hasProperty(QTextFormat::FontStrikeOut) |
| 2232 |
&& format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) { |
| 2233 |
hasDecoration = true; |
| 2234 |
if (format.fontStrikeOut()) { |
| 2235 |
html += QLatin1String(" line-through"); |
| 2236 |
atLeastOneDecorationSet = true; |
| 2237 |
} |
| 2238 |
} |
| 2239 |
|
| 2240 |
if (hasDecoration) { |
| 2241 |
if (!atLeastOneDecorationSet) |
| 2242 |
html += QLatin1String("none"); |
| 2243 |
html += QLatin1Char(';'); |
| 2244 |
attributesEmitted = true; |
| 2245 |
} else { |
| 2246 |
html.chop(qstrlen(decorationTag.latin1())); |
| 2247 |
} |
| 2248 |
|
| 2249 |
if (format.foreground() != defaultCharFormat.foreground() |
| 2250 |
&& format.foreground().style() != Qt::NoBrush) { |
| 2251 |
html += QLatin1String(" color:"); |
| 2252 |
html += format.foreground().color().name(); |
| 2253 |
html += QLatin1Char(';'); |
| 2254 |
attributesEmitted = true; |
| 2255 |
} |
| 2256 |
|
| 2257 |
if (format.background() != defaultCharFormat.background() |
| 2258 |
&& format.background().style() == Qt::SolidPattern) { |
| 2259 |
html += QLatin1String(" background-color:"); |
| 2260 |
html += format.background().color().name(); |
| 2261 |
html += QLatin1Char(';'); |
| 2262 |
attributesEmitted = true; |
| 2263 |
} |
| 2264 |
|
| 2265 |
if (format.verticalAlignment() != defaultCharFormat.verticalAlignment() |
| 2266 |
&& format.verticalAlignment() != QTextCharFormat::AlignNormal) |
| 2267 |
{ |
| 2268 |
html += QLatin1String(" vertical-align:"); |
| 2269 |
|
| 2270 |
QTextCharFormat::VerticalAlignment valign = format.verticalAlignment(); |
| 2271 |
if (valign == QTextCharFormat::AlignSubScript) |
| 2272 |
html += QLatin1String("sub"); |
| 2273 |
else if (valign == QTextCharFormat::AlignSuperScript) |
| 2274 |
html += QLatin1String("super"); |
| 2275 |
else if (valign == QTextCharFormat::AlignMiddle) |
| 2276 |
html += QLatin1String("middle"); |
| 2277 |
else if (valign == QTextCharFormat::AlignTop) |
| 2278 |
html += QLatin1String("top"); |
| 2279 |
else if (valign == QTextCharFormat::AlignBottom) |
| 2280 |
html += QLatin1String("bottom"); |
| 2281 |
|
| 2282 |
html += QLatin1Char(';'); |
| 2283 |
attributesEmitted = true; |
| 2284 |
} |
| 2285 |
|
| 2286 |
if (format.fontCapitalization() != QFont::MixedCase) { |
| 2287 |
const QFont::Capitalization caps = format.fontCapitalization(); |
| 2288 |
if (caps == QFont::AllUppercase) |
| 2289 |
html += QLatin1String(" text-transform:uppercase;"); |
| 2290 |
else if (caps == QFont::AllLowercase) |
| 2291 |
html += QLatin1String(" text-transform:lowercase;"); |
| 2292 |
else if (caps == QFont::SmallCaps) |
| 2293 |
html += QLatin1String(" font-variant:small-caps;"); |
| 2294 |
attributesEmitted = true; |
| 2295 |
} |
| 2296 |
|
| 2297 |
if (format.fontWordSpacing() != 0.0) { |
| 2298 |
html += QLatin1String(" word-spacing:"); |
| 2299 |
html += QString::number(format.fontWordSpacing()); |
| 2300 |
html += QLatin1String("px;"); |
| 2301 |
attributesEmitted = true; |
| 2302 |
} |
| 2303 |
|
| 2304 |
return attributesEmitted; |
| 2305 |
} |
| 2306 |
|
| 2307 |
void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length) |
| 2308 |
{ |
| 2309 |
if (length.type() == QTextLength::VariableLength) // default |
| 2310 |
return; |
| 2311 |
|
| 2312 |
html += QLatin1Char(' '); |
| 2313 |
html += QLatin1String(attribute); |
| 2314 |
html += QLatin1String("=\""); |
| 2315 |
html += QString::number(length.rawValue()); |
| 2316 |
|
| 2317 |
if (length.type() == QTextLength::PercentageLength) |
| 2318 |
html += QLatin1String("%\""); |
| 2319 |
else |
| 2320 |
html += QLatin1Char('\"'); |
| 2321 |
} |
| 2322 |
|
| 2323 |
void QTextHtmlExporter::emitAlignment(Qt::Alignment align) |
| 2324 |
{ |
| 2325 |
if (align & Qt::AlignLeft) |
| 2326 |
return; |
| 2327 |
else if (align & Qt::AlignRight) |
| 2328 |
html += QLatin1String(" align=\"right\""); |
| 2329 |
else if (align & Qt::AlignHCenter) |
| 2330 |
html += QLatin1String(" align=\"center\""); |
| 2331 |
else if (align & Qt::AlignJustify) |
| 2332 |
html += QLatin1String(" align=\"justify\""); |
| 2333 |
} |
| 2334 |
|
| 2335 |
void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos, StyleMode mode) |
| 2336 |
{ |
| 2337 |
if (pos == QTextFrameFormat::InFlow) |
| 2338 |
return; |
| 2339 |
|
| 2340 |
if (mode == EmitStyleTag) |
| 2341 |
html += QLatin1String(" style=\"float:"); |
| 2342 |
else |
| 2343 |
html += QLatin1String(" float:"); |
| 2344 |
|
| 2345 |
if (pos == QTextFrameFormat::FloatLeft) |
| 2346 |
html += QLatin1String(" left;"); |
| 2347 |
else if (pos == QTextFrameFormat::FloatRight) |
| 2348 |
html += QLatin1String(" right;"); |
| 2349 |
else |
| 2350 |
Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type"); |
| 2351 |
|
| 2352 |
if (mode == EmitStyleTag) |
| 2353 |
html += QLatin1Char('\"'); |
| 2354 |
} |
| 2355 |
|
| 2356 |
void QTextHtmlExporter::emitBorderStyle(QTextFrameFormat::BorderStyle style) |
| 2357 |
{ |
| 2358 |
Q_ASSERT(style <= QTextFrameFormat::BorderStyle_Outset); |
| 2359 |
|
| 2360 |
html += QLatin1String(" border-style:"); |
| 2361 |
|
| 2362 |
switch (style) { |
| 2363 |
case QTextFrameFormat::BorderStyle_None: |
| 2364 |
html += QLatin1String("none"); |
| 2365 |
break; |
| 2366 |
case QTextFrameFormat::BorderStyle_Dotted: |
| 2367 |
html += QLatin1String("dotted"); |
| 2368 |
break; |
| 2369 |
case QTextFrameFormat::BorderStyle_Dashed: |
| 2370 |
html += QLatin1String("dashed"); |
| 2371 |
break; |
| 2372 |
case QTextFrameFormat::BorderStyle_Solid: |
| 2373 |
html += QLatin1String("solid"); |
| 2374 |
break; |
| 2375 |
case QTextFrameFormat::BorderStyle_Double: |
| 2376 |
html += QLatin1String("double"); |
| 2377 |
break; |
| 2378 |
case QTextFrameFormat::BorderStyle_DotDash: |
| 2379 |
html += QLatin1String("dot-dash"); |
| 2380 |
break; |
| 2381 |
case QTextFrameFormat::BorderStyle_DotDotDash: |
| 2382 |
html += QLatin1String("dot-dot-dash"); |
| 2383 |
break; |
| 2384 |
case QTextFrameFormat::BorderStyle_Groove: |
| 2385 |
html += QLatin1String("groove"); |
| 2386 |
break; |
| 2387 |
case QTextFrameFormat::BorderStyle_Ridge: |
| 2388 |
html += QLatin1String("ridge"); |
| 2389 |
break; |
| 2390 |
case QTextFrameFormat::BorderStyle_Inset: |
| 2391 |
html += QLatin1String("inset"); |
| 2392 |
break; |
| 2393 |
case QTextFrameFormat::BorderStyle_Outset: |
| 2394 |
html += QLatin1String("outset"); |
| 2395 |
break; |
| 2396 |
default: |
| 2397 |
Q_ASSERT(false); |
| 2398 |
break; |
| 2399 |
}; |
| 2400 |
|
| 2401 |
html += QLatin1Char(';'); |
| 2402 |
} |
| 2403 |
|
| 2404 |
void QTextHtmlExporter::emitPageBreakPolicy(QTextFormat::PageBreakFlags policy) |
| 2405 |
{ |
| 2406 |
if (policy & QTextFormat::PageBreak_AlwaysBefore) |
| 2407 |
html += QLatin1String(" page-break-before:always;"); |
| 2408 |
|
| 2409 |
if (policy & QTextFormat::PageBreak_AlwaysAfter) |
| 2410 |
html += QLatin1String(" page-break-after:always;"); |
| 2411 |
} |
| 2412 |
|
| 2413 |
void QTextHtmlExporter::emitFontFamily(const QString &family) |
| 2414 |
{ |
| 2415 |
html += QLatin1String(" font-family:"); |
| 2416 |
|
| 2417 |
QLatin1String quote("\'"); |
| 2418 |
if (family.contains(QLatin1Char('\''))) |
| 2419 |
quote = QLatin1String("""); |
| 2420 |
|
| 2421 |
html += quote; |
| 2422 |
html += Qt::escape(family); |
| 2423 |
html += quote; |
| 2424 |
html += QLatin1Char(';'); |
| 2425 |
} |
| 2426 |
|
| 2427 |
void QTextHtmlExporter::emitMargins(const QString &top, const QString &bottom, const QString &left, const QString &right) |
| 2428 |
{ |
| 2429 |
html += QLatin1String(" margin-top:"); |
| 2430 |
html += top; |
| 2431 |
html += QLatin1String("px;"); |
| 2432 |
|
| 2433 |
html += QLatin1String(" margin-bottom:"); |
| 2434 |
html += bottom; |
| 2435 |
html += QLatin1String("px;"); |
| 2436 |
|
| 2437 |
html += QLatin1String(" margin-left:"); |
| 2438 |
html += left; |
| 2439 |
html += QLatin1String("px;"); |
| 2440 |
|
| 2441 |
html += QLatin1String(" margin-right:"); |
| 2442 |
html += right; |
| 2443 |
html += QLatin1String("px;"); |
| 2444 |
} |
| 2445 |
|
| 2446 |
void QTextHtmlExporter::emitFragment(const QTextFragment &fragment) |
| 2447 |
{ |
| 2448 |
const QTextCharFormat format = fragment.charFormat(); |
| 2449 |
|
| 2450 |
bool closeAnchor = false; |
| 2451 |
|
| 2452 |
if (format.isAnchor()) { |
| 2453 |
const QString name = format.anchorName(); |
| 2454 |
if (!name.isEmpty()) { |
| 2455 |
html += QLatin1String("<a name=\""); |
| 2456 |
html += Qt::escape(name); |
| 2457 |
html += QLatin1String("\"></a>"); |
| 2458 |
} |
| 2459 |
const QString href = format.anchorHref(); |
| 2460 |
if (!href.isEmpty()) { |
| 2461 |
html += QLatin1String("<a href=\""); |
| 2462 |
html += Qt::escape(href); |
| 2463 |
html += QLatin1String("\">"); |
| 2464 |
closeAnchor = true; |
| 2465 |
} |
| 2466 |
} |
| 2467 |
|
| 2468 |
QString txt = fragment.text(); |
| 2469 |
const bool isObject = txt.contains(QChar::ObjectReplacementCharacter); |
| 2470 |
const bool isImage = isObject && format.isImageFormat(); |
| 2471 |
|
| 2472 |
QLatin1String styleTag("<span style=\""); |
| 2473 |
html += styleTag; |
| 2474 |
|
| 2475 |
bool attributesEmitted = false; |
| 2476 |
if (!isImage) |
| 2477 |
attributesEmitted = emitCharFormatStyle(format); |
| 2478 |
if (attributesEmitted) |
| 2479 |
html += QLatin1String("\">"); |
| 2480 |
else |
| 2481 |
html.chop(qstrlen(styleTag.latin1())); |
| 2482 |
|
| 2483 |
if (isObject) { |
| 2484 |
for (int i = 0; isImage && i < txt.length(); ++i) { |
| 2485 |
QTextImageFormat imgFmt = format.toImageFormat(); |
| 2486 |
|
| 2487 |
html += QLatin1String("<img"); |
| 2488 |
|
| 2489 |
if (imgFmt.hasProperty(QTextFormat::ImageName)) |
| 2490 |
emitAttribute("src", imgFmt.name()); |
| 2491 |
|
| 2492 |
if (imgFmt.hasProperty(QTextFormat::ImageWidth)) |
| 2493 |
emitAttribute("width", QString::number(imgFmt.width())); |
| 2494 |
|
| 2495 |
if (imgFmt.hasProperty(QTextFormat::ImageHeight)) |
| 2496 |
emitAttribute("height", QString::number(imgFmt.height())); |
| 2497 |
|
| 2498 |
if (imgFmt.verticalAlignment() == QTextCharFormat::AlignMiddle) |
| 2499 |
html += QLatin1String(" style=\"vertical-align: middle;\""); |
| 2500 |
else if (imgFmt.verticalAlignment() == QTextCharFormat::AlignTop) |
| 2501 |
html += QLatin1String(" style=\"vertical-align: top;\""); |
| 2502 |
|
| 2503 |
if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt))) |
| 2504 |
emitFloatStyle(imageFrame->frameFormat().position()); |
| 2505 |
|
| 2506 |
html += QLatin1String(" />"); |
| 2507 |
} |
| 2508 |
} else { |
| 2509 |
Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter)); |
| 2510 |
|
| 2511 |
txt = Qt::escape(txt); |
| 2512 |
|
| 2513 |
// split for [\n{LineSeparator}] |
| 2514 |
QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]"); |
| 2515 |
forcedLineBreakRegExp[3] = QChar::LineSeparator; |
| 2516 |
|
| 2517 |
const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp)); |
| 2518 |
for (int i = 0; i < lines.count(); ++i) { |
| 2519 |
if (i > 0) |
| 2520 |
html += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co. |
| 2521 |
html += lines.at(i); |
| 2522 |
} |
| 2523 |
} |
| 2524 |
|
| 2525 |
if (attributesEmitted) |
| 2526 |
html += QLatin1String("</span>"); |
| 2527 |
|
| 2528 |
if (closeAnchor) |
| 2529 |
html += QLatin1String("</a>"); |
| 2530 |
} |
| 2531 |
|
| 2532 |
static bool isOrderedList(int style) |
| 2533 |
{ |
| 2534 |
return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha |
| 2535 |
|| style == QTextListFormat::ListUpperAlpha |
| 2536 |
|| style == QTextListFormat::ListUpperRoman |
| 2537 |
|| style == QTextListFormat::ListLowerRoman |
| 2538 |
; |
| 2539 |
} |
| 2540 |
|
| 2541 |
void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block) |
| 2542 |
{ |
| 2543 |
QTextBlockFormat format = block.blockFormat(); |
| 2544 |
emitAlignment(format.alignment()); |
| 2545 |
|
| 2546 |
// assume default to not bloat the html too much |
| 2547 |
// html += QLatin1String(" dir='ltr'"); |
| 2548 |
if (block.textDirection() == Qt::RightToLeft) |
| 2549 |
html += QLatin1String(" dir='rtl'"); |
| 2550 |
|
| 2551 |
QLatin1String style(" style=\""); |
| 2552 |
html += style; |
| 2553 |
|
| 2554 |
const bool emptyBlock = block.begin().atEnd(); |
| 2555 |
if (emptyBlock) { |
| 2556 |
html += QLatin1String("-qt-paragraph-type:empty;"); |
| 2557 |
} |
| 2558 |
|
| 2559 |
emitMargins(QString::number(format.topMargin()), |
| 2560 |
QString::number(format.bottomMargin()), |
| 2561 |
QString::number(format.leftMargin()), |
| 2562 |
QString::number(format.rightMargin())); |
| 2563 |
|
| 2564 |
html += QLatin1String(" -qt-block-indent:"); |
| 2565 |
html += QString::number(format.indent()); |
| 2566 |
html += QLatin1Char(';'); |
| 2567 |
|
| 2568 |
html += QLatin1String(" text-indent:"); |
| 2569 |
html += QString::number(format.textIndent()); |
| 2570 |
html += QLatin1String("px;"); |
| 2571 |
|
| 2572 |
if (block.userState() != -1) { |
| 2573 |
html += QLatin1String(" -qt-user-state:"); |
| 2574 |
html += QString::number(block.userState()); |
| 2575 |
html += QLatin1Char(';'); |
| 2576 |
} |
| 2577 |
|
| 2578 |
emitPageBreakPolicy(format.pageBreakPolicy()); |
| 2579 |
|
| 2580 |
QTextCharFormat diff; |
| 2581 |
if (emptyBlock) { // only print character properties when we don't expect them to be repeated by actual text in the parag |
| 2582 |
const QTextCharFormat blockCharFmt = block.charFormat(); |
| 2583 |
diff = formatDifference(defaultCharFormat, blockCharFmt).toCharFormat(); |
| 2584 |
} |
| 2585 |
|
| 2586 |
diff.clearProperty(QTextFormat::BackgroundBrush); |
| 2587 |
if (format.hasProperty(QTextFormat::BackgroundBrush)) { |
| 2588 |
QBrush bg = format.background(); |
| 2589 |
if (bg.style() != Qt::NoBrush) |
| 2590 |
diff.setProperty(QTextFormat::BackgroundBrush, format.property(QTextFormat::BackgroundBrush)); |
| 2591 |
} |
| 2592 |
|
| 2593 |
if (!diff.properties().isEmpty()) |
| 2594 |
emitCharFormatStyle(diff); |
| 2595 |
|
| 2596 |
html += QLatin1Char('"'); |
| 2597 |
|
| 2598 |
} |
| 2599 |
|
| 2600 |
void QTextHtmlExporter::emitBlock(const QTextBlock &block) |
| 2601 |
{ |
| 2602 |
if (block.begin().atEnd()) { |
| 2603 |
// ### HACK, remove once QTextFrame::Iterator is fixed |
| 2604 |
int p = block.position(); |
| 2605 |
if (p > 0) |
| 2606 |
--p; |
| 2607 |
QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p); |
| 2608 |
QChar ch = doc->docHandle()->buffer().at(frag->stringPosition); |
| 2609 |
if (ch == QTextBeginningOfFrame |
| 2610 |
|| ch == QTextEndOfFrame) |
| 2611 |
return; |
| 2612 |
} |
| 2613 |
|
| 2614 |
html += QLatin1Char('\n'); |
| 2615 |
|
| 2616 |
// save and later restore, in case we 'change' the default format by |
| 2617 |
// emitting block char format information |
| 2618 |
QTextCharFormat oldDefaultCharFormat = defaultCharFormat; |
| 2619 |
|
| 2620 |
QTextList *list = block.textList(); |
| 2621 |
if (list) { |
| 2622 |
if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate |
| 2623 |
const QTextListFormat format = list->format(); |
| 2624 |
const int style = format.style(); |
| 2625 |
switch (style) { |
| 2626 |
case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break; |
| 2627 |
case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break; |
| 2628 |
case QTextListFormat::ListCircle: html += QLatin1String("<ul type=\"circle\""); break; |
| 2629 |
case QTextListFormat::ListSquare: html += QLatin1String("<ul type=\"square\""); break; |
| 2630 |
case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=\"a\""); break; |
| 2631 |
case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=\"A\""); break; |
| 2632 |
case QTextListFormat::ListLowerRoman: html += QLatin1String("<ol type=\"i\""); break; |
| 2633 |
case QTextListFormat::ListUpperRoman: html += QLatin1String("<ol type=\"I\""); break; |
| 2634 |
default: html += QLatin1String("<ul"); // ### should not happen |
| 2635 |
} |
| 2636 |
|
| 2637 |
QString styleString = QString::fromLatin1("margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px;"); |
| 2638 |
|
| 2639 |
if (format.hasProperty(QTextFormat::ListIndent)) { |
| 2640 |
styleString += QLatin1String(" -qt-list-indent: "); |
| 2641 |
styleString += QString::number(format.indent()); |
| 2642 |
styleString += QLatin1Char(';'); |
| 2643 |
} |
| 2644 |
|
| 2645 |
if (format.hasProperty(QTextFormat::ListNumberPrefix)) { |
| 2646 |
QString numberPrefix = format.numberPrefix(); |
| 2647 |
numberPrefix.replace(QLatin1Char('"'), QLatin1String("\\22")); |
| 2648 |
numberPrefix.replace(QLatin1Char('\''), QLatin1String("\\27")); // FIXME: There's a problem in the CSS parser the prevents this from being correctly restored |
| 2649 |
styleString += QLatin1String(" -qt-list-number-prefix: "); |
| 2650 |
styleString += QLatin1Char('\''); |
| 2651 |
styleString += numberPrefix; |
| 2652 |
styleString += QLatin1Char('\''); |
| 2653 |
styleString += QLatin1Char(';'); |
| 2654 |
} |
| 2655 |
|
| 2656 |
if (format.hasProperty(QTextFormat::ListNumberSuffix)) { |
| 2657 |
if (format.numberSuffix() != QLatin1String(".")) { // this is our default |
| 2658 |
QString numberSuffix = format.numberSuffix(); |
| 2659 |
numberSuffix.replace(QLatin1Char('"'), QLatin1String("\\22")); |
| 2660 |
numberSuffix.replace(QLatin1Char('\''), QLatin1String("\\27")); // see above |
| 2661 |
styleString += QLatin1String(" -qt-list-number-suffix: "); |
| 2662 |
styleString += QLatin1Char('\''); |
| 2663 |
styleString += numberSuffix; |
| 2664 |
styleString += QLatin1Char('\''); |
| 2665 |
styleString += QLatin1Char(';'); |
| 2666 |
} |
| 2667 |
} |
| 2668 |
|
| 2669 |
html += QLatin1String(" style=\""); |
| 2670 |
html += styleString; |
| 2671 |
html += QLatin1String("\">"); |
| 2672 |
} |
| 2673 |
|
| 2674 |
html += QLatin1String("<li"); |
| 2675 |
|
| 2676 |
const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat(); |
| 2677 |
if (!blockFmt.properties().isEmpty()) { |
| 2678 |
html += QLatin1String(" style=\""); |
| 2679 |
emitCharFormatStyle(blockFmt); |
| 2680 |
html += QLatin1Char('\"'); |
| 2681 |
|
| 2682 |
defaultCharFormat.merge(block.charFormat()); |
| 2683 |
} |
| 2684 |
} |
| 2685 |
|
| 2686 |
const QTextBlockFormat blockFormat = block.blockFormat(); |
| 2687 |
if (blockFormat.hasProperty(QTextFormat::BlockTrailingHorizontalRulerWidth)) { |
| 2688 |
html += QLatin1String("<hr"); |
| 2689 |
|
| 2690 |
QTextLength width = blockFormat.lengthProperty(QTextFormat::BlockTrailingHorizontalRulerWidth); |
| 2691 |
if (width.type() != QTextLength::VariableLength) |
| 2692 |
emitTextLength("width", width); |
| 2693 |
else |
| 2694 |
html += QLatin1Char(' '); |
| 2695 |
|
| 2696 |
html += QLatin1String("/>"); |
| 2697 |
return; |
| 2698 |
} |
| 2699 |
|
| 2700 |
const bool pre = blockFormat.nonBreakableLines(); |
| 2701 |
if (pre) { |
| 2702 |
if (list) |
| 2703 |
html += QLatin1Char('>'); |
| 2704 |
html += QLatin1String("<pre"); |
| 2705 |
} else if (!list) { |
| 2706 |
html += QLatin1String("<p"); |
| 2707 |
} |
| 2708 |
|
| 2709 |
emitBlockAttributes(block); |
| 2710 |
|
| 2711 |
html += QLatin1Char('>'); |
| 2712 |
if (block.begin().atEnd()) |
| 2713 |
html += QLatin1String("<br />"); |
| 2714 |
|
| 2715 |
QTextBlock::Iterator it = block.begin(); |
| 2716 |
if (fragmentMarkers && !it.atEnd() && block == doc->begin()) |
| 2717 |
html += QLatin1String("<!--StartFragment-->"); |
| 2718 |
|
| 2719 |
for (; !it.atEnd(); ++it) |
| 2720 |
emitFragment(it.fragment()); |
| 2721 |
|
| 2722 |
if (fragmentMarkers && block.position() + block.length() == doc->docHandle()->length()) |
| 2723 |
html += QLatin1String("<!--EndFragment-->"); |
| 2724 |
|
| 2725 |
if (pre) |
| 2726 |
html += QLatin1String("</pre>"); |
| 2727 |
else if (list) |
| 2728 |
html += QLatin1String("</li>"); |
| 2729 |
else |
| 2730 |
html += QLatin1String("</p>"); |
| 2731 |
|
| 2732 |
if (list) { |
| 2733 |
if (list->itemNumber(block) == list->count() - 1) { // last item? close list |
| 2734 |
if (isOrderedList(list->format().style())) |
| 2735 |
html += QLatin1String("</ol>"); |
| 2736 |
else |
| 2737 |
html += QLatin1String("</ul>"); |
| 2738 |
} |
| 2739 |
} |
| 2740 |
|
| 2741 |
defaultCharFormat = oldDefaultCharFormat; |
| 2742 |
} |
| 2743 |
|
| 2744 |
extern bool qHasPixmapTexture(const QBrush& brush); |
| 2745 |
|
| 2746 |
QString QTextHtmlExporter::findUrlForImage(const QTextDocument *doc, qint64 cacheKey, bool isPixmap) |
| 2747 |
{ |
| 2748 |
QString url; |
| 2749 |
if (!doc) |
| 2750 |
return url; |
| 2751 |
|
| 2752 |
if (QTextDocument *parent = qobject_cast<QTextDocument *>(doc->parent())) |
| 2753 |
return findUrlForImage(parent, cacheKey, isPixmap); |
| 2754 |
|
| 2755 |
if (doc && doc->docHandle()) { |
| 2756 |
QTextDocumentPrivate *priv = doc->docHandle(); |
| 2757 |
QMap<QUrl, QVariant>::const_iterator it = priv->cachedResources.constBegin(); |
| 2758 |
for (; it != priv->cachedResources.constEnd(); ++it) { |
| 2759 |
|
| 2760 |
const QVariant &v = it.value(); |
| 2761 |
if (v.type() == QVariant::Image && !isPixmap) { |
| 2762 |
if (qvariant_cast<QImage>(v).cacheKey() == cacheKey) |
| 2763 |
break; |
| 2764 |
} |
| 2765 |
|
| 2766 |
if (v.type() == QVariant::Pixmap && isPixmap) { |
| 2767 |
if (qvariant_cast<QPixmap>(v).cacheKey() == cacheKey) |
| 2768 |
break; |
| 2769 |
} |
| 2770 |
} |
| 2771 |
|
| 2772 |
if (it != priv->cachedResources.constEnd()) |
| 2773 |
url = it.key().toString(); |
| 2774 |
} |
| 2775 |
|
| 2776 |
return url; |
| 2777 |
} |
| 2778 |
|
| 2779 |
void QTextDocumentPrivate::mergeCachedResources(const QTextDocumentPrivate *priv) |
| 2780 |
{ |
| 2781 |
if (!priv) |
| 2782 |
return; |
| 2783 |
|
| 2784 |
cachedResources.unite(priv->cachedResources); |
| 2785 |
} |
| 2786 |
|
| 2787 |
void QTextHtmlExporter::emitBackgroundAttribute(const QTextFormat &format) |
| 2788 |
{ |
| 2789 |
if (format.hasProperty(QTextFormat::BackgroundImageUrl)) { |
| 2790 |
QString url = format.property(QTextFormat::BackgroundImageUrl).toString(); |
| 2791 |
emitAttribute("background", url); |
| 2792 |
} else { |
| 2793 |
const QBrush &brush = format.background(); |
| 2794 |
if (brush.style() == Qt::SolidPattern) { |
| 2795 |
emitAttribute("bgcolor", brush.color().name()); |
| 2796 |
} else if (brush.style() == Qt::TexturePattern) { |
| 2797 |
const bool isPixmap = qHasPixmapTexture(brush); |
| 2798 |
const qint64 cacheKey = isPixmap ? brush.texture().cacheKey() : brush.textureImage().cacheKey(); |
| 2799 |
|
| 2800 |
const QString url = findUrlForImage(doc, cacheKey, isPixmap); |
| 2801 |
|
| 2802 |
if (!url.isEmpty()) |
| 2803 |
emitAttribute("background", url); |
| 2804 |
} |
| 2805 |
} |
| 2806 |
} |
| 2807 |
|
| 2808 |
void QTextHtmlExporter::emitTable(const QTextTable *table) |
| 2809 |
{ |
| 2810 |
QTextTableFormat format = table->format(); |
| 2811 |
|
| 2812 |
html += QLatin1String("\n<table"); |
| 2813 |
|
| 2814 |
if (format.hasProperty(QTextFormat::FrameBorder)) |
| 2815 |
emitAttribute("border", QString::number(format.border())); |
| 2816 |
|
| 2817 |
emitFrameStyle(format, TableFrame); |
| 2818 |
|
| 2819 |
emitAlignment(format.alignment()); |
| 2820 |
emitTextLength("width", format.width()); |
| 2821 |
|
| 2822 |
if (format.hasProperty(QTextFormat::TableCellSpacing)) |
| 2823 |
emitAttribute("cellspacing", QString::number(format.cellSpacing())); |
| 2824 |
if (format.hasProperty(QTextFormat::TableCellPadding)) |
| 2825 |
emitAttribute("cellpadding", QString::number(format.cellPadding())); |
| 2826 |
|
| 2827 |
emitBackgroundAttribute(format); |
| 2828 |
|
| 2829 |
html += QLatin1Char('>'); |
| 2830 |
|
| 2831 |
const int rows = table->rows(); |
| 2832 |
const int columns = table->columns(); |
| 2833 |
|
| 2834 |
QVector<QTextLength> columnWidths = format.columnWidthConstraints(); |
| 2835 |
if (columnWidths.isEmpty()) { |
| 2836 |
columnWidths.resize(columns); |
| 2837 |
columnWidths.fill(QTextLength()); |
| 2838 |
} |
| 2839 |
Q_ASSERT(columnWidths.count() == columns); |
| 2840 |
|
| 2841 |
QVarLengthArray<bool> widthEmittedForColumn(columns); |
| 2842 |
for (int i = 0; i < columns; ++i) |
| 2843 |
widthEmittedForColumn[i] = false; |
| 2844 |
|
| 2845 |
const int headerRowCount = qMin(format.headerRowCount(), rows); |
| 2846 |
if (headerRowCount > 0) |
| 2847 |
html += QLatin1String("<thead>"); |
| 2848 |
|
| 2849 |
for (int row = 0; row < rows; ++row) { |
| 2850 |
html += QLatin1String("\n<tr>"); |
| 2851 |
|
| 2852 |
for (int col = 0; col < columns; ++col) { |
| 2853 |
const QTextTableCell cell = table->cellAt(row, col); |
| 2854 |
|
| 2855 |
// for col/rowspans |
| 2856 |
if (cell.row() != row) |
| 2857 |
continue; |
| 2858 |
|
| 2859 |
if (cell.column() != col) |
| 2860 |
continue; |
| 2861 |
|
| 2862 |
html += QLatin1String("\n<td"); |
| 2863 |
|
| 2864 |
if (!widthEmittedForColumn[col] && cell.columnSpan() == 1) { |
| 2865 |
emitTextLength("width", columnWidths.at(col)); |
| 2866 |
widthEmittedForColumn[col] = true; |
| 2867 |
} |
| 2868 |
|
| 2869 |
if (cell.columnSpan() > 1) |
| 2870 |
emitAttribute("colspan", QString::number(cell.columnSpan())); |
| 2871 |
|
| 2872 |
if (cell.rowSpan() > 1) |
| 2873 |
emitAttribute("rowspan", QString::number(cell.rowSpan())); |
| 2874 |
|
| 2875 |
const QTextTableCellFormat cellFormat = cell.format().toTableCellFormat(); |
| 2876 |
emitBackgroundAttribute(cellFormat); |
| 2877 |
|
| 2878 |
QTextCharFormat oldDefaultCharFormat = defaultCharFormat; |
| 2879 |
|
| 2880 |
QTextCharFormat::VerticalAlignment valign = cellFormat.verticalAlignment(); |
| 2881 |
|
| 2882 |
QString styleString; |
| 2883 |
if (valign >= QTextCharFormat::AlignMiddle && valign <= QTextCharFormat::AlignBottom) { |
| 2884 |
styleString += QLatin1String(" vertical-align:"); |
| 2885 |
switch (valign) { |
| 2886 |
case QTextCharFormat::AlignMiddle: |
| 2887 |
styleString += QLatin1String("middle"); |
| 2888 |
break; |
| 2889 |
case QTextCharFormat::AlignTop: |
| 2890 |
styleString += QLatin1String("top"); |
| 2891 |
break; |
| 2892 |
case QTextCharFormat::AlignBottom: |
| 2893 |
styleString += QLatin1String("bottom"); |
| 2894 |
break; |
| 2895 |
default: |
| 2896 |
break; |
| 2897 |
} |
| 2898 |
styleString += QLatin1Char(';'); |
| 2899 |
|
| 2900 |
QTextCharFormat temp; |
| 2901 |
temp.setVerticalAlignment(valign); |
| 2902 |
defaultCharFormat.merge(temp); |
| 2903 |
} |
| 2904 |
|
| 2905 |
if (cellFormat.hasProperty(QTextFormat::TableCellLeftPadding)) |
| 2906 |
styleString += QLatin1String(" padding-left:") + QString::number(cellFormat.leftPadding()) + QLatin1Char(';'); |
| 2907 |
if (cellFormat.hasProperty(QTextFormat::TableCellRightPadding)) |
| 2908 |
styleString += QLatin1String(" padding-right:") + QString::number(cellFormat.rightPadding()) + QLatin1Char(';'); |
| 2909 |
if (cellFormat.hasProperty(QTextFormat::TableCellTopPadding)) |
| 2910 |
styleString += QLatin1String(" padding-top:") + QString::number(cellFormat.topPadding()) + QLatin1Char(';'); |
| 2911 |
if (cellFormat.hasProperty(QTextFormat::TableCellBottomPadding)) |
| 2912 |
styleString += QLatin1String(" padding-bottom:") + QString::number(cellFormat.bottomPadding()) + QLatin1Char(';'); |
| 2913 |
|
| 2914 |
if (!styleString.isEmpty()) |
| 2915 |
html += QLatin1String(" style=\"") + styleString + QLatin1Char('\"'); |
| 2916 |
|
| 2917 |
html += QLatin1Char('>'); |
| 2918 |
|
| 2919 |
emitFrame(cell.begin()); |
| 2920 |
|
| 2921 |
html += QLatin1String("</td>"); |
| 2922 |
|
| 2923 |
defaultCharFormat = oldDefaultCharFormat; |
| 2924 |
} |
| 2925 |
|
| 2926 |
html += QLatin1String("</tr>"); |
| 2927 |
if (headerRowCount > 0 && row == headerRowCount - 1) |
| 2928 |
html += QLatin1String("</thead>"); |
| 2929 |
} |
| 2930 |
|
| 2931 |
html += QLatin1String("</table>"); |
| 2932 |
} |
| 2933 |
|
| 2934 |
void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt) |
| 2935 |
{ |
| 2936 |
if (!frameIt.atEnd()) { |
| 2937 |
QTextFrame::Iterator next = frameIt; |
| 2938 |
++next; |
| 2939 |
if (next.atEnd() |
| 2940 |
&& frameIt.currentFrame() == 0 |
| 2941 |
&& frameIt.parentFrame() != doc->rootFrame() |
| 2942 |
&& frameIt.currentBlock().begin().atEnd()) |
| 2943 |
return; |
| 2944 |
} |
| 2945 |
|
| 2946 |
for (QTextFrame::Iterator it = frameIt; |
| 2947 |
!it.atEnd(); ++it) { |
| 2948 |
if (QTextFrame *f = it.currentFrame()) { |
| 2949 |
if (QTextTable *table = qobject_cast<QTextTable *>(f)) { |
| 2950 |
emitTable(table); |
| 2951 |
} else { |
| 2952 |
emitTextFrame(f); |
| 2953 |
} |
| 2954 |
} else if (it.currentBlock().isValid()) { |
| 2955 |
emitBlock(it.currentBlock()); |
| 2956 |
} |
| 2957 |
} |
| 2958 |
} |
| 2959 |
|
| 2960 |
void QTextHtmlExporter::emitTextFrame(const QTextFrame *f) |
| 2961 |
{ |
| 2962 |
FrameType frameType = f->parentFrame() ? TextFrame : RootFrame; |
| 2963 |
|
| 2964 |
html += QLatin1String("\n<table"); |
| 2965 |
QTextFrameFormat format = f->frameFormat(); |
| 2966 |
|
| 2967 |
if (format.hasProperty(QTextFormat::FrameBorder)) |
| 2968 |
emitAttribute("border", QString::number(format.border())); |
| 2969 |
|
| 2970 |
emitFrameStyle(format, frameType); |
| 2971 |
|
| 2972 |
emitTextLength("width", format.width()); |
| 2973 |
emitTextLength("height", format.height()); |
| 2974 |
|
| 2975 |
// root frame's bcolor goes in the <body> tag |
| 2976 |
if (frameType != RootFrame) |
| 2977 |
emitBackgroundAttribute(format); |
| 2978 |
|
| 2979 |
html += QLatin1Char('>'); |
| 2980 |
html += QLatin1String("\n<tr>\n<td style=\"border: none;\">"); |
| 2981 |
emitFrame(f->begin()); |
| 2982 |
html += QLatin1String("</td></tr></table>"); |
| 2983 |
} |
| 2984 |
|
| 2985 |
void QTextHtmlExporter::emitFrameStyle(const QTextFrameFormat &format, FrameType frameType) |
| 2986 |
{ |
| 2987 |
QLatin1String styleAttribute(" style=\""); |
| 2988 |
html += styleAttribute; |
| 2989 |
const int originalHtmlLength = html.length(); |
| 2990 |
|
| 2991 |
if (frameType == TextFrame) |
| 2992 |
html += QLatin1String("-qt-table-type: frame;"); |
| 2993 |
else if (frameType == RootFrame) |
| 2994 |
html += QLatin1String("-qt-table-type: root;"); |
| 2995 |
|
| 2996 |
const QTextFrameFormat defaultFormat; |
| 2997 |
|
| 2998 |
emitFloatStyle(format.position(), OmitStyleTag); |
| 2999 |
emitPageBreakPolicy(format.pageBreakPolicy()); |
| 3000 |
|
| 3001 |
if (format.borderBrush() != defaultFormat.borderBrush()) { |
| 3002 |
html += QLatin1String(" border-color:"); |
| 3003 |
html += format.borderBrush().color().name(); |
| 3004 |
html += QLatin1Char(';'); |
| 3005 |
} |
| 3006 |
|
| 3007 |
if (format.borderStyle() != defaultFormat.borderStyle()) |
| 3008 |
emitBorderStyle(format.borderStyle()); |
| 3009 |
|
| 3010 |
if (format.hasProperty(QTextFormat::FrameMargin) |
| 3011 |
|| format.hasProperty(QTextFormat::FrameLeftMargin) |
| 3012 |
|| format.hasProperty(QTextFormat::FrameRightMargin) |
| 3013 |
|| format.hasProperty(QTextFormat::FrameTopMargin) |
| 3014 |
|| format.hasProperty(QTextFormat::FrameBottomMargin)) |
| 3015 |
emitMargins(QString::number(format.topMargin()), |
| 3016 |
QString::number(format.bottomMargin()), |
| 3017 |
QString::number(format.leftMargin()), |
| 3018 |
QString::number(format.rightMargin())); |
| 3019 |
|
| 3020 |
if (html.length() == originalHtmlLength) // nothing emitted? |
| 3021 |
html.chop(qstrlen(styleAttribute.latin1())); |
| 3022 |
else |
| 3023 |
html += QLatin1Char('\"'); |
| 3024 |
} |
| 3025 |
|
| 3026 |
/*! |
| 3027 |
Returns a string containing an HTML representation of the document. |
| 3028 |
|
| 3029 |
The \a encoding parameter specifies the value for the charset attribute |
| 3030 |
in the html header. For example if 'utf-8' is specified then the |
| 3031 |
beginning of the generated html will look like this: |
| 3032 |
\snippet doc/src/snippets/code/src_gui_text_qtextdocument.cpp 1 |
| 3033 |
|
| 3034 |
If no encoding is specified then no such meta information is generated. |
| 3035 |
|
| 3036 |
If you later on convert the returned html string into a byte array for |
| 3037 |
transmission over a network or when saving to disk you should specify |
| 3038 |
the encoding you're going to use for the conversion to a byte array here. |
| 3039 |
|
| 3040 |
\sa {Supported HTML Subset} |
| 3041 |
*/ |
| 3042 |
#ifndef QT_NO_TEXTHTMLPARSER |
| 3043 |
QString QTextDocument::toHtml(const QByteArray &encoding) const |
| 3044 |
{ |
| 3045 |
return QTextHtmlExporter(this).toHtml(encoding); |
| 3046 |
} |
| 3047 |
#endif // QT_NO_TEXTHTMLPARSER |
| 3048 |
|
| 3049 |
/*! |
| 3050 |
Returns a vector of text formats for all the formats used in the document. |
| 3051 |
*/ |
| 3052 |
QVector<QTextFormat> QTextDocument::allFormats() const |
| 3053 |
{ |
| 3054 |
Q_D(const QTextDocument); |
| 3055 |
return d->formatCollection()->formats; |
| 3056 |
} |
| 3057 |
|
| 3058 |
|
| 3059 |
/*! |
| 3060 |
\internal |
| 3061 |
|
| 3062 |
So that not all classes have to be friends of each other... |
| 3063 |
*/ |
| 3064 |
QTextDocumentPrivate *QTextDocument::docHandle() const |
| 3065 |
{ |
| 3066 |
Q_D(const QTextDocument); |
| 3067 |
return const_cast<QTextDocumentPrivate *>(d); |
| 3068 |
} |
| 3069 |
|
| 3070 |
/*! |
| 3071 |
\since 4.4 |
| 3072 |
\fn QTextDocument::undoCommandAdded() |
| 3073 |
|
| 3074 |
This signal is emitted every time a new level of undo is added to the QTextDocument. |
| 3075 |
*/ |
| 3076 |
|
| 3077 |
QT_END_NAMESPACE |