OSDN Git Service

add simple code to View Redrawing event
[pheasant/hexedit.git] / qtext / qtextdocumentfragment.cpp
1 /****************************************************************************
2 **
3 ** Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: Qt Software Information (qt-info@nokia.com)
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** Commercial Usage
9 ** Licensees holding valid Qt Commercial licenses may use this file in
10 ** accordance with the Qt Commercial License Agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and Nokia.
13 **
14 **
15 ** GNU General Public License Usage
16 ** Alternatively, this file may be used under the terms of the GNU
17 ** General Public License versions 2.0 or 3.0 as published by the Free
18 ** Software Foundation and appearing in the file LICENSE.GPL included in
19 ** the packaging of this file.  Please review the following information
20 ** to ensure GNU General Public Licensing requirements will be met:
21 ** http://www.fsf.org/licensing/licenses/info/GPLv2.html and
22 ** http://www.gnu.org/copyleft/gpl.html.  In addition, as a special
23 ** exception, Nokia gives you certain additional rights. These rights
24 ** are described in the Nokia Qt GPL Exception version 1.3, included in
25 ** the file GPL_EXCEPTION.txt in this package.
26 **
27 ** Qt for Windows(R) Licensees
28 ** As a special exception, Nokia, as the sole copyright holder for Qt
29 ** Designer, grants users of the Qt/Eclipse Integration plug-in the
30 ** right for the Qt/Eclipse Integration to link to functionality
31 ** provided by Qt Designer and its related libraries.
32 **
33 ** If you are unsure which license is appropriate for your use, please
34 ** contact the sales department at qt-sales@nokia.com.
35 **
36 ****************************************************************************/
37
38 #include "qtextdocumentfragment.h"
39 #include "qtextdocumentfragment_p.h"
40 #include "qtextcursor_p.h"
41 #include "qtextlist.h"
42 #include "private/qunicodetables_p.h"
43
44 #include <qdebug.h>
45 #include <qtextcodec.h>
46 #include <qbytearray.h>
47 #include <qdatastream.h>
48 #include <qdatetime.h>
49
50 QT_BEGIN_NAMESPACE
51
52 QTextCopyHelper::QTextCopyHelper(const QTextCursor &_source, const QTextCursor &_destination, bool forceCharFormat, const QTextCharFormat &fmt)
53     : formatCollection(*_destination.d->priv->formatCollection()), originalText(_source.d->priv->buffer())
54 {
55     src = _source.d->priv;
56     dst = _destination.d->priv;
57     insertPos = _destination.position();
58     this->forceCharFormat = forceCharFormat;
59     primaryCharFormatIndex = convertFormatIndex(fmt);
60     cursor = _source;
61 }
62
63 int QTextCopyHelper::convertFormatIndex(const QTextFormat &oldFormat, int objectIndexToSet)
64 {
65     QTextFormat fmt = oldFormat;
66     if (objectIndexToSet != -1) {
67         fmt.setObjectIndex(objectIndexToSet);
68     } else if (fmt.objectIndex() != -1) {
69         int newObjectIndex = objectIndexMap.value(fmt.objectIndex(), -1);
70         if (newObjectIndex == -1) {
71             QTextFormat objFormat = src->formatCollection()->objectFormat(fmt.objectIndex());
72             Q_ASSERT(objFormat.objectIndex() == -1);
73             newObjectIndex = formatCollection.createObjectIndex(objFormat);
74             objectIndexMap.insert(fmt.objectIndex(), newObjectIndex);
75         }
76         fmt.setObjectIndex(newObjectIndex);
77     }
78     int idx = formatCollection.indexForFormat(fmt);
79     Q_ASSERT(formatCollection.format(idx).type() == oldFormat.type());
80     return idx;
81 }
82
83 int QTextCopyHelper::appendFragment(int pos, int endPos, int objectIndex)
84 {
85     QTextDocumentPrivate::FragmentIterator fragIt = src->find(pos);
86     const QTextFragmentData * const frag = fragIt.value();
87
88     Q_ASSERT(objectIndex == -1
89              || (frag->size == 1 && src->formatCollection()->format(frag->format).objectIndex() != -1));
90
91     int charFormatIndex;
92     if (forceCharFormat)
93        charFormatIndex = primaryCharFormatIndex;
94     else
95        charFormatIndex = convertFormatIndex(frag->format, objectIndex);
96
97     const int inFragmentOffset = qMax(0, pos - fragIt.position());
98     int charsToCopy = qMin(int(frag->size - inFragmentOffset), endPos - pos);
99
100     QTextBlock nextBlock = src->blocksFind(pos + 1);
101
102     int blockIdx = -2;
103     if (nextBlock.position() == pos + 1) {
104         blockIdx = convertFormatIndex(nextBlock.blockFormat());
105     } else if (pos == 0 && insertPos == 0) {
106         dst->setBlockFormat(dst->blocksBegin(), dst->blocksBegin(), convertFormat(src->blocksBegin().blockFormat()).toBlockFormat());
107         dst->setCharFormat(-1, 1, convertFormat(src->blocksBegin().charFormat()).toCharFormat());
108     }
109
110     QString txtToInsert(originalText.constData() + frag->stringPosition + inFragmentOffset, charsToCopy);
111     if (txtToInsert.length() == 1
112         && (txtToInsert.at(0) == QChar::ParagraphSeparator
113             || txtToInsert.at(0) == QTextBeginningOfFrame
114             || txtToInsert.at(0) == QTextEndOfFrame
115            )
116        ) {
117         dst->insertBlock(txtToInsert.at(0), insertPos, blockIdx, charFormatIndex);
118         ++insertPos;
119     } else {
120         if (nextBlock.textList()) {
121             QTextBlock dstBlock = dst->blocksFind(insertPos);
122             if (!dstBlock.textList()) {
123                 blockIdx = convertFormatIndex(nextBlock.blockFormat());
124                 dst->insertBlock(insertPos, blockIdx, charFormatIndex);
125                 ++insertPos;
126             }
127         }
128         dst->insert(insertPos, txtToInsert, charFormatIndex);
129         const int userState = nextBlock.userState();
130         if (userState != -1)
131             dst->blocksFind(insertPos).setUserState(userState);
132         insertPos += txtToInsert.length();
133     }
134
135     return charsToCopy;
136 }
137
138 void QTextCopyHelper::appendFragments(int pos, int endPos)
139 {
140     Q_ASSERT(pos < endPos);
141
142     while (pos < endPos)
143         pos += appendFragment(pos, endPos);
144 }
145
146 void QTextCopyHelper::copy()
147 {
148     if (cursor.hasComplexSelection()) {
149         QTextTable *table = cursor.currentTable();
150         int row_start, col_start, num_rows, num_cols;
151         cursor.selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
152
153         QTextTableFormat tableFormat = table->format();
154         tableFormat.setColumns(num_cols);
155         tableFormat.clearColumnWidthConstraints();
156         const int objectIndex = dst->formatCollection()->createObjectIndex(tableFormat);
157
158         Q_ASSERT(row_start != -1);
159         for (int r = row_start; r < row_start + num_rows; ++r) {
160             for (int c = col_start; c < col_start + num_cols; ++c) {
161                 QTextTableCell cell = table->cellAt(r, c);
162                 const int rspan = cell.rowSpan();
163                 const int cspan = cell.columnSpan();
164                 if (rspan != 1) {
165                     int cr = cell.row();
166                     if (cr != r)
167                         continue;
168                 }
169                 if (cspan != 1) {
170                     int cc = cell.column();
171                     if (cc != c)
172                         continue;
173                 }
174
175                 // add the QTextBeginningOfFrame
176                 QTextCharFormat cellFormat = cell.format();
177                 if (r + rspan >= row_start + num_rows) {
178                     cellFormat.setTableCellRowSpan(row_start + num_rows - r);
179                 }
180                 if (c + cspan >= col_start + num_cols) {
181                     cellFormat.setTableCellColumnSpan(col_start + num_cols - c);
182                 }
183                 const int charFormatIndex = convertFormatIndex(cellFormat, objectIndex);
184
185                 int blockIdx = -2;
186                 const int cellPos = cell.firstPosition();
187                 QTextBlock block = src->blocksFind(cellPos);
188                 if (block.position() == cellPos) {
189                     blockIdx = convertFormatIndex(block.blockFormat());
190                 }
191
192                 dst->insertBlock(QTextBeginningOfFrame, insertPos, blockIdx, charFormatIndex);
193                 ++insertPos;
194
195                 // nothing to add for empty cells
196                 if (cell.lastPosition() > cellPos) {
197                     // add the contents
198                     appendFragments(cellPos, cell.lastPosition());
199                 }
200             }
201         }
202
203         // add end of table
204         int end = table->lastPosition();
205         appendFragment(end, end+1, objectIndex);
206     } else {
207         appendFragments(cursor.selectionStart(), cursor.selectionEnd());
208     }
209 }
210
211 QTextDocumentFragmentPrivate::QTextDocumentFragmentPrivate(const QTextCursor &_cursor)
212     : ref(1), doc(new QTextDocument), importedFromPlainText(false)
213 {
214     doc->setUndoRedoEnabled(false);
215
216     if (!_cursor.hasSelection())
217         return;
218
219     doc->docHandle()->beginEditBlock();
220     QTextCursor destCursor(doc);
221     QTextCopyHelper(_cursor, destCursor).copy();
222     doc->docHandle()->endEditBlock();
223
224     if (_cursor.d)
225         doc->docHandle()->mergeCachedResources(_cursor.d->priv);
226 }
227
228 void QTextDocumentFragmentPrivate::insert(QTextCursor &_cursor) const
229 {
230     if (_cursor.isNull())
231         return;
232
233     QTextDocumentPrivate *destPieceTable = _cursor.d->priv;
234     destPieceTable->beginEditBlock();
235
236     QTextCursor sourceCursor(doc);
237     sourceCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
238     QTextCopyHelper(sourceCursor, _cursor, importedFromPlainText, _cursor.charFormat()).copy();
239
240     destPieceTable->endEditBlock();
241 }
242
243 /*!
244     \class QTextDocumentFragment
245     \reentrant
246
247     \brief The QTextDocumentFragment class represents a piece of formatted text
248     from a QTextDocument.
249
250     \ingroup text
251     \ingroup shared
252
253     A QTextDocumentFragment is a fragment of rich text, that can be inserted into
254     a QTextDocument. A document fragment can be created from a
255     QTextDocument, from a QTextCursor's selection, or from another
256     document fragment. Document fragments can also be created by the
257     static functions, fromPlainText() and fromHtml().
258
259     The contents of a document fragment can be obtained as plain text
260     by using the toPlainText() function, or it can be obtained as HTML
261     with toHtml().
262 */
263
264
265 /*!
266     Constructs an empty QTextDocumentFragment.
267
268     \sa isEmpty()
269 */
270 QTextDocumentFragment::QTextDocumentFragment()
271     : d(0)
272 {
273 }
274
275 /*!
276     Converts the given \a document into a QTextDocumentFragment.
277     Note that the QTextDocumentFragment only stores the document contents, not meta information
278     like the document's title.
279 */
280 QTextDocumentFragment::QTextDocumentFragment(const QTextDocument *document)
281     : d(0)
282 {
283     if (!document)
284         return;
285
286     QTextCursor cursor(const_cast<QTextDocument *>(document));
287     cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
288     d = new QTextDocumentFragmentPrivate(cursor);
289 }
290
291 /*!
292     Creates a QTextDocumentFragment from the \a{cursor}'s selection.
293     If the cursor doesn't have a selection, the created fragment is empty.
294
295     \sa isEmpty() QTextCursor::selection()
296 */
297 QTextDocumentFragment::QTextDocumentFragment(const QTextCursor &cursor)
298     : d(0)
299 {
300     if (!cursor.hasSelection())
301         return;
302
303     d = new QTextDocumentFragmentPrivate(cursor);
304 }
305
306 /*!
307     \fn QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &other)
308
309     Copy constructor. Creates a copy of the \a other fragment.
310 */
311 QTextDocumentFragment::QTextDocumentFragment(const QTextDocumentFragment &rhs)
312     : d(rhs.d)
313 {
314     if (d)
315         d->ref.ref();
316 }
317
318 /*!
319     \fn QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &other)
320
321     Assigns the \a other fragment to this fragment.
322 */
323 QTextDocumentFragment &QTextDocumentFragment::operator=(const QTextDocumentFragment &rhs)
324 {
325     if (rhs.d)
326         rhs.d->ref.ref();
327     if (d && !d->ref.deref())
328         delete d;
329     d = rhs.d;
330     return *this;
331 }
332
333 /*!
334     Destroys the document fragment.
335 */
336 QTextDocumentFragment::~QTextDocumentFragment()
337 {
338     if (d && !d->ref.deref())
339         delete d;
340 }
341
342 /*!
343     Returns true if the fragment is empty; otherwise returns false.
344 */
345 bool QTextDocumentFragment::isEmpty() const
346 {
347     return !d || !d->doc || d->doc->docHandle()->length() <= 1;
348 }
349
350 /*!
351     Returns the document fragment's text as plain text (i.e. with no
352     formatting information).
353
354     \sa toHtml()
355 */
356 QString QTextDocumentFragment::toPlainText() const
357 {
358     if (!d)
359         return QString();
360
361     return d->doc->toPlainText();
362 }
363
364 // #### Qt 5: merge with other overload
365 /*!
366     \overload
367 */
368
369 #ifndef QT_NO_TEXTHTMLPARSER
370
371 QString QTextDocumentFragment::toHtml() const
372 {
373     return toHtml(QByteArray());
374 }
375
376 /*!
377     \since 4.2
378
379     Returns the contents of the document fragment as HTML,
380     using the specified \a encoding (e.g., "UTF-8", "ISO 8859-1").
381
382     \sa toPlainText(), QTextDocument::toHtml(), QTextCodec
383 */
384 QString QTextDocumentFragment::toHtml(const QByteArray &encoding) const
385 {
386     if (!d)
387         return QString();
388
389     return QTextHtmlExporter(d->doc).toHtml(encoding, QTextHtmlExporter::ExportFragment);
390 }
391
392 #endif // QT_NO_TEXTHTMLPARSER
393
394 /*!
395     Returns a document fragment that contains the given \a plainText.
396
397     When inserting such a fragment into a QTextDocument the current char format of
398     the QTextCursor used for insertion is used as format for the text.
399 */
400 QTextDocumentFragment QTextDocumentFragment::fromPlainText(const QString &plainText)
401 {
402     QTextDocumentFragment res;
403
404     res.d = new QTextDocumentFragmentPrivate;
405     res.d->importedFromPlainText = true;
406     QTextCursor cursor(res.d->doc);
407     cursor.insertText(plainText);
408     return res;
409 }
410
411 static QTextListFormat::Style nextListStyle(QTextListFormat::Style style)
412 {
413     if (style == QTextListFormat::ListDisc)
414         return QTextListFormat::ListCircle;
415     else if (style == QTextListFormat::ListCircle)
416         return QTextListFormat::ListSquare;
417     return style;
418 }
419
420 #ifndef QT_NO_TEXTHTMLPARSER
421
422 QTextHtmlImporter::QTextHtmlImporter(QTextDocument *_doc, const QString &_html, ImportMode mode, const QTextDocument *resourceProvider)
423     : indent(0), compressNextWhitespace(PreserveWhiteSpace), doc(_doc), importMode(mode)
424 {
425     cursor = QTextCursor(doc);
426     wsm = QTextHtmlParserNode::WhiteSpaceNormal;
427
428     QString html = _html;
429     const int startFragmentPos = html.indexOf(QLatin1String("<!--StartFragment-->"));
430     if (startFragmentPos != -1) {
431         const int endFragmentPos = html.indexOf(QLatin1String("<!--EndFragment-->"));
432         if (startFragmentPos < endFragmentPos)
433             html = html.mid(startFragmentPos, endFragmentPos - startFragmentPos);
434         else
435             html = html.mid(startFragmentPos);
436
437         html.prepend(QLatin1String("<meta name=\"qrichtext\" content=\"1\" />"));
438     }
439
440     parse(html, resourceProvider ? resourceProvider : doc);
441 //    dumpHtml();
442 }
443
444 void QTextHtmlImporter::import()
445 {
446     cursor.beginEditBlock();
447     hasBlock = true;
448     forceBlockMerging = false;
449     compressNextWhitespace = RemoveWhiteSpace;
450     blockTagClosed = false;
451     for (currentNodeIdx = 0; currentNodeIdx < count(); ++currentNodeIdx) {
452         currentNode = &at(currentNodeIdx);
453         wsm = textEditMode ? QTextHtmlParserNode::WhiteSpacePreWrap : currentNode->wsm;
454
455         /*
456          * process each node in three stages:
457          * 1) check if the hierarchy changed and we therefore passed the
458          *    equivalent of a closing tag -> we may need to finish off
459          *    some structures like tables
460          *
461          * 2) check if the current node is a special node like a
462          *    <table>, <ul> or <img> tag that requires special processing
463          *
464          * 3) if the node should result in a QTextBlock create one and
465          *    finally insert text that may be attached to the node
466          */
467
468         /* emit 'closing' table blocks or adjust current indent level
469          * if we
470          *  1) are beyond the first node
471          *  2) the current node not being a child of the previous node
472          *      means there was a tag closing in the input html
473          */
474         if (currentNodeIdx > 0 && (currentNode->parent != currentNodeIdx - 1)) {
475             blockTagClosed = closeTag();
476             // visually collapse subsequent block tags, but if the element after the closed block tag
477             // is for example an inline element (!isBlock) we have to make sure we start a new paragraph by setting
478             // hasBlock to false.
479             if (blockTagClosed
480                 && !currentNode->isBlock()
481                 && currentNode->id != Html_unknown)
482             {
483                 hasBlock = false;
484             } else if (hasBlock) {
485                 // when collapsing subsequent block tags we need to clear the block format
486                 QTextBlockFormat blockFormat = currentNode->blockFormat;
487                 blockFormat.setIndent(indent);
488
489                 QTextBlockFormat oldFormat = cursor.blockFormat();
490                 if (oldFormat.hasProperty(QTextFormat::PageBreakPolicy)) {
491                     QTextFormat::PageBreakFlags pageBreak = oldFormat.pageBreakPolicy();
492                     if (pageBreak == QTextFormat::PageBreak_AlwaysAfter)
493                         /* We remove an empty paragrah that requested a page break after.
494                            moving that request to the next paragraph means we also need to make
495                             that a pagebreak before to keep the same visual appearance.
496                         */
497                         pageBreak = QTextFormat::PageBreak_AlwaysBefore;
498                     blockFormat.setPageBreakPolicy(pageBreak);
499                 }
500
501                 cursor.setBlockFormat(blockFormat);
502             }
503         }
504
505         if (currentNode->displayMode == QTextHtmlElement::DisplayNone) {
506             if (currentNode->id == Html_title)
507                 doc->setMetaInformation(QTextDocument::DocumentTitle, currentNode->text);
508             // ignore explicitly 'invisible' elements
509             continue;
510         }
511
512         if (processSpecialNodes() == ContinueWithNextNode)
513             continue;
514
515         // make sure there's a block for 'Blah' after <ul><li>foo</ul>Blah
516         if (blockTagClosed
517             && !hasBlock
518             && !currentNode->isBlock()
519             && !currentNode->text.isEmpty() && !currentNode->hasOnlyWhitespace()
520             && currentNode->displayMode == QTextHtmlElement::DisplayInline) {
521
522             QTextBlockFormat block = currentNode->blockFormat;
523             block.setIndent(indent);
524
525             appendBlock(block, currentNode->charFormat);
526
527             hasBlock = true;
528         }
529
530         if (currentNode->isBlock()) {
531             if (processBlockNode() == ContinueWithNextNode)
532                 continue;
533         }
534
535         if (currentNode->charFormat.isAnchor() && !currentNode->charFormat.anchorName().isEmpty()) {
536             namedAnchors.append(currentNode->charFormat.anchorName());
537         }
538
539         if (appendNodeText())
540             hasBlock = false; // if we actually appended text then we don't
541                               // have an empty block anymore
542     }
543
544     cursor.endEditBlock();
545 }
546
547 bool QTextHtmlImporter::appendNodeText()
548 {
549     const int initialCursorPosition = cursor.position();
550     QTextCharFormat format = currentNode->charFormat;
551
552     if(wsm == QTextHtmlParserNode::WhiteSpacePre || wsm == QTextHtmlParserNode::WhiteSpacePreWrap)
553         compressNextWhitespace = PreserveWhiteSpace;
554
555     QString text = currentNode->text;
556
557     QString textToInsert;
558     textToInsert.reserve(text.size());
559
560     for (int i = 0; i < text.length(); ++i) {
561         QChar ch = text.at(i);
562
563         if (ch.isSpace()
564             && ch != QChar::Nbsp
565             && ch != QChar::ParagraphSeparator) {
566
567             if (compressNextWhitespace == CollapseWhiteSpace)
568                 compressNextWhitespace = RemoveWhiteSpace; // allow this one, and remove the ones coming next.
569             else if(compressNextWhitespace == RemoveWhiteSpace)
570                 continue;
571
572             if (wsm == QTextHtmlParserNode::WhiteSpacePre
573                 || textEditMode
574                ) {
575                 if (ch == QLatin1Char('\n')) {
576                     if (textEditMode)
577                         continue;
578                 } else if (ch == QLatin1Char('\r')) {
579                     continue;
580                 }
581             } else if (wsm != QTextHtmlParserNode::WhiteSpacePreWrap) {
582                 compressNextWhitespace = RemoveWhiteSpace;
583                 if (wsm == QTextHtmlParserNode::WhiteSpaceNoWrap)
584                     ch = QChar::Nbsp;
585                 else
586                     ch = QLatin1Char(' ');
587             }
588         } else {
589             compressNextWhitespace = PreserveWhiteSpace;
590         }
591
592         if (ch == QLatin1Char('\n')
593             || ch == QChar::ParagraphSeparator) {
594
595             if (!textToInsert.isEmpty()) {
596                 cursor.insertText(textToInsert, format);
597                 textToInsert.clear();
598             }
599
600             QTextBlockFormat fmt = cursor.blockFormat();
601
602             if (fmt.hasProperty(QTextFormat::BlockBottomMargin)) {
603                 QTextBlockFormat tmp = fmt;
604                 tmp.clearProperty(QTextFormat::BlockBottomMargin);
605                 cursor.setBlockFormat(tmp);
606             }
607
608             fmt.clearProperty(QTextFormat::BlockTopMargin);
609             appendBlock(fmt, cursor.charFormat());
610         } else {
611             if (!namedAnchors.isEmpty()) {
612                 if (!textToInsert.isEmpty()) {
613                     cursor.insertText(textToInsert, format);
614                     textToInsert.clear();
615                 }
616
617                 format.setAnchor(true);
618                 format.setAnchorNames(namedAnchors);
619                 cursor.insertText(ch, format);
620                 namedAnchors.clear();
621                 format.clearProperty(QTextFormat::IsAnchor);
622                 format.clearProperty(QTextFormat::AnchorName);
623             } else {
624                 textToInsert += ch;
625             }
626         }
627     }
628
629     if (!textToInsert.isEmpty()) {
630         cursor.insertText(textToInsert, format);
631     }
632
633     return cursor.position() != initialCursorPosition;
634 }
635
636 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processSpecialNodes()
637 {
638     switch (currentNode->id) {
639         case Html_body:
640             if (currentNode->charFormat.background().style() != Qt::NoBrush) {
641                 QTextFrameFormat fmt = doc->rootFrame()->frameFormat();
642                 fmt.setBackground(currentNode->charFormat.background());
643                 doc->rootFrame()->setFrameFormat(fmt);
644                 const_cast<QTextHtmlParserNode *>(currentNode)->charFormat.clearProperty(QTextFormat::BackgroundBrush);
645             }
646             compressNextWhitespace = RemoveWhiteSpace;
647             break;
648
649         case Html_ol:
650         case Html_ul: {
651             QTextListFormat::Style style = currentNode->listStyle;
652
653             if (currentNode->id == Html_ul && !currentNode->hasOwnListStyle && currentNode->parent) {
654                 const QTextHtmlParserNode *n = &at(currentNode->parent);
655                 while (n) {
656                     if (n->id == Html_ul) {
657                         style = nextListStyle(currentNode->listStyle);
658                     }
659                     if (n->parent)
660                         n = &at(n->parent);
661                     else
662                         n = 0;
663                 }
664             }
665
666             QTextListFormat listFmt;
667             listFmt.setStyle(style);
668
669             ++indent;
670             if (currentNode->hasCssListIndent)
671                 listFmt.setIndent(currentNode->cssListIndent);
672             else
673                 listFmt.setIndent(indent);
674
675             List l;
676             l.format = listFmt;
677             l.listNode = currentNodeIdx;
678             lists.append(l);
679             compressNextWhitespace = RemoveWhiteSpace;
680
681             // broken html: <ul>Text here<li>Foo
682             const QString simpl = currentNode->text.simplified();
683             if (simpl.isEmpty() || simpl.at(0).isSpace())
684                 return ContinueWithNextNode;
685             break;
686         }
687
688         case Html_table: {
689             Table t = scanTable(currentNodeIdx);
690             tables.append(t);
691             hasBlock = false;
692             compressNextWhitespace = RemoveWhiteSpace;
693             return ContinueWithNextNode;
694         }
695
696         case Html_tr:
697             return ContinueWithNextNode;
698
699         case Html_img: {
700             QTextImageFormat fmt;
701             fmt.setName(currentNode->imageName);
702
703             fmt.merge(currentNode->charFormat);
704
705             if (currentNode->imageWidth >= 0)
706                 fmt.setWidth(currentNode->imageWidth);
707             if (currentNode->imageHeight >= 0)
708                 fmt.setHeight(currentNode->imageHeight);
709
710             cursor.insertImage(fmt, QTextFrameFormat::Position(currentNode->cssFloat));
711
712             cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
713             cursor.mergeCharFormat(currentNode->charFormat);
714             cursor.movePosition(QTextCursor::Right);
715             compressNextWhitespace = CollapseWhiteSpace;
716
717             hasBlock = false;
718             return ContinueWithNextNode;
719         }
720
721         case Html_hr: {
722             QTextBlockFormat blockFormat = currentNode->blockFormat;
723             blockFormat.setTopMargin(topMargin(currentNodeIdx));
724             blockFormat.setBottomMargin(bottomMargin(currentNodeIdx));
725             blockFormat.setProperty(QTextFormat::BlockTrailingHorizontalRulerWidth, currentNode->width);
726             if (hasBlock && importMode == ImportToDocument)
727                 cursor.mergeBlockFormat(blockFormat);
728             else
729                 appendBlock(blockFormat);
730             hasBlock = false;
731             compressNextWhitespace = RemoveWhiteSpace;
732             return ContinueWithNextNode;
733         }
734
735         default: break;
736     }
737     return ContinueWithCurrentNode;
738 }
739
740 // returns true if a block tag was closed
741 bool QTextHtmlImporter::closeTag()
742 {
743     const QTextHtmlParserNode *closedNode = &at(currentNodeIdx - 1);
744     const int endDepth = depth(currentNodeIdx) - 1;
745     int depth = this->depth(currentNodeIdx - 1);
746     bool blockTagClosed = false;
747
748     while (depth > endDepth) {
749         Table *t = 0;
750         if (!tables.isEmpty())
751             t = &tables.last();
752
753         switch (closedNode->id) {
754             case Html_tr:
755                 if (t && !t->isTextFrame) {
756                     ++t->currentRow;
757
758                     // for broken html with rowspans but missing tr tags
759                     while (!t->currentCell.atEnd() && t->currentCell.row < t->currentRow)
760                         ++t->currentCell;
761                 }
762
763                 blockTagClosed = true;
764                 break;
765
766             case Html_table:
767                 if (!t)
768                     break;
769                 indent = t->lastIndent;
770
771                 tables.resize(tables.size() - 1);
772                 t = 0;
773
774                 if (tables.isEmpty()) {
775                     cursor = doc->rootFrame()->lastCursorPosition();
776                 } else {
777                     t = &tables.last();
778                     if (t->isTextFrame)
779                         cursor = t->frame->lastCursorPosition();
780                     else if (!t->currentCell.atEnd())
781                         cursor = t->currentCell.cell().lastCursorPosition();
782                 }
783
784                 // we don't need an extra block after tables, so we don't
785                 // claim to have closed one for the creation of a new one
786                 // in import()
787                 blockTagClosed = false;
788                 compressNextWhitespace = RemoveWhiteSpace;
789                 break;
790
791             case Html_th:
792             case Html_td:
793                 if (t && !t->isTextFrame)
794                     ++t->currentCell;
795                 blockTagClosed = true;
796                 compressNextWhitespace = RemoveWhiteSpace;
797                 break;
798
799             case Html_ol:
800             case Html_ul:
801                 if (lists.isEmpty())
802                     break;
803                 lists.resize(lists.size() - 1);
804                 --indent;
805                 blockTagClosed = true;
806                 break;
807
808             case Html_br:
809                 compressNextWhitespace = RemoveWhiteSpace;
810                 break;
811
812             case Html_div:
813                 if (closedNode->children.isEmpty())
814                     break;
815                 // fall through
816             default:
817                 if (closedNode->isBlock())
818                     blockTagClosed = true;
819                 break;
820         }
821
822         closedNode = &at(closedNode->parent);
823         --depth;
824     }
825
826     return blockTagClosed;
827 }
828
829 QTextHtmlImporter::Table QTextHtmlImporter::scanTable(int tableNodeIdx)
830 {
831     Table table;
832     table.columns = 0;
833
834     QVector<QTextLength> columnWidths;
835
836     int tableHeaderRowCount = 0;
837     QVector<int> rowNodes;
838     rowNodes.reserve(at(tableNodeIdx).children.count());
839     foreach (int row, at(tableNodeIdx).children)
840         switch (at(row).id) {
841             case Html_tr:
842                 rowNodes += row;
843                 break;
844             case Html_thead:
845             case Html_tbody:
846             case Html_tfoot:
847                 foreach (int potentialRow, at(row).children)
848                     if (at(potentialRow).id == Html_tr) {
849                         rowNodes += potentialRow;
850                         if (at(row).id == Html_thead)
851                             ++tableHeaderRowCount;
852                     }
853                 break;
854             default: break;
855         }
856
857     QVector<RowColSpanInfo> rowColSpans;
858     QVector<RowColSpanInfo> rowColSpanForColumn;
859
860     int effectiveRow = 0;
861     foreach (int row, rowNodes) {
862         int colsInRow = 0;
863
864         foreach (int cell, at(row).children)
865             if (at(cell).isTableCell()) {
866                 // skip all columns with spans from previous rows
867                 while (colsInRow < rowColSpanForColumn.size()) {
868                     const RowColSpanInfo &spanInfo = rowColSpanForColumn[colsInRow];
869
870                     if (spanInfo.row + spanInfo.rowSpan > effectiveRow) {
871                         Q_ASSERT(spanInfo.col == colsInRow);
872                         colsInRow += spanInfo.colSpan;
873                     } else
874                         break;
875                 }
876
877                 const QTextHtmlParserNode &c = at(cell);
878                 const int currentColumn = colsInRow;
879                 colsInRow += c.tableCellColSpan;
880
881                 RowColSpanInfo spanInfo;
882                 spanInfo.row = effectiveRow;
883                 spanInfo.col = currentColumn;
884                 spanInfo.colSpan = c.tableCellColSpan;
885                 spanInfo.rowSpan = c.tableCellRowSpan;
886                 if (spanInfo.colSpan > 1 || spanInfo.rowSpan > 1)
887                     rowColSpans.append(spanInfo);
888
889                 columnWidths.resize(qMax(columnWidths.count(), colsInRow));
890                 rowColSpanForColumn.resize(columnWidths.size());
891                 for (int i = currentColumn; i < currentColumn + c.tableCellColSpan; ++i) {
892                     if (columnWidths.at(i).type() == QTextLength::VariableLength) {
893                         QTextLength w = c.width;
894                         if (c.tableCellColSpan > 1 && w.type() != QTextLength::VariableLength)
895                             w = QTextLength(w.type(), w.value(100.) / c.tableCellColSpan);
896                         columnWidths[i] = w;
897                     }
898                     rowColSpanForColumn[i] = spanInfo;
899                 }
900             }
901
902         table.columns = qMax(table.columns, colsInRow);
903
904         ++effectiveRow;
905     }
906     table.rows = effectiveRow;
907
908     table.lastIndent = indent;
909     indent = 0;
910
911     if (table.rows == 0 || table.columns == 0)
912         return table;
913
914     QTextFrameFormat fmt;
915     const QTextHtmlParserNode &node = at(tableNodeIdx);
916
917     if (!node.isTextFrame) {
918         QTextTableFormat tableFmt;
919         tableFmt.setCellSpacing(node.tableCellSpacing);
920         tableFmt.setCellPadding(node.tableCellPadding);
921         if (node.blockFormat.hasProperty(QTextFormat::BlockAlignment))
922             tableFmt.setAlignment(node.blockFormat.alignment());
923         tableFmt.setColumns(table.columns);
924         tableFmt.setColumnWidthConstraints(columnWidths);
925         tableFmt.setHeaderRowCount(tableHeaderRowCount);
926         fmt = tableFmt;
927     }
928
929     fmt.setTopMargin(topMargin(tableNodeIdx));
930     fmt.setBottomMargin(bottomMargin(tableNodeIdx));
931     fmt.setLeftMargin(leftMargin(tableNodeIdx)
932                       + table.lastIndent * 40 // ##### not a good emulation
933                       );
934     fmt.setRightMargin(rightMargin(tableNodeIdx));
935
936     // compatibility
937     if (qFuzzyCompare(fmt.leftMargin(), fmt.rightMargin())
938         && qFuzzyCompare(fmt.leftMargin(), fmt.topMargin())
939         && qFuzzyCompare(fmt.leftMargin(), fmt.bottomMargin()))
940         fmt.setProperty(QTextFormat::FrameMargin, fmt.leftMargin());
941
942     fmt.setBorderStyle(node.borderStyle);
943     fmt.setBorderBrush(node.borderBrush);
944     fmt.setBorder(node.tableBorder);
945     fmt.setWidth(node.width);
946     fmt.setHeight(node.height);
947     if (node.blockFormat.hasProperty(QTextFormat::PageBreakPolicy))
948         fmt.setPageBreakPolicy(node.blockFormat.pageBreakPolicy());
949
950     if (node.blockFormat.hasProperty(QTextFormat::LayoutDirection))
951         fmt.setLayoutDirection(node.blockFormat.layoutDirection());
952     if (node.charFormat.background().style() != Qt::NoBrush)
953         fmt.setBackground(node.charFormat.background());
954     fmt.setPosition(QTextFrameFormat::Position(node.cssFloat));
955
956     if (node.isTextFrame) {
957         if (node.isRootFrame) {
958             table.frame = cursor.currentFrame();
959             table.frame->setFrameFormat(fmt);
960         } else
961             table.frame = cursor.insertFrame(fmt);
962
963         table.isTextFrame = true;
964     } else {
965         const int oldPos = cursor.position();
966         QTextTable *textTable = cursor.insertTable(table.rows, table.columns, fmt.toTableFormat());
967         table.frame = textTable;
968
969         for (int i = 0; i < rowColSpans.count(); ++i) {
970             const RowColSpanInfo &nfo = rowColSpans.at(i);
971             textTable->mergeCells(nfo.row, nfo.col, nfo.rowSpan, nfo.colSpan);
972         }
973
974         table.currentCell = TableCellIterator(textTable);
975         cursor.setPosition(oldPos); // restore for caption support which needs to be inserted right before the table
976     }
977     return table;
978 }
979
980 QTextHtmlImporter::ProcessNodeResult QTextHtmlImporter::processBlockNode()
981 {
982     QTextBlockFormat block;
983     QTextCharFormat charFmt;
984     bool modifiedBlockFormat = true;
985     bool modifiedCharFormat = true;
986
987     if (currentNode->isTableCell() && !tables.isEmpty()) {
988         Table &t = tables.last();
989         if (!t.isTextFrame && !t.currentCell.atEnd()) {
990             QTextTableCell cell = t.currentCell.cell();
991             if (cell.isValid()) {
992                 QTextTableCellFormat fmt = cell.format().toTableCellFormat();
993                 if (topPadding(currentNodeIdx) >= 0)
994                     fmt.setTopPadding(topPadding(currentNodeIdx));
995                 if (bottomPadding(currentNodeIdx) >= 0)
996                     fmt.setBottomPadding(bottomPadding(currentNodeIdx));
997                 if (leftPadding(currentNodeIdx) >= 0)
998                     fmt.setLeftPadding(leftPadding(currentNodeIdx));
999                 if (rightPadding(currentNodeIdx) >= 0)
1000                     fmt.setRightPadding(rightPadding(currentNodeIdx));
1001                 cell.setFormat(fmt);
1002
1003                 cursor.setPosition(cell.firstPosition());
1004             }
1005         }
1006         hasBlock = true;
1007         compressNextWhitespace = RemoveWhiteSpace;
1008
1009         if (currentNode->charFormat.background().style() != Qt::NoBrush) {
1010             charFmt.setBackground(currentNode->charFormat.background());
1011             cursor.mergeBlockCharFormat(charFmt);
1012         }
1013     }
1014
1015     if (hasBlock) {
1016         block = cursor.blockFormat();
1017         charFmt = cursor.blockCharFormat();
1018         modifiedBlockFormat = false;
1019         modifiedCharFormat = false;
1020     }
1021
1022     // collapse
1023     {
1024         qreal tm = qreal(topMargin(currentNodeIdx));
1025         if (tm > block.topMargin()) {
1026             block.setTopMargin(tm);
1027             modifiedBlockFormat = true;
1028         }
1029     }
1030
1031     int bottomMargin = this->bottomMargin(currentNodeIdx);
1032
1033     // for list items we may want to collapse with the bottom margin of the
1034     // list.
1035     const QTextHtmlParserNode *parentNode = currentNode->parent ? &at(currentNode->parent) : 0;
1036     if ((currentNode->id == Html_li || currentNode->id == Html_dt || currentNode->id == Html_dd)
1037         && parentNode
1038         && (parentNode->isListStart() || parentNode->id == Html_dl)
1039         && (parentNode->children.last() == currentNodeIdx)) {
1040         bottomMargin = qMax(bottomMargin, this->bottomMargin(currentNode->parent));
1041     }
1042
1043     if (block.bottomMargin() != bottomMargin) {
1044         block.setBottomMargin(bottomMargin);
1045         modifiedBlockFormat = true;
1046     }
1047
1048     {
1049         const qreal lm = leftMargin(currentNodeIdx);
1050         const qreal rm = rightMargin(currentNodeIdx);
1051
1052         if (block.leftMargin() != lm) {
1053             block.setLeftMargin(lm);
1054             modifiedBlockFormat = true;
1055         }
1056         if (block.rightMargin() != rm) {
1057             block.setRightMargin(rm);
1058             modifiedBlockFormat = true;
1059         }
1060     }
1061
1062     if (currentNode->id != Html_li
1063         && indent != 0
1064         && (lists.isEmpty()
1065             || !hasBlock
1066             || !lists.last().list
1067             || lists.last().list->itemNumber(cursor.block()) == -1
1068            )
1069        ) {
1070         block.setIndent(indent);
1071         modifiedBlockFormat = true;
1072     }
1073
1074     if (currentNode->blockFormat.propertyCount() > 0) {
1075         modifiedBlockFormat = true;
1076         block.merge(currentNode->blockFormat);
1077     }
1078
1079     if (currentNode->charFormat.propertyCount() > 0) {
1080         modifiedCharFormat = true;
1081         charFmt.merge(currentNode->charFormat);
1082     }
1083
1084     // ####################
1085     //                block.setFloatPosition(node->cssFloat);
1086
1087     if (wsm == QTextHtmlParserNode::WhiteSpacePre) {
1088         block.setNonBreakableLines(true);
1089         modifiedBlockFormat = true;
1090     }
1091
1092     if (currentNode->charFormat.background().style() != Qt::NoBrush && !currentNode->isTableCell()) {
1093         block.setBackground(currentNode->charFormat.background());
1094         modifiedBlockFormat = true;
1095     }
1096
1097     if (hasBlock && (!currentNode->isEmptyParagraph || forceBlockMerging)) {
1098         if (modifiedBlockFormat)
1099             cursor.setBlockFormat(block);
1100         if (modifiedCharFormat)
1101             cursor.setBlockCharFormat(charFmt);
1102     } else {
1103         if (currentNodeIdx == 1 && cursor.position() == 0 && currentNode->isEmptyParagraph) {
1104             cursor.setBlockFormat(block);
1105             cursor.setBlockCharFormat(charFmt);
1106         } else {
1107             appendBlock(block, charFmt);
1108         }
1109     }
1110
1111     if (currentNode->userState != -1)
1112         cursor.block().setUserState(currentNode->userState);
1113
1114     if (currentNode->id == Html_li && !lists.isEmpty()) {
1115         List &l = lists.last();
1116         if (l.list) {
1117             l.list->add(cursor.block());
1118         } else {
1119             l.list = cursor.createList(l.format);
1120             const qreal listTopMargin = topMargin(l.listNode);
1121             if (listTopMargin > block.topMargin()) {
1122                 block.setTopMargin(listTopMargin);
1123                 cursor.mergeBlockFormat(block);
1124             }
1125         }
1126         if (hasBlock) {
1127             QTextBlockFormat fmt;
1128             fmt.setIndent(0);
1129             cursor.mergeBlockFormat(fmt);
1130         }
1131     }
1132
1133     forceBlockMerging = false;
1134     if (currentNode->id == Html_body || currentNode->id == Html_html)
1135         forceBlockMerging = true;
1136
1137     if (currentNode->isEmptyParagraph) {
1138         hasBlock = false;
1139         return ContinueWithNextNode;
1140     }
1141
1142     hasBlock = true;
1143     blockTagClosed = false;
1144     return ContinueWithCurrentNode;
1145 }
1146
1147 void QTextHtmlImporter::appendBlock(const QTextBlockFormat &format, QTextCharFormat charFmt)
1148 {
1149     if (!namedAnchors.isEmpty()) {
1150         charFmt.setAnchor(true);
1151         charFmt.setAnchorNames(namedAnchors);
1152         namedAnchors.clear();
1153     }
1154
1155     cursor.insertBlock(format, charFmt);
1156
1157     if (wsm != QTextHtmlParserNode::WhiteSpacePre && wsm != QTextHtmlParserNode::WhiteSpacePreWrap)
1158         compressNextWhitespace = RemoveWhiteSpace;
1159 }
1160
1161 #endif // QT_NO_TEXTHTMLPARSER
1162
1163 /*!
1164     \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text)
1165
1166     Returns a QTextDocumentFragment based on the arbitrary piece of
1167     HTML in the given \a text. The formatting is preserved as much as
1168     possible; for example, "<b>bold</b>" will become a document
1169     fragment with the text "bold" with a bold character format.
1170 */
1171
1172 #ifndef QT_NO_TEXTHTMLPARSER
1173
1174 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html)
1175 {
1176     return fromHtml(html, 0);
1177 }
1178
1179 /*!
1180     \fn QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &text, const QTextDocument *resourceProvider)
1181     \since 4.2
1182
1183     Returns a QTextDocumentFragment based on the arbitrary piece of
1184     HTML in the given \a text. The formatting is preserved as much as
1185     possible; for example, "<b>bold</b>" will become a document
1186     fragment with the text "bold" with a bold character format.
1187
1188     If the provided HTML contains references to external resources such as imported style sheets, then
1189     they will be loaded through the \a resourceProvider.
1190 */
1191
1192 QTextDocumentFragment QTextDocumentFragment::fromHtml(const QString &html, const QTextDocument *resourceProvider)
1193 {
1194     QTextDocumentFragment res;
1195     res.d = new QTextDocumentFragmentPrivate;
1196
1197     QTextHtmlImporter importer(res.d->doc, html, QTextHtmlImporter::ImportToFragment, resourceProvider);
1198     importer.import();
1199     return res;
1200 }
1201
1202 QT_END_NAMESPACE
1203 #endif // QT_NO_TEXTHTMLPARSER