~oif-team/ubuntu/natty/qt4-x11/xi2.1

« back to all changes in this revision

Viewing changes to src/gui/text/qtextdocument.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Adam Conrad
  • Date: 2005-08-24 04:09:09 UTC
  • Revision ID: james.westby@ubuntu.com-20050824040909-xmxe9jfr4a0w5671
Tags: upstream-4.0.0
ImportĀ upstreamĀ versionĀ 4.0.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 1992-2005 Trolltech AS. All rights reserved.
 
4
**
 
5
** This file is part of the text module of the Qt Toolkit.
 
6
**
 
7
** This file may be distributed under the terms of the Q Public License
 
8
** as defined by Trolltech AS of Norway and appearing in the file
 
9
** LICENSE.QPL included in the packaging of this file.
 
10
**
 
11
** This file may be distributed and/or modified under the terms of the
 
12
** GNU General Public License version 2 as published by the Free Software
 
13
** Foundation and appearing in the file LICENSE.GPL included in the
 
14
** packaging of this file.
 
15
**
 
16
** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
 
17
**   information about Qt Commercial License Agreements.
 
18
** See http://www.trolltech.com/qpl/ for QPL licensing information.
 
19
** See http://www.trolltech.com/gpl/ for GPL licensing information.
 
20
**
 
21
** Contact info@trolltech.com if any conditions of this licensing are
 
22
** not clear to you.
 
23
**
 
24
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 
25
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 
26
**
 
27
****************************************************************************/
 
28
 
 
29
#include "qtextdocument.h"
 
30
#include <qtextformat.h>
 
31
#include "qtextdocumentlayout_p.h"
 
32
#include "qtextdocumentfragment.h"
 
33
#include "qtextdocumentfragment_p.h"
 
34
#include "qtexttable.h"
 
35
#include "qtextlist.h"
 
36
#include <qdebug.h>
 
37
#include <qregexp.h>
 
38
#include <qvarlengtharray.h>
 
39
#include <qtextcodec.h>
 
40
#include "qtexthtmlparser_p.h"
 
41
#include "qpainter.h"
 
42
#include "qprinter.h"
 
43
#include "qtextedit.h"
 
44
 
 
45
#include "qtextdocument_p.h"
 
46
 
 
47
#include <limits.h>
 
48
 
 
49
/*!
 
50
    Returns true if the string \a text is likely to be rich text;
 
51
    otherwise returns false.
 
52
 
 
53
    This function uses a fast and therefore simple heuristic. It
 
54
    mainly checks whether there is something that looks like a tag
 
55
    before the first line break. Although the result may be correct
 
56
    for common cases, there is no guarantee.
 
57
*/
 
58
bool Qt::mightBeRichText(const QString& text)
 
59
{
 
60
    if (text.isEmpty())
 
61
        return false;
 
62
    int start = 0;
 
63
 
 
64
    while (start < text.length() && text.at(start).isSpace())
 
65
        ++start;
 
66
 
 
67
    // skip a leading <?xml ... ?> as for example with xhtml
 
68
    if (text.mid(start, 5) == QLatin1String("<?xml")) {
 
69
        while (start < text.length()) {
 
70
            if (text.at(start) == QLatin1Char('?')
 
71
                && start + 2 < text.length()
 
72
                && text.at(start + 1) == QLatin1Char('>')) {
 
73
                start += 2;
 
74
                break;
 
75
            }
 
76
            ++start;
 
77
        }
 
78
 
 
79
        while (start < text.length() && text.at(start).isSpace())
 
80
            ++start;
 
81
    }
 
82
 
 
83
    if (text.mid(start, 5).toLower() == QLatin1String("<!doc"))
 
84
        return true;
 
85
    int open = start;
 
86
    while (open < text.length() && text.at(open) != '<'
 
87
            && text.at(open) != '\n') {
 
88
        if (text.at(open) == '&' &&  text.mid(open+1,3) == "lt;")
 
89
            return true; // support desperate attempt of user to see <...>
 
90
        ++open;
 
91
    }
 
92
    if (open < text.length() && text.at(open) == '<') {
 
93
        const int close = text.indexOf('>', open);
 
94
        if (close > -1) {
 
95
            QString tag;
 
96
            for (int i = open+1; i < close; ++i) {
 
97
                if (text[i].isDigit() || text[i].isLetter())
 
98
                    tag += text[i];
 
99
                else if (!tag.isEmpty() && text[i].isSpace())
 
100
                    break;
 
101
                else if (!text[i].isSpace() && (!tag.isEmpty() || text[i] != '!'))
 
102
                    return false; // that's not a tag
 
103
            }
 
104
            return QTextHtmlParser::lookupElement(tag.toLower()) != -1;
 
105
        }
 
106
    }
 
107
    return false;
 
108
}
 
109
 
 
110
/*!
 
111
  Auxiliary function. Converts the plain text string \a plain to a
 
112
  rich text formatted string with any HTML meta-characters escaped.
 
113
 */
 
114
QString Qt::escape(const QString& plain)
 
115
{
 
116
    QString rich;
 
117
    rich.reserve(int(plain.length() * 1.1));
 
118
    for (int i = 0; i < plain.length(); ++i) {
 
119
        if (plain.at(i) == QLatin1Char('<'))
 
120
            rich += QLatin1String("&lt;");
 
121
        else if (plain.at(i) == QLatin1Char('>'))
 
122
            rich += QLatin1String("&gt;");
 
123
        else if (plain.at(i) == QLatin1Char('&'))
 
124
            rich += QLatin1String("&amp;");
 
125
        else
 
126
            rich += plain.at(i);
 
127
    }
 
128
    return rich;
 
129
}
 
130
 
 
131
/*!
 
132
    \fn QString Qt::convertFromPlainText(const QString &plain, WhiteSpaceMode mode)
 
133
 
 
134
    Auxiliary function. Converts the plain text string \a plain to a
 
135
    rich text formatted paragraph while preserving most of its look.
 
136
 
 
137
    \a mode defines the whitespace mode. Possible values are \c
 
138
    QStyleSheetItem::WhiteSpacePre (no wrapping, all whitespaces
 
139
    preserved) and \c QStyleSheetItem::WhiteSpaceNormal (wrapping,
 
140
    simplified whitespaces).
 
141
 
 
142
    \sa escape()
 
143
*/
 
144
QString Qt::convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
 
145
{
 
146
    int col = 0;
 
147
    QString rich;
 
148
    rich += "<p>";
 
149
    for (int i = 0; i < plain.length(); ++i) {
 
150
        if (plain[i] == '\n'){
 
151
            int c = 1;
 
152
            while (i+1 < plain.length() && plain[i+1] == '\n') {
 
153
                i++;
 
154
                c++;
 
155
            }
 
156
            if (c == 1)
 
157
                rich += "<br>\n";
 
158
            else {
 
159
                rich += "</p>\n";
 
160
                while (--c > 1)
 
161
                    rich += "<br>\n";
 
162
                rich += "<p>";
 
163
            }
 
164
            col = 0;
 
165
        } else {
 
166
            if (mode == Qt::WhiteSpacePre && plain[i] == '\t'){
 
167
                rich += 0x00a0U;
 
168
                ++col;
 
169
                while (col % 8) {
 
170
                    rich += 0x00a0U;
 
171
                    ++col;
 
172
                }
 
173
            }
 
174
            else if (mode == Qt::WhiteSpacePre && plain[i].isSpace())
 
175
                rich += 0x00a0U;
 
176
            else if (plain[i] == '<')
 
177
                rich +="&lt;";
 
178
            else if (plain[i] == '>')
 
179
                rich +="&gt;";
 
180
            else if (plain[i] == '&')
 
181
                rich +="&amp;";
 
182
            else
 
183
                rich += plain[i];
 
184
            ++col;
 
185
        }
 
186
    }
 
187
    if (col != 0)
 
188
        rich += "</p>";
 
189
    return rich;
 
190
}
 
191
 
 
192
/*!
 
193
  \internal
 
194
*/
 
195
QTextCodec *Qt::codecForHtml(const QByteArray &ba)
 
196
{
 
197
    // determine charset
 
198
    int mib = 4; // Latin1
 
199
    int pos;
 
200
    QTextCodec *c = 0;
 
201
 
 
202
    if (ba.size() > 1 && (((uchar)ba[0] == 0xfe && (uchar)ba[1] == 0xff)
 
203
                          || ((uchar)ba[0] == 0xff && (uchar)ba[1] == 0xfe))) {
 
204
        mib = 1000; // utf16
 
205
    } else if (ba.size() > 2
 
206
             && (uchar)ba[0] == 0xef
 
207
             && (uchar)ba[1] == 0xbb
 
208
             && (uchar)ba[2] == 0xbf) {
 
209
        mib = 106; // utf-8
 
210
    } else {
 
211
        QByteArray header = ba.left(512).toLower();
 
212
        if ((pos = header.indexOf("http-equiv=")) != -1) {
 
213
            pos = header.indexOf("charset=", pos) + strlen("charset=");
 
214
            if (pos != -1) {
 
215
                int pos2 = header.indexOf('\"', pos+1);
 
216
                QByteArray cs = header.mid(pos, pos2-pos);
 
217
                //            qDebug("found charset: %s", cs.data());
 
218
                c = QTextCodec::codecForName(cs);
 
219
            }
 
220
        }
 
221
    }
 
222
    if (!c)
 
223
        c = QTextCodec::codecForMib(mib);
 
224
 
 
225
    return c;
 
226
}
 
227
 
 
228
// internal, do not Q_EXPORT
 
229
// can go away when QTextDocumentFragment uses QTextDocument
 
230
void qt_replace_special_text_characters(QString *text)
 
231
{
 
232
    text->replace(QTextBeginningOfFrame, '\n');
 
233
    text->replace(QTextEndOfFrame, '\n');
 
234
    text->replace(QChar::ParagraphSeparator, '\n');
 
235
    text->replace(QChar::LineSeparator, '\n');
 
236
    text->replace(QChar::Nbsp, ' ');
 
237
}
 
238
 
 
239
/*!
 
240
    \class QTextDocument qtextdocument.h
 
241
    \brief The QTextDocument class holds formatted text that can be
 
242
    viewed and edited using a QTextEdit.
 
243
 
 
244
    \ingroup text
 
245
    \mainclass
 
246
 
 
247
    QTextDocument is a container for structured rich text documents, providing
 
248
    support for styled text and various types of document elements, such as
 
249
    lists, tables, frames, and images.
 
250
    They can be created for use in a QTextEdit, or used independently.
 
251
 
 
252
    Each document element is described by an associated format object. Each
 
253
    format object is treated as a unique object by QTextDocuments, and can be
 
254
    passed to objectForFormat() to obtain the document element that it is
 
255
    applied to.
 
256
 
 
257
    A QTextDocument can be edited programmatically using a QTextCursor, and
 
258
    its contents can be examined by traversing the document structure. The
 
259
    entire document structure is stored as a hierarchy of document elements
 
260
    beneath the root frame, found with the rootFrame() function. Alternatively,
 
261
    if you just want to iterate over the textual contents of the document you
 
262
    can use begin(), end(), and findBlock() to retrieve text blocks that you
 
263
    can examine and iterate over.
 
264
 
 
265
    The layout of a document is determined by the documentLayout();
 
266
    you can create your own QAbstractTextDocumentLayout subclass and
 
267
    set it using setDocumentLayout() if you want to use your own
 
268
    layout logic. The document's title can be obtained by calling the
 
269
    documentTitle() function.
 
270
 
 
271
    The toPlainText() and toHtml() convenience functions allow you to retrieve the
 
272
    contents of the document as plain text and HTML.
 
273
    The document's text can be searched using the find() functions.
 
274
 
 
275
    Undo/redo of operations performed on the document can be controlled using
 
276
    the setUndoRedoEnabled() function. The undo/redo system can be controlled
 
277
    by an editor widget through the undo() and redo() slots; the document also
 
278
    provides contentsChanged(), undoAvailable(), and redoAvailable() signals
 
279
    that inform connected editor widgets about the state of the undo/redo
 
280
    system.
 
281
 
 
282
    \sa QTextCursor QTextEdit \link richtext.html Rich Text Processing\endlink
 
283
*/
 
284
 
 
285
/*!
 
286
    \property QTextDocument::defaultFont
 
287
    \brief the default font used to display the document's text
 
288
*/
 
289
 
 
290
/*!
 
291
    Constructs an empty QTextDocument with the given \a parent.
 
292
*/
 
293
QTextDocument::QTextDocument(QObject *parent)
 
294
    : QObject(*new QTextDocumentPrivate, parent)
 
295
{
 
296
    Q_D(QTextDocument);
 
297
    d->init();
 
298
}
 
299
 
 
300
/*!
 
301
    Constructs a QTextDocument containing the plain (unformatted) \a text
 
302
    specified, and with the given \a parent.
 
303
*/
 
304
QTextDocument::QTextDocument(const QString &text, QObject *parent)
 
305
    : QObject(*new QTextDocumentPrivate, parent)
 
306
{
 
307
    Q_D(QTextDocument);
 
308
    d->init();
 
309
    QTextCursor(this).insertText(text);
 
310
}
 
311
 
 
312
/*!
 
313
    Destroys the document.
 
314
*/
 
315
QTextDocument::~QTextDocument()
 
316
{
 
317
}
 
318
 
 
319
 
 
320
/*!
 
321
  Creates a new QTextDocument that is a copy of this text document. \a
 
322
  parent is the parent of the returned text document.
 
323
*/
 
324
QTextDocument *QTextDocument::clone(QObject *parent) const
 
325
{
 
326
    QTextDocument *doc = new QTextDocument(parent);
 
327
    QTextCursor(doc).insertFragment(QTextDocumentFragment(this));
 
328
    return doc;
 
329
}
 
330
 
 
331
/*!
 
332
    Returns true if the document is empty; otherwise returns false.
 
333
*/
 
334
bool QTextDocument::isEmpty() const
 
335
{
 
336
    Q_D(const QTextDocument);
 
337
    /* because if we're empty we still have one single paragraph as
 
338
     * one single fragment */
 
339
    return d->length() <= 1;
 
340
}
 
341
 
 
342
/*!
 
343
  Clears the document.
 
344
*/
 
345
void QTextDocument::clear()
 
346
{
 
347
    Q_D(QTextDocument);
 
348
    d->clear();
 
349
}
 
350
 
 
351
/*!
 
352
    Undoes the last editing operation on the document if
 
353
    \link QTextDocument::isUndoAvailable() undo is available\endlink.
 
354
*/
 
355
void QTextDocument::undo()
 
356
{
 
357
    Q_D(QTextDocument);
 
358
    d->undoRedo(true);
 
359
}
 
360
 
 
361
/*!
 
362
    Redoes the last editing operation on the document if \link
 
363
    QTextDocument::isRedoAvailable() redo is available\endlink.
 
364
*/
 
365
void QTextDocument::redo()
 
366
{
 
367
    Q_D(QTextDocument);
 
368
    d->undoRedo(false);
 
369
}
 
370
 
 
371
/*!
 
372
    \internal
 
373
 
 
374
    Appends a custom undo \a item to the undo stack.
 
375
*/
 
376
void QTextDocument::appendUndoItem(QAbstractUndoItem *item)
 
377
{
 
378
    Q_D(QTextDocument);
 
379
    d->appendUndoItem(item);
 
380
}
 
381
 
 
382
/*!
 
383
    \property QTextDocument::undoRedoEnabled
 
384
    \brief whether undo/redo are enabled for this document
 
385
 
 
386
    This defaults to true. If disabled, the undo stack is cleared and
 
387
    no items will be added to it.
 
388
*/
 
389
void QTextDocument::setUndoRedoEnabled(bool enable)
 
390
{
 
391
    Q_D(QTextDocument);
 
392
    d->enableUndoRedo(enable);
 
393
}
 
394
 
 
395
bool QTextDocument::isUndoRedoEnabled() const
 
396
{
 
397
    Q_D(const QTextDocument);
 
398
    return d->isUndoRedoEnabled();
 
399
}
 
400
 
 
401
/*!
 
402
    \fn void QTextDocument::markContentsDirty(int position, int length)
 
403
 
 
404
    Marks the contents specified by the given \a position and \a length
 
405
    as "dirty", informing the document that it needs to be layed out
 
406
    again.
 
407
*/
 
408
void QTextDocument::markContentsDirty(int from, int length)
 
409
{
 
410
    Q_D(QTextDocument);
 
411
    d->documentChange(from, length);
 
412
}
 
413
 
 
414
/*!
 
415
    \fn void QTextDocument::contentsChanged()
 
416
 
 
417
    This signal is emitted whenever the document's content changes; for
 
418
    example, when text is inserted or deleted, or when formatting is applied.
 
419
 
 
420
    \sa contentsChange()
 
421
*/
 
422
 
 
423
/*!
 
424
    \fn void QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded)
 
425
 
 
426
    This signal is emitted whenever the document's content changes; for
 
427
    example, when text is inserted or deleted, or when formatting is applied.
 
428
 
 
429
    Information is provided about the \a position of the character in the
 
430
    document where the change occurred, the number of characters removed
 
431
    (\a charsRemoved), and the number of characters added (\a charsAdded).
 
432
 
 
433
    The signal is emitted before the document's layout manager is notified
 
434
    about the change. This hook allows you to implement syntax highlighting
 
435
    for the document.
 
436
 
 
437
    \sa QAbstractTextDocumentLayout::documentChanged(), contentsChanged()
 
438
*/
 
439
 
 
440
 
 
441
/*!
 
442
    \fn QTextDocument::undoAvailable(bool available);
 
443
 
 
444
    This signal is emitted whenever undo operations become available
 
445
    (\a available is true) or unavailable (\a available is false).
 
446
*/
 
447
 
 
448
/*!
 
449
    \fn QTextDocument::redoAvailable(bool available);
 
450
 
 
451
    This signal is emitted whenever redo operations become available
 
452
    (\a available is true) or unavailable (\a available is false).
 
453
*/
 
454
 
 
455
/*!
 
456
    \fn QTextDocument::cursorPositionChanged(const QTextCursor &cursor);
 
457
 
 
458
    This signal is emitted whenever the position of a cursor changed
 
459
    due to an editing operation. The cursor that changed is passed in
 
460
    \a cursor.
 
461
*/
 
462
 
 
463
/*!
 
464
    Returns true is undo is available; otherwise returns false.
 
465
*/
 
466
bool QTextDocument::isUndoAvailable() const
 
467
{
 
468
    Q_D(const QTextDocument);
 
469
    return d->isUndoAvailable();
 
470
}
 
471
 
 
472
/*!
 
473
    Returns true is redo is available; otherwise returns false.
 
474
*/
 
475
bool QTextDocument::isRedoAvailable() const
 
476
{
 
477
    Q_D(const QTextDocument);
 
478
    return d->isRedoAvailable();
 
479
}
 
480
 
 
481
/*!
 
482
    Sets the document to use the given \a layout. The previous layout
 
483
    is deleted.
 
484
*/
 
485
void QTextDocument::setDocumentLayout(QAbstractTextDocumentLayout *layout)
 
486
{
 
487
    Q_D(QTextDocument);
 
488
    d->setLayout(layout);
 
489
}
 
490
 
 
491
/*!
 
492
    Returns the document layout for this document.
 
493
*/
 
494
QAbstractTextDocumentLayout *QTextDocument::documentLayout() const
 
495
{
 
496
    Q_D(const QTextDocument);
 
497
    if (!d->lout) {
 
498
        QTextDocument *that = const_cast<QTextDocument *>(this);
 
499
        that->d_func()->setLayout(new QTextDocumentLayout(that));
 
500
    }
 
501
    return d->lout;
 
502
}
 
503
 
 
504
 
 
505
/*!
 
506
    Returns meta information about the document of the type specified by
 
507
    \a info.
 
508
 
 
509
    \sa setMetaInformation()
 
510
*/
 
511
QString QTextDocument::metaInformation(MetaInformation info) const
 
512
{
 
513
    if (info != DocumentTitle)
 
514
        return QString();
 
515
    Q_D(const QTextDocument);
 
516
    return d->config()->title;
 
517
}
 
518
 
 
519
/*!
 
520
    Sets the document's meta information of the type specified by \a info
 
521
    to the given \a string.
 
522
 
 
523
    \sa metaInformation()
 
524
*/
 
525
void QTextDocument::setMetaInformation(MetaInformation info, const QString &string)
 
526
{
 
527
    if (info != DocumentTitle)
 
528
        return;
 
529
    Q_D(QTextDocument);
 
530
    d->config()->title = string;
 
531
}
 
532
 
 
533
/*!
 
534
    Returns the plain text contained in the document. If you want
 
535
    formatting information use a QTextCursor instead.
 
536
 
 
537
    \sa toHtml()
 
538
*/
 
539
QString QTextDocument::toPlainText() const
 
540
{
 
541
    Q_D(const QTextDocument);
 
542
    QString txt = d->plainText();
 
543
    qt_replace_special_text_characters(&txt);
 
544
    return txt;
 
545
}
 
546
 
 
547
/*!
 
548
    Replaces the entire contents of the document with the given plain
 
549
    \a text.
 
550
 
 
551
    \sa setHtml()
 
552
*/
 
553
void QTextDocument::setPlainText(const QString &text)
 
554
{
 
555
    QTextDocumentFragment fragment = QTextDocumentFragment::fromPlainText(text);
 
556
    QTextCursor cursor(this);
 
557
    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
 
558
    setUndoRedoEnabled(false);
 
559
    cursor.insertFragment(fragment);
 
560
    setUndoRedoEnabled(true);
 
561
}
 
562
 
 
563
/*!
 
564
    Replaces the entire contents of the document with the given
 
565
    HTML-formatted text in the \a html string.
 
566
 
 
567
    The HTML formatting is respected as much as possible; for example,
 
568
    "<b>bold</b> text" will produce text where the first word has a font
 
569
    weight that gives it a bold appearance: "\bold{bold} text".
 
570
 
 
571
    \sa setPlainText()
 
572
*/
 
573
void QTextDocument::setHtml(const QString &html)
 
574
{
 
575
    QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(html);
 
576
    QTextCursor cursor(this);
 
577
    cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
 
578
    setUndoRedoEnabled(false);
 
579
    cursor.insertFragment(fragment);
 
580
    setUndoRedoEnabled(true);
 
581
}
 
582
 
 
583
/*!
 
584
    \enum QTextDocument::FindFlag
 
585
 
 
586
    This enum describes the options available to QTextDocument's find function. The options
 
587
    can be OR-red together from the following list:
 
588
 
 
589
    \value FindBackward
 
590
    \value FindCaseSensitively By default find works case insensitive. Specifying this option
 
591
    changes the behaviour to a case sensitive find operation.
 
592
    \value FindWholeWords Makes find match only complete words.
 
593
*/
 
594
 
 
595
/*!
 
596
    \enum QTextDocument::MetaInformation
 
597
 
 
598
    This enum describes the different types of meta information that can be
 
599
    added to a document.
 
600
 
 
601
    \value DocumentTitle    The title of the document.
 
602
 
 
603
    \sa metaInformation(), setMetaInformation()
 
604
*/
 
605
 
 
606
static bool findInBlock(const QTextBlock &block, const QString &text, const QString &expression, int offset,
 
607
                        QTextDocument::FindFlags options, QTextCursor &cursor)
 
608
{
 
609
    const Qt::CaseSensitivity cs = (options & QTextDocument::FindCaseSensitively) ? Qt::CaseSensitive : Qt::CaseInsensitive;
 
610
 
 
611
    const int idx = (options & QTextDocument::FindBackward) ?
 
612
                    text.lastIndexOf(expression, offset, cs) : text.indexOf(expression, offset, cs);
 
613
    if (idx == -1)
 
614
        return false;
 
615
 
 
616
    if (options & QTextDocument::FindWholeWords) {
 
617
        const int start = idx;
 
618
        const int end = start + expression.length();
 
619
        if ((start != 0 && text.at(start - 1).isLetterOrNumber())
 
620
                || (end != text.length() && text.at(end).isLetterOrNumber()))
 
621
            return false;
 
622
    }
 
623
 
 
624
    cursor = QTextCursor(block.docHandle(), block.position() + idx);
 
625
    cursor.setPosition(cursor.position() + expression.length(), QTextCursor::KeepAnchor);
 
626
    return true;
 
627
}
 
628
 
 
629
/*!
 
630
    \fn QTextCursor QTextDocument::find(const QString &expr, int position, FindFlags options) const
 
631
 
 
632
    \overload
 
633
 
 
634
    Finds the next occurrence of the string, \a expr, in the document.
 
635
    The search starts at the given \a position, and proceeds forwards
 
636
    through the document unless specified otherwise in the search options.
 
637
    The \a options control the type of search performed.
 
638
 
 
639
    Returns a cursor with the match selected if \a expr was found; otherwise
 
640
    returns a null cursor.
 
641
 
 
642
    If the \a position is 0 (the default) the search begins from the beginning
 
643
    of the document; otherwise it begins at the specified position.
 
644
*/
 
645
QTextCursor QTextDocument::find(const QString &expr, int from, FindFlags options) const
 
646
{
 
647
    Q_D(const QTextDocument);
 
648
 
 
649
    if (expr.isEmpty())
 
650
        return QTextCursor();
 
651
 
 
652
    int pos = from;
 
653
 
 
654
    QTextCursor cursor;
 
655
    QTextBlock block = d->blocksFind(pos);
 
656
 
 
657
    if (!(options & FindBackward)) {
 
658
        while (block.isValid()) {
 
659
            int blockOffset = qMax(0, pos - block.position());
 
660
            const QString blockText = block.text();
 
661
 
 
662
            const int blockLength = block.length();
 
663
            while (blockOffset < blockLength) {
 
664
                if (findInBlock(block, blockText, expr, blockOffset, options, cursor))
 
665
                    return cursor;
 
666
 
 
667
                blockOffset += expr.length();
 
668
            }
 
669
 
 
670
            block = block.next();
 
671
        }
 
672
    } else {
 
673
        while (block.isValid()) {
 
674
            int blockOffset = pos - block.position() - expr.size() - 1;
 
675
            if (blockOffset > block.length())
 
676
                blockOffset = block.length() - 1;
 
677
 
 
678
            const QString blockText = block.text();
 
679
 
 
680
            while (blockOffset >= 0) {
 
681
                if (findInBlock(block, blockText, expr, blockOffset, options, cursor))
 
682
                    return cursor;
 
683
 
 
684
                blockOffset -= expr.length();
 
685
            }
 
686
 
 
687
            block = block.previous();
 
688
        }
 
689
    }
 
690
 
 
691
    return QTextCursor();
 
692
}
 
693
 
 
694
/*!
 
695
    \fn QTextCursor QTextDocument::find(const QString &expr, const QTextCursor &cursor, FindFlags options) const
 
696
 
 
697
    Finds the next occurrence of the string, \a expr, in the document.
 
698
    The search starts at the position of the given \a cursor, and proceeds
 
699
    forwards through the document unless specified otherwise in the search
 
700
    options. The \a options control the type of search performed.
 
701
 
 
702
    Returns a cursor with the match selected if \a expr was found; otherwise
 
703
    returns a null cursor.
 
704
 
 
705
    If the given \a cursor has a selection, the search begins after the
 
706
    selection; otherwise it begins at the cursor's position.
 
707
 
 
708
    By default the search is case-sensitive, and can match text anywhere in the
 
709
    document.
 
710
*/
 
711
QTextCursor QTextDocument::find(const QString &expr, const QTextCursor &from, FindFlags options) const
 
712
{
 
713
    const int pos = (from.isNull() ? 0 : from.selectionEnd());
 
714
    return find(expr, pos, options);
 
715
}
 
716
 
 
717
 
 
718
 
 
719
/*!
 
720
    \fn QTextObject *QTextDocument::createObject(const QTextFormat &format)
 
721
 
 
722
    Creates and returns a new document object (a QTextObject), based
 
723
    on the given \a format.
 
724
 
 
725
    QTextObjects will always get created through this method, so you
 
726
    must reimplement it if you use custom text objects inside your document.
 
727
*/
 
728
QTextObject *QTextDocument::createObject(const QTextFormat &f)
 
729
{
 
730
    QTextObject *obj = 0;
 
731
    if (f.isListFormat())
 
732
        obj = new QTextList(this);
 
733
    else if (f.isTableFormat())
 
734
        obj = new QTextTable(this);
 
735
    else if (f.isFrameFormat())
 
736
        obj = new QTextFrame(this);
 
737
 
 
738
    return obj;
 
739
}
 
740
 
 
741
/*!
 
742
    \internal
 
743
 
 
744
    Returns the frame that contains the text cursor position \a pos.
 
745
*/
 
746
QTextFrame *QTextDocument::frameAt(int pos) const
 
747
{
 
748
    Q_D(const QTextDocument);
 
749
    return d->frameAt(pos);
 
750
}
 
751
 
 
752
/*!
 
753
    Returns the document's root frame.
 
754
*/
 
755
QTextFrame *QTextDocument::rootFrame() const
 
756
{
 
757
    Q_D(const QTextDocument);
 
758
    return d->rootFrame();
 
759
}
 
760
 
 
761
/*!
 
762
    Returns the text object associated with the given \a objectIndex.
 
763
*/
 
764
QTextObject *QTextDocument::object(int objectIndex) const
 
765
{
 
766
    Q_D(const QTextDocument);
 
767
    return d->objectForIndex(objectIndex);
 
768
}
 
769
 
 
770
/*!
 
771
    Returns the text object associated with the format \a f.
 
772
*/
 
773
QTextObject *QTextDocument::objectForFormat(const QTextFormat &f) const
 
774
{
 
775
    Q_D(const QTextDocument);
 
776
    return d->objectForFormat(f);
 
777
}
 
778
 
 
779
 
 
780
/*!
 
781
    Returns the text block that contains the \a{pos}-th character.
 
782
*/
 
783
QTextBlock QTextDocument::findBlock(int pos) const
 
784
{
 
785
    Q_D(const QTextDocument);
 
786
    return QTextBlock(docHandle(), d->blockMap().findNode(pos));
 
787
}
 
788
 
 
789
/*!
 
790
    Returns the document's first text block.
 
791
*/
 
792
QTextBlock QTextDocument::begin() const
 
793
{
 
794
    Q_D(const QTextDocument);
 
795
    return QTextBlock(docHandle(), d->blockMap().begin().n);
 
796
}
 
797
 
 
798
/*!
 
799
    Returns the document's last text block.
 
800
*/
 
801
QTextBlock QTextDocument::end() const
 
802
{
 
803
    return QTextBlock(docHandle(), 0);
 
804
}
 
805
 
 
806
/*!
 
807
    \property QTextDocument::pageSize
 
808
    \brief the page size that should be used for layouting the document
 
809
 
 
810
    \sa modificationChanged()
 
811
*/
 
812
 
 
813
void QTextDocument::setPageSize(const QSizeF &size)
 
814
{
 
815
    Q_D(QTextDocument);
 
816
    d->pageSize = size;
 
817
    documentLayout()->documentChanged(0, 0, d->length());
 
818
}
 
819
 
 
820
QSizeF QTextDocument::pageSize() const
 
821
{
 
822
    Q_D(const QTextDocument);
 
823
    return d->pageSize;
 
824
}
 
825
 
 
826
/*!
 
827
  returns the number of pages in this document.
 
828
*/
 
829
int QTextDocument::pageCount() const
 
830
{
 
831
    return documentLayout()->pageCount();
 
832
}
 
833
 
 
834
/*!
 
835
    Sets the default \a font to use in the document layout.
 
836
*/
 
837
void QTextDocument::setDefaultFont(const QFont &font)
 
838
{
 
839
    Q_D(QTextDocument);
 
840
    d->defaultFont = font;
 
841
    documentLayout()->documentChanged(0, 0, d->length());
 
842
}
 
843
 
 
844
/*!
 
845
    Returns the default font to be used in the document layout.
 
846
*/
 
847
QFont QTextDocument::defaultFont() const
 
848
{
 
849
    Q_D(const QTextDocument);
 
850
    return d->defaultFont;
 
851
}
 
852
 
 
853
/*!
 
854
    \fn QTextDocument::modificationChanged(bool changed)
 
855
 
 
856
    This signal is emitted whenever the content of the document
 
857
    changes in a way that affects the modification state. If \a
 
858
    changed is true if the document has been modified; otherwise it is
 
859
    false.
 
860
 
 
861
    For example calling setModified(false) on a document and then
 
862
    inserting text causes the signal to get emitted. If you undo that
 
863
    operation, causing the document to return to its original
 
864
    unmodified state, the signal will get emitted again.
 
865
*/
 
866
 
 
867
/*!
 
868
    \property QTextDocument::modified
 
869
    \brief whether the document has been modified by the user
 
870
 
 
871
    \sa modificationChanged()
 
872
*/
 
873
 
 
874
bool QTextDocument::isModified() const
 
875
{
 
876
    return docHandle()->isModified();
 
877
}
 
878
 
 
879
void QTextDocument::setModified(bool m)
 
880
{
 
881
    docHandle()->setModified(m);
 
882
}
 
883
 
 
884
/*!
 
885
    Prints the document to the given \a printer. The QPrinter must be
 
886
    set up before being used with this function.
 
887
 
 
888
    This is only a convenience method to print the whole document to the printer.
 
889
*/
 
890
void QTextDocument::print(QPrinter *printer) const
 
891
{
 
892
    QPainter p(printer);
 
893
 
 
894
    // Check that there is a valid device to print to.
 
895
    if (!p.device()) return;
 
896
 
 
897
    const int dpiy = p.device()->logicalDpiY();
 
898
    const int margin = (int) ((2/2.54)*dpiy); // 2 cm margins
 
899
    QRectF body(margin, margin, p.device()->width() - 2*margin, p.device()->height() - 2*margin);
 
900
 
 
901
    QTextDocument *doc = clone();
 
902
    QAbstractTextDocumentLayout *layout = doc->documentLayout();
 
903
    QFont font(doc->defaultFont());
 
904
    font.setPointSize(10); // we define 10pt to be a nice base size for printing
 
905
    doc->setDefaultFont(font);
 
906
    layout->setPaintDevice(printer);
 
907
    doc->setPageSize(body.size());
 
908
 
 
909
    QRectF view(0, 0, body.width(), body.height());
 
910
    p.translate(body.left(), body.top());
 
911
 
 
912
    int page = 1;
 
913
    do {
 
914
        QAbstractTextDocumentLayout::PaintContext ctx;
 
915
        p.setClipRect(view);
 
916
        ctx.clip = view;
 
917
        layout->draw(&p, ctx);
 
918
 
 
919
        p.setClipping(false);
 
920
        p.setFont(font);
 
921
        QString pageString = QString::number(page);
 
922
        p.drawText(qRound(view.right() - p.fontMetrics().width(pageString)),
 
923
                   qRound(view.bottom() + p.fontMetrics().ascent() + 5*dpiy/72), pageString);
 
924
 
 
925
        view.translate(0, body.height());
 
926
        p.translate(0 , -body.height());
 
927
 
 
928
        if (view.top() >= layout->documentSize().height())
 
929
            break;
 
930
 
 
931
        printer->newPage();
 
932
        page++;
 
933
    } while (true);
 
934
 
 
935
    delete doc;
 
936
}
 
937
 
 
938
/*!
 
939
    \enum QTextDocument::ResourceType
 
940
 
 
941
    This enum describes the types of resources that can be loaded by
 
942
    QTextDocument's loadResource() function.
 
943
 
 
944
    \value HtmlResource  The resource contains HTML.
 
945
    \value ImageResource The resource contains image data.
 
946
    \value UserResource  The first available value for user defined
 
947
                         resource types.
 
948
 
 
949
    \sa loadResource()
 
950
*/
 
951
 
 
952
/*!
 
953
    Returns data of the specified \a type from the resource with the
 
954
    given \a name.
 
955
 
 
956
    This function is called by the rich text engine to request data that isn't
 
957
    directly stored by QTextDocument, but still associated with it. For example,
 
958
    images are referenced indirectly by the name attribute of a QTextImageFormat
 
959
    object.
 
960
 
 
961
    Resources are cached internally in the document. If a resource can
 
962
    not be found in the cache, loadResource is called to try to load
 
963
    the resource. loadResource should then use addResource to add the
 
964
    resource to the cache.
 
965
*/
 
966
QVariant QTextDocument::resource(int type, const QUrl &name) const
 
967
{
 
968
    Q_D(const QTextDocument);
 
969
    QVariant r = d->resources.value(name);
 
970
    if (!r.isValid())
 
971
        r = const_cast<QTextDocument *>(this)->loadResource(type, name);
 
972
    return r;
 
973
}
 
974
 
 
975
/*!
 
976
    Adds the resource \a resource to the resource cache, using \a
 
977
    type and \a name as identifiers.
 
978
*/
 
979
void QTextDocument::addResource(int type, const QUrl &name, const QVariant &resource)
 
980
{
 
981
    Q_UNUSED(type);
 
982
    Q_D(QTextDocument);
 
983
    d->resources.insert(name, resource);
 
984
}
 
985
 
 
986
/*!
 
987
    Loads data of the specified \a type from the resource with the
 
988
    given \a name.
 
989
 
 
990
    This function is called by the rich text engine to request data that isn't
 
991
    directly stored by QTextDocument, but still associated with it. For example,
 
992
    images are referenced indirectly by the name attribute of a QTextImageFormat
 
993
    object.
 
994
 
 
995
    When called by Qt, \a type is one of the values of
 
996
    QTextDocument::ResourceType.
 
997
 
 
998
    If the QTextDocument is a child object of a QTextEdit, QTextBrowser,
 
999
    or a QTextDocument itself then the default implementation tries
 
1000
    to retrieve the data from the parent.
 
1001
*/
 
1002
QVariant QTextDocument::loadResource(int type, const QUrl &name)
 
1003
{
 
1004
    QVariant r;
 
1005
    if (QTextDocument *doc = qobject_cast<QTextDocument *>(parent()))
 
1006
        r = doc->loadResource(type, name);
 
1007
    else if (QTextEdit *edit = qobject_cast<QTextEdit *>(parent()))
 
1008
        r = edit->loadResource(type, name);
 
1009
    if (!r.isNull())
 
1010
        addResource(type, name, r);
 
1011
    return r;
 
1012
}
 
1013
 
 
1014
static QTextFormat formatDifference(const QTextFormat &from, const QTextFormat &to)
 
1015
{
 
1016
    QTextFormat diff = to;
 
1017
 
 
1018
    const QMap<int, QVariant> props = to.properties();
 
1019
    for (QMap<int, QVariant>::ConstIterator it = props.begin(), end = props.end();
 
1020
         it != end; ++it)
 
1021
        if (it.value() == from.property(it.key()))
 
1022
            diff.clearProperty(it.key());
 
1023
 
 
1024
    return diff;
 
1025
}
 
1026
 
 
1027
class QTextHtmlExporter
 
1028
{
 
1029
public:
 
1030
    QTextHtmlExporter(const QTextDocument *_doc);
 
1031
 
 
1032
    QString toHtml(const QByteArray &encoding);
 
1033
 
 
1034
private:
 
1035
    void emitFrame(QTextFrame::Iterator frameIt);
 
1036
    void emitBlock(const QTextBlock &block);
 
1037
    void emitTable(const QTextTable *table);
 
1038
    void emitFragment(const QTextFragment &fragment);
 
1039
 
 
1040
    void emitBlockAttributes(const QTextBlock &block);
 
1041
    bool emitCharFormatStyle(const QTextCharFormat &format);
 
1042
    void emitTextLength(const char *attribute, const QTextLength &length);
 
1043
    void emitAlignment(Qt::Alignment alignment);
 
1044
    void emitFloatStyle(QTextFrameFormat::Position pos);
 
1045
    void emitAttribute(const char *attribute, const QString &value);
 
1046
 
 
1047
    QString html;
 
1048
    QTextCharFormat defaultCharFormat;
 
1049
    const QTextDocument *doc;
 
1050
};
 
1051
 
 
1052
QTextHtmlExporter::QTextHtmlExporter(const QTextDocument *_doc)
 
1053
    : doc(_doc)
 
1054
{
 
1055
    const QFont defaultFont = doc->defaultFont();
 
1056
    defaultCharFormat.setFont(defaultFont);
 
1057
}
 
1058
 
 
1059
/*!
 
1060
    Returns the document in HTML format. The conversion may not be
 
1061
    perfect, especially for complex documents, due to the limitations
 
1062
    of HTML.
 
1063
*/
 
1064
QString QTextHtmlExporter::toHtml(const QByteArray &encoding)
 
1065
{
 
1066
    html = QLatin1String("<html><head><meta name=\"qrichtext\" content=\"1\" />");
 
1067
 
 
1068
    if (!encoding.isEmpty())
 
1069
        html += QString("<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%1\" />").arg(QString::fromAscii(encoding));
 
1070
 
 
1071
    QString title  = doc->metaInformation(QTextDocument::DocumentTitle);
 
1072
    if (!title.isEmpty())
 
1073
        html += "<title>" + title + "</title>";
 
1074
    html += QString("</head><body style=\" white-space: pre-wrap; font-family:%1; font-weight:%2; font-style:%3; text-decoration:none;\"")
 
1075
            .arg(defaultCharFormat.fontFamily())
 
1076
            .arg(defaultCharFormat.fontWeight() * 8)
 
1077
            .arg(defaultCharFormat.fontItalic() ? "italic" : "normal");
 
1078
 
 
1079
    const QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
 
1080
    QBrush bg = fmt.background();
 
1081
    if (bg != Qt::NoBrush)
 
1082
        emitAttribute("bgcolor", bg.color().name());
 
1083
 
 
1084
    html += QLatin1Char('>');
 
1085
 
 
1086
    emitFrame(doc->rootFrame()->begin());
 
1087
    html += QLatin1String("</body></html>");
 
1088
    return html;
 
1089
}
 
1090
 
 
1091
void QTextHtmlExporter::emitAttribute(const char *attribute, const QString &value)
 
1092
{
 
1093
    html += QLatin1Char(' ');
 
1094
    html += attribute;
 
1095
    html += QLatin1String("=\"");
 
1096
    html += value;
 
1097
    html += QLatin1Char('"');
 
1098
}
 
1099
 
 
1100
bool QTextHtmlExporter::emitCharFormatStyle(const QTextCharFormat &format)
 
1101
{
 
1102
    bool attributesEmitted = false;
 
1103
 
 
1104
    {
 
1105
        const QString family = format.fontFamily();
 
1106
        if (!family.isEmpty() && family != defaultCharFormat.fontFamily()) {
 
1107
            html += QLatin1String(" font-family:");
 
1108
            html += family;
 
1109
            html += QLatin1Char(';');
 
1110
            attributesEmitted = true;
 
1111
        }
 
1112
    }
 
1113
 
 
1114
    if (format.hasProperty(QTextFormat::FontPointSize)
 
1115
        && format.fontPointSize() != defaultCharFormat.fontPointSize()) {
 
1116
        html += QLatin1String(" font-size:");
 
1117
        html += QString::number(format.fontPointSize());
 
1118
        html += QLatin1String("pt;");
 
1119
        attributesEmitted = true;
 
1120
    }
 
1121
 
 
1122
    if (format.fontWeight() != defaultCharFormat.fontWeight()) {
 
1123
        html += QLatin1String(" font-weight:");
 
1124
        html += QString::number(format.fontWeight() * 8);
 
1125
        html += QLatin1Char(';');
 
1126
        attributesEmitted = true;
 
1127
    }
 
1128
 
 
1129
    if (format.fontItalic() != defaultCharFormat.fontItalic()) {
 
1130
        html += QLatin1String(" font-style:");
 
1131
        html += (format.fontItalic() ? QLatin1String("italic") : QLatin1String("normal"));
 
1132
        html += QLatin1Char(';');
 
1133
        attributesEmitted = true;
 
1134
    }
 
1135
 
 
1136
    QLatin1String decorationTag(" text-decoration:");
 
1137
    html += decorationTag;
 
1138
    bool hasDecoration = false;
 
1139
    bool atLeastOneDecorationSet = false;
 
1140
 
 
1141
    if (format.fontUnderline() != defaultCharFormat.fontUnderline()) {
 
1142
        hasDecoration = true;
 
1143
        if (format.fontUnderline()) {
 
1144
            html += QLatin1String(" underline");
 
1145
            atLeastOneDecorationSet = true;
 
1146
        }
 
1147
    }
 
1148
 
 
1149
    if (format.fontOverline() != defaultCharFormat.fontOverline()) {
 
1150
        hasDecoration = true;
 
1151
        if (format.fontOverline()) {
 
1152
            html += QLatin1String(" overline");
 
1153
            atLeastOneDecorationSet = true;
 
1154
        }
 
1155
    }
 
1156
 
 
1157
    if (format.fontStrikeOut() != defaultCharFormat.fontStrikeOut()) {
 
1158
        hasDecoration = true;
 
1159
        if (format.fontStrikeOut()) {
 
1160
            html += QLatin1String(" line-through");
 
1161
            atLeastOneDecorationSet = true;
 
1162
        }
 
1163
    }
 
1164
 
 
1165
    if (hasDecoration) {
 
1166
        if (!atLeastOneDecorationSet)
 
1167
            html += QLatin1String("none");
 
1168
        html += QLatin1Char(';');
 
1169
        attributesEmitted = true;
 
1170
    } else {
 
1171
        html.chop(qstrlen(decorationTag.latin1()));
 
1172
    }
 
1173
 
 
1174
    if (format.foreground() != defaultCharFormat.foreground()) {
 
1175
        html += QLatin1String(" color:");
 
1176
        html += format.foreground().color().name();
 
1177
        html += QLatin1Char(';');
 
1178
        attributesEmitted = true;
 
1179
    }
 
1180
 
 
1181
    if (format.verticalAlignment() != defaultCharFormat.verticalAlignment()) {
 
1182
        html += QLatin1String(" vertical-align:");
 
1183
 
 
1184
        QTextCharFormat::VerticalAlignment valign = format.verticalAlignment();
 
1185
        if (valign == QTextCharFormat::AlignSubScript)
 
1186
            html += QLatin1String("sub");
 
1187
        else if (valign == QTextCharFormat::AlignSuperScript)
 
1188
            html += QLatin1String("super");
 
1189
 
 
1190
        html += QLatin1Char(';');
 
1191
        attributesEmitted = true;
 
1192
    }
 
1193
 
 
1194
    return attributesEmitted;
 
1195
}
 
1196
 
 
1197
void QTextHtmlExporter::emitTextLength(const char *attribute, const QTextLength &length)
 
1198
{
 
1199
    if (length.type() == QTextLength::VariableLength) // default
 
1200
        return;
 
1201
 
 
1202
    html += QLatin1Char(' ');
 
1203
    html += attribute;
 
1204
    html += QLatin1String("=\"");
 
1205
    html += QString::number(length.rawValue());
 
1206
 
 
1207
    if (length.type() == QTextLength::PercentageLength)
 
1208
        html += QLatin1String("%\"");
 
1209
    else
 
1210
        html += QLatin1String("\"");
 
1211
}
 
1212
 
 
1213
void QTextHtmlExporter::emitAlignment(Qt::Alignment alignment)
 
1214
{
 
1215
    switch (alignment & Qt::AlignHorizontal_Mask) {
 
1216
        case Qt::AlignLeft: break;
 
1217
        case Qt::AlignRight: html += QLatin1String(" align='right'"); break;
 
1218
        case Qt::AlignHCenter: html += QLatin1String(" align='center'"); break;
 
1219
        case Qt::AlignJustify: html += QLatin1String(" align='justify'"); break;
 
1220
    }
 
1221
}
 
1222
 
 
1223
void QTextHtmlExporter::emitFloatStyle(QTextFrameFormat::Position pos)
 
1224
{
 
1225
    if (pos == QTextFrameFormat::InFlow)
 
1226
        return;
 
1227
 
 
1228
    html += QLatin1String(" style=\"float:");
 
1229
    if (pos == QTextFrameFormat::FloatLeft)
 
1230
        html += QLatin1String(" left;\"");
 
1231
    else if (pos == QTextFrameFormat::FloatRight)
 
1232
        html += QLatin1String(" right;\"");
 
1233
    else
 
1234
        Q_ASSERT_X(0, "QTextHtmlExporter::emitFloatStyle()", "pos should be a valid enum type");
 
1235
}
 
1236
 
 
1237
void QTextHtmlExporter::emitFragment(const QTextFragment &fragment)
 
1238
{
 
1239
    const QTextCharFormat format = fragment.charFormat();
 
1240
 
 
1241
    if (format.hasProperty(QTextFormat::DocumentFragmentMark)
 
1242
        && (format.intProperty(QTextFormat::DocumentFragmentMark) & QTextDocumentFragmentPrivate::FragmentStart))
 
1243
        html += QLatin1String("<!--StartFragment-->");
 
1244
 
 
1245
    bool closeAnchor = false;
 
1246
 
 
1247
    if (format.isAnchor()) {
 
1248
        const QString name = format.anchorName();
 
1249
        if (!name.isEmpty()) {
 
1250
            html += QLatin1String("<a name=\"");
 
1251
            html += name;
 
1252
            html += QLatin1String("\"></a>");
 
1253
        }
 
1254
        const QString href = format.anchorHref();
 
1255
        if (!href.isEmpty()) {
 
1256
            html += QLatin1String("<a href=\"");
 
1257
            html += href;
 
1258
            html += QLatin1String("\">");
 
1259
            closeAnchor = true;
 
1260
        }
 
1261
    }
 
1262
 
 
1263
    QLatin1String styleTag("<span style=\"");
 
1264
    html += styleTag;
 
1265
 
 
1266
    const bool attributesEmitted = emitCharFormatStyle(format);
 
1267
    if (attributesEmitted)
 
1268
        html += QLatin1String("\">");
 
1269
    else
 
1270
        html.chop(qstrlen(styleTag.latin1()));
 
1271
 
 
1272
    QString txt = fragment.text();
 
1273
    if (txt.count() == 1 && txt.at(0) == QChar::ObjectReplacementCharacter) {
 
1274
        if (format.isImageFormat()) {
 
1275
            QTextImageFormat imgFmt = format.toImageFormat();
 
1276
 
 
1277
            html += QLatin1String("<img");
 
1278
 
 
1279
            if (imgFmt.hasProperty(QTextFormat::ImageName))
 
1280
                emitAttribute("src", imgFmt.name());
 
1281
 
 
1282
            if (imgFmt.hasProperty(QTextFormat::ImageWidth))
 
1283
                emitAttribute("width", QString::number(imgFmt.width()));
 
1284
 
 
1285
            if (imgFmt.hasProperty(QTextFormat::ImageHeight))
 
1286
                emitAttribute("height", QString::number(imgFmt.height()));
 
1287
 
 
1288
            if (QTextFrame *imageFrame = qobject_cast<QTextFrame *>(doc->objectForFormat(imgFmt)))
 
1289
                emitFloatStyle(imageFrame->frameFormat().position());
 
1290
 
 
1291
            html += QLatin1String(" />");
 
1292
        }
 
1293
    } else {
 
1294
        Q_ASSERT(!txt.contains(QChar::ObjectReplacementCharacter));
 
1295
 
 
1296
        txt = Qt::escape(txt);
 
1297
 
 
1298
        // split for [\n{LineSeparator}]
 
1299
        QString forcedLineBreakRegExp = QString::fromLatin1("[\\na]");
 
1300
        forcedLineBreakRegExp[3] = QChar::LineSeparator;
 
1301
 
 
1302
        const QStringList lines = txt.split(QRegExp(forcedLineBreakRegExp));
 
1303
        for (int i = 0; i < lines.count(); ++i) {
 
1304
            if (i > 0)
 
1305
                html += QLatin1String("<br />"); // space on purpose for compatibility with Netscape, Lynx & Co.
 
1306
            html += lines.at(i);
 
1307
        }
 
1308
    }
 
1309
 
 
1310
    if (attributesEmitted)
 
1311
        html += QLatin1String("</span>");
 
1312
 
 
1313
    if (closeAnchor)
 
1314
        html += QLatin1String("</a>");
 
1315
 
 
1316
    if (format.hasProperty(QTextFormat::DocumentFragmentMark)
 
1317
        && (format.intProperty(QTextFormat::DocumentFragmentMark) & QTextDocumentFragmentPrivate::FragmentEnd))
 
1318
        html += QLatin1String("<!--EndFragment-->");
 
1319
}
 
1320
 
 
1321
static bool isOrderedList(int style)
 
1322
{
 
1323
    return style == QTextListFormat::ListDecimal || style == QTextListFormat::ListLowerAlpha
 
1324
           || style == QTextListFormat::ListUpperAlpha;
 
1325
}
 
1326
 
 
1327
void QTextHtmlExporter::emitBlockAttributes(const QTextBlock &block)
 
1328
{
 
1329
    QTextBlockFormat format = block.blockFormat();
 
1330
    emitAlignment(format.alignment());
 
1331
 
 
1332
    Qt::LayoutDirection dir = format.layoutDirection();
 
1333
    if (dir == Qt::LeftToRight) {
 
1334
        // assume default to not bloat the html too much
 
1335
        // html += QLatin1String(" dir='ltr'");
 
1336
    } else {
 
1337
        html += QLatin1String(" dir='rtl'");
 
1338
    }
 
1339
 
 
1340
    QLatin1String style(" style=\"");
 
1341
    html += style;
 
1342
 
 
1343
    if (block.begin().atEnd()) {
 
1344
        html += "-qt-paragraph-type:empty;";
 
1345
    }
 
1346
 
 
1347
    html += QLatin1String(" margin-top:");
 
1348
    html += QString::number(format.topMargin());
 
1349
    html += QLatin1String("px;");
 
1350
 
 
1351
    html += QLatin1String(" margin-bottom:");
 
1352
    html += QString::number(format.bottomMargin());
 
1353
    html += QLatin1String("px;");
 
1354
 
 
1355
    html += QLatin1String(" margin-left:");
 
1356
    html += QString::number(format.leftMargin());
 
1357
    html += QLatin1String("px;");
 
1358
 
 
1359
    html += QLatin1String(" margin-right:");
 
1360
    html += QString::number(format.rightMargin());
 
1361
    html += QLatin1String("px;");
 
1362
 
 
1363
    html += QLatin1String(" -qt-block-indent:");
 
1364
    html += QString::number(format.indent());
 
1365
    html += QLatin1Char(';');
 
1366
 
 
1367
    html += QLatin1String(" text-indent:");
 
1368
    html += QString::number(format.indent());
 
1369
    html += QLatin1String("px;");
 
1370
 
 
1371
    // ### 'if' needed as long as the block char format of a block at pos == 0
 
1372
    // is equivalent to the char format at that position.
 
1373
    // later on in the piecetable that's not the case, that's when the block char
 
1374
    // fmt is at block.position() - 1
 
1375
    // When changing this also change the 'if' in emitBlock
 
1376
    if (block.position() > 0) {
 
1377
        QTextCharFormat diff = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
 
1378
        if (!diff.properties().isEmpty())
 
1379
            emitCharFormatStyle(diff);
 
1380
    }
 
1381
 
 
1382
    html += QLatin1Char('"');
 
1383
 
 
1384
    QBrush bg = format.background();
 
1385
    if (bg != Qt::NoBrush)
 
1386
        emitAttribute("bgcolor", bg.color().name());
 
1387
}
 
1388
 
 
1389
void QTextHtmlExporter::emitBlock(const QTextBlock &block)
 
1390
{
 
1391
    if (block.begin().atEnd()) {
 
1392
        // ### HACK, remove once QTextFrame::Iterator is fixed
 
1393
        int p = block.position();
 
1394
        if (p > 0)
 
1395
            --p;
 
1396
        QTextDocumentPrivate::FragmentIterator frag = doc->docHandle()->find(p);
 
1397
        QChar ch = doc->docHandle()->buffer().at(frag->stringPosition);
 
1398
        if (ch == QTextBeginningOfFrame
 
1399
            || ch == QTextEndOfFrame)
 
1400
            return;
 
1401
    }
 
1402
 
 
1403
    // save and later restore, in case we 'change' the default format by
 
1404
    // emitting block char format information
 
1405
    QTextCharFormat oldDefaultCharFormat = defaultCharFormat;
 
1406
 
 
1407
    QTextList *list = block.textList();
 
1408
    if (list) {
 
1409
        if (list->itemNumber(block) == 0) { // first item? emit <ul> or appropriate
 
1410
            const QTextListFormat format = list->format();
 
1411
            const int style = format.style();
 
1412
            switch (style) {
 
1413
                case QTextListFormat::ListDecimal: html += QLatin1String("<ol"); break;
 
1414
                case QTextListFormat::ListDisc: html += QLatin1String("<ul"); break;
 
1415
                case QTextListFormat::ListCircle: html += QLatin1String("<ul type=circle"); break;
 
1416
                case QTextListFormat::ListSquare: html += QLatin1String("<ul type=square"); break;
 
1417
                case QTextListFormat::ListLowerAlpha: html += QLatin1String("<ol type=a"); break;
 
1418
                case QTextListFormat::ListUpperAlpha: html += QLatin1String("<ol type=A"); break;
 
1419
                default: html += QLatin1String("<ul"); // ### should not happen
 
1420
            }
 
1421
 
 
1422
            if (format.hasProperty(QTextFormat::ListIndent)) {
 
1423
                html += QLatin1String(" style=\"-qt-list-indent: ");
 
1424
                html += QString::number(format.indent());
 
1425
                html += QLatin1String(";\"");
 
1426
            }
 
1427
 
 
1428
            html += QLatin1Char('>');
 
1429
        }
 
1430
 
 
1431
        html += QLatin1String("<li");
 
1432
 
 
1433
        const QTextCharFormat blockFmt = formatDifference(defaultCharFormat, block.charFormat()).toCharFormat();
 
1434
        if (!blockFmt.properties().isEmpty()) {
 
1435
            html += QLatin1String(" style=\"");
 
1436
            emitCharFormatStyle(blockFmt);
 
1437
            html += QLatin1Char('\"');
 
1438
 
 
1439
            defaultCharFormat.merge(block.charFormat());
 
1440
        }
 
1441
    }
 
1442
 
 
1443
    const bool pre = block.blockFormat().nonBreakableLines();
 
1444
    if (pre) {
 
1445
        if (list)
 
1446
            html += QLatin1Char('>');
 
1447
        html += QLatin1String("<pre");
 
1448
    } else if (!list) {
 
1449
        html += QLatin1String("<p");
 
1450
    }
 
1451
 
 
1452
    emitBlockAttributes(block);
 
1453
 
 
1454
    html += QLatin1Char('>');
 
1455
 
 
1456
    // ### 'if' needed as long as the block char format of a block at pos == 0
 
1457
    // is equivalent to the char format at that position.
 
1458
    // later on in the piecetable that's not the case, that's when the block char
 
1459
    // fmt is at block.position() - 1
 
1460
    // When changing this also change the 'if' in emitBlockAttributes
 
1461
    if (block.position() > 0) {
 
1462
        defaultCharFormat.merge(block.charFormat());
 
1463
    }
 
1464
 
 
1465
    for (QTextBlock::Iterator it = block.begin();
 
1466
         !it.atEnd(); ++it)
 
1467
        emitFragment(it.fragment());
 
1468
 
 
1469
    if (pre)
 
1470
        html += QLatin1String("</pre>");
 
1471
    else if (!list)
 
1472
        html += QLatin1String("</p>");
 
1473
 
 
1474
    if (list) {
 
1475
        if (list->itemNumber(block) == list->count() - 1) { // last item? close list
 
1476
            if (isOrderedList(list->format().style()))
 
1477
                html += QLatin1String("</ol>");
 
1478
            else
 
1479
                html += QLatin1String("</ul>");
 
1480
        }
 
1481
    }
 
1482
 
 
1483
    defaultCharFormat = oldDefaultCharFormat;
 
1484
}
 
1485
 
 
1486
void QTextHtmlExporter::emitTable(const QTextTable *table)
 
1487
{
 
1488
    QTextTableFormat format = table->format();
 
1489
 
 
1490
    html += QLatin1String("<table");
 
1491
 
 
1492
    if (format.hasProperty(QTextFormat::FrameBorder))
 
1493
        emitAttribute("border", QString::number(format.border()));
 
1494
 
 
1495
    emitFloatStyle(format.position());
 
1496
    emitAlignment(format.alignment());
 
1497
    emitTextLength("width", format.width());
 
1498
 
 
1499
    if (format.hasProperty(QTextFormat::TableCellSpacing))
 
1500
        emitAttribute("cellspacing", QString::number(format.cellSpacing()));
 
1501
    if (format.hasProperty(QTextFormat::TableCellPadding))
 
1502
        emitAttribute("cellpadding", QString::number(format.cellPadding()));
 
1503
 
 
1504
    QBrush bg = format.background();
 
1505
    if (bg != Qt::NoBrush)
 
1506
        emitAttribute("bgcolor", bg.color().name());
 
1507
 
 
1508
    html += QLatin1Char('>');
 
1509
 
 
1510
    const int rows = table->rows();
 
1511
    const int columns = table->columns();
 
1512
 
 
1513
    QVector<QTextLength> columnWidths = format.columnWidthConstraints();
 
1514
    if (columnWidths.isEmpty()) {
 
1515
        columnWidths.resize(columns);
 
1516
        columnWidths.fill(QTextLength());
 
1517
    }
 
1518
    Q_ASSERT(columnWidths.count() == columns);
 
1519
 
 
1520
    QVarLengthArray<bool> widthEmittedForColumn(columns);
 
1521
    for (int i = 0; i < columns; ++i)
 
1522
        widthEmittedForColumn[i] = false;
 
1523
 
 
1524
    for (int row = 0; row < rows; ++row) {
 
1525
        html += QLatin1String("<tr>");
 
1526
 
 
1527
        for (int col = 0; col < columns; ++col) {
 
1528
            const QTextTableCell cell = table->cellAt(row, col);
 
1529
 
 
1530
            // for col/rowspans
 
1531
            if (cell.row() != row)
 
1532
                break;
 
1533
 
 
1534
            if (cell.column() != col)
 
1535
                break;
 
1536
 
 
1537
            html += QLatin1String("<td");
 
1538
 
 
1539
            if (!widthEmittedForColumn[col]) {
 
1540
                emitTextLength("width", columnWidths.at(col));
 
1541
                widthEmittedForColumn[col] = true;
 
1542
            }
 
1543
 
 
1544
            if (cell.columnSpan() > 1)
 
1545
                emitAttribute("colspan", QString::number(cell.columnSpan()));
 
1546
 
 
1547
            if (cell.rowSpan() > 1)
 
1548
                emitAttribute("rowspan", QString::number(cell.rowSpan()));
 
1549
 
 
1550
            const QTextCharFormat cellFormat = cell.format();
 
1551
            QBrush bg = cellFormat.background();
 
1552
            if (bg != Qt::NoBrush)
 
1553
                emitAttribute("bgcolor", bg.color().name());
 
1554
 
 
1555
            html += QLatin1Char('>');
 
1556
 
 
1557
            emitFrame(cell.begin());
 
1558
 
 
1559
            html += QLatin1String("</td>");
 
1560
        }
 
1561
 
 
1562
        html += QLatin1String("</tr>");
 
1563
    }
 
1564
 
 
1565
    html += QLatin1String("</table>");
 
1566
}
 
1567
 
 
1568
void QTextHtmlExporter::emitFrame(QTextFrame::Iterator frameIt)
 
1569
{
 
1570
    if (!frameIt.atEnd()) {
 
1571
        QTextFrame::Iterator next = frameIt;
 
1572
        ++next;
 
1573
        if (next.atEnd()
 
1574
            && frameIt.currentFrame() == 0
 
1575
            && frameIt.parentFrame() != doc->rootFrame()
 
1576
            && frameIt.currentBlock().begin().atEnd())
 
1577
            return;
 
1578
    }
 
1579
 
 
1580
    for (QTextFrame::Iterator it = frameIt;
 
1581
         !it.atEnd(); ++it) {
 
1582
        if (QTextTable *table = qobject_cast<QTextTable *>(it.currentFrame()))
 
1583
            emitTable(table);
 
1584
        else if (it.currentBlock().isValid())
 
1585
            emitBlock(it.currentBlock());
 
1586
    }
 
1587
}
 
1588
 
 
1589
/*!
 
1590
    Returns a string containing an HTML representation of the document.
 
1591
 
 
1592
    The \a encoding parameter specifies the value for the charset attribute
 
1593
    in the html header. For example if 'utf-8' is specified then the
 
1594
    beginning of the generated html will look like this:
 
1595
    \code
 
1596
    <html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>...
 
1597
    \endcode
 
1598
 
 
1599
    If no encoding is specified then no such meta information is generated.
 
1600
 
 
1601
    If you later on convert the returned html string into a byte array for
 
1602
    transmission over a network or when saving to disk you should specify
 
1603
    the encoding you're going to use for the conversion to a byte array here.
 
1604
*/
 
1605
QString QTextDocument::toHtml(const QByteArray &encoding) const
 
1606
{
 
1607
    return QTextHtmlExporter(this).toHtml(encoding);
 
1608
}
 
1609
 
 
1610
/*!
 
1611
    Returns a vector of text formats for all the formats used in the document.
 
1612
*/
 
1613
QVector<QTextFormat> QTextDocument::allFormats() const
 
1614
{
 
1615
    Q_D(const QTextDocument);
 
1616
    return d->formatCollection()->formats;
 
1617
}
 
1618
 
 
1619
 
 
1620
/*!
 
1621
  \internal
 
1622
 
 
1623
  So that not all classes have to be friends of each other...
 
1624
*/
 
1625
QTextDocumentPrivate *QTextDocument::docHandle() const
 
1626
{
 
1627
    Q_D(const QTextDocument);
 
1628
    return const_cast<QTextDocumentPrivate *>(d);
 
1629
}