source: trunk/src/gui/text/qtextdocument_p.cpp@ 624

Last change on this file since 624 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

File size: 51.4 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include <private/qtools_p.h>
43#include <qdebug.h>
44
45#include "qtextdocument_p.h"
46#include "qtextdocument.h"
47#include <qtextformat.h>
48#include "qtextformat_p.h"
49#include "qtextobject_p.h"
50#include "qtextcursor.h"
51#include "qtextimagehandler_p.h"
52#include "qtextcursor_p.h"
53#include "qtextdocumentlayout_p.h"
54#include "qtexttable.h"
55#include "qtextengine_p.h"
56
57#include <stdlib.h>
58
59QT_BEGIN_NAMESPACE
60
61#define PMDEBUG if(0) qDebug
62
63// The VxWorks DIAB compiler crashes when initializing the anonymouse union with { a7 }
64#if !defined(Q_CC_DIAB)
65# define QT_INIT_TEXTUNDOCOMMAND(c, a1, a2, a3, a4, a5, a6, a7, a8) \
66 QTextUndoCommand c = { a1, a2, 0, 0, a3, a4, a5, a6, { a7 }, a8 }
67#else
68# define QT_INIT_TEXTUNDOCOMMAND(c, a1, a2, a3, a4, a5, a6, a7, a8) \
69 QTextUndoCommand c = { a1, a2, 0, 0, a3, a4, a5, a6 }; c.blockFormat = a7; c.revision = a8
70#endif
71
72/*
73 Structure of a document:
74
75 DOCUMENT :== FRAME_CONTENTS
76 FRAME :== START_OF_FRAME FRAME_CONTENTS END_OF_FRAME
77 FRAME_CONTENTS = LIST_OF_BLOCKS ((FRAME | TABLE) LIST_OF_BLOCKS)*
78 TABLE :== (START_OF_FRAME TABLE_CELL)+ END_OF_FRAME
79 TABLE_CELL = FRAME_CONTENTS
80 LIST_OF_BLOCKS :== (BLOCK END_OF_PARA)* BLOCK
81 BLOCK :== (FRAGMENT)*
82 FRAGMENT :== String of characters
83
84 END_OF_PARA :== 0x2029 # Paragraph separator in Unicode
85 START_OF_FRAME :== 0xfdd0
86 END_OF_FRAME := 0xfdd1
87
88 Note also that LIST_OF_BLOCKS can be empty. Nevertheless, there is
89 at least one valid cursor position there where you could start
90 typing. The block format is in this case determined by the last
91 END_OF_PARA/START_OF_FRAME/END_OF_FRAME (see below).
92
93 Lists are not in here, as they are treated specially. A list is just
94 a collection of (not neccessarily connected) blocks, that share the
95 same objectIndex() in the format that refers to the list format and
96 object.
97
98 The above does not clearly note where formats are. Here's
99 how it looks currently:
100
101 FRAGMENT: one charFormat associated
102
103 END_OF_PARA: one charFormat, and a blockFormat for the _next_ block.
104
105 START_OF_FRAME: one char format, and a blockFormat (for the next
106 block). The format associated with the objectIndex() of the
107 charFormat decides whether this is a frame or table and its
108 properties
109
110 END_OF_FRAME: one charFormat and a blockFormat (for the next
111 block). The object() of the charFormat is the same as for the
112 corresponding START_OF_BLOCK.
113
114
115 The document is independent of the layout with certain restrictions:
116
117 * Cursor movement (esp. up and down) depend on the layout.
118 * You cannot have more than one layout, as the layout data of QTextObjects
119 is stored in the text object itself.
120
121*/
122
123void QTextBlockData::invalidate() const
124{
125 if (layout)
126 layout->engine()->invalidate();
127}
128
129static bool isValidBlockSeparator(const QChar &ch)
130{
131 return ch == QChar::ParagraphSeparator
132 || ch == QTextBeginningOfFrame
133 || ch == QTextEndOfFrame;
134}
135
136#ifndef QT_NO_DEBUG
137static bool noBlockInString(const QString &str)
138{
139 return !str.contains(QChar::ParagraphSeparator)
140 && !str.contains(QTextBeginningOfFrame)
141 && !str.contains(QTextEndOfFrame);
142}
143#endif
144
145bool QTextUndoCommand::tryMerge(const QTextUndoCommand &other)
146{
147 if (command != other.command)
148 return false;
149
150 if (command == Inserted
151 && (pos + length == other.pos)
152 && (strPos + length == other.strPos)
153 && format == other.format) {
154
155 length += other.length;
156 return true;
157 }
158
159 // removal to the 'right' using 'Delete' key
160 if (command == Removed
161 && pos == other.pos
162 && (strPos + length == other.strPos)
163 && format == other.format) {
164
165 length += other.length;
166 return true;
167 }
168
169 // removal to the 'left' using 'Backspace'
170 if (command == Removed
171 && (other.pos + other.length == pos)
172 && (other.strPos + other.length == strPos)
173 && (format == other.format)) {
174
175 int l = length;
176 (*this) = other;
177
178 length += l;
179 return true;
180 }
181
182 return false;
183}
184
185QTextDocumentPrivate::QTextDocumentPrivate()
186 : wasUndoAvailable(false),
187 wasRedoAvailable(false),
188 docChangeOldLength(0),
189 docChangeLength(0),
190 framesDirty(true),
191 rtFrame(0),
192 initialBlockCharFormatIndex(-1) // set correctly later in init()
193{
194 editBlock = 0;
195 docChangeFrom = -1;
196
197 undoState = 0;
198 revision = -1; // init() inserts a block, bringing it to 0
199
200 lout = 0;
201
202 modified = false;
203 modifiedState = 0;
204
205 undoEnabled = true;
206 inContentsChange = false;
207
208 defaultTextOption.setTabStop(80); // same as in qtextengine.cpp
209 defaultTextOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
210
211 indentWidth = 40;
212 documentMargin = 4;
213
214 maximumBlockCount = 0;
215 needsEnsureMaximumBlockCount = false;
216 unreachableCharacterCount = 0;
217 lastBlockCount = 0;
218}
219
220void QTextDocumentPrivate::init()
221{
222 framesDirty = false;
223
224 bool undoState = undoEnabled;
225 undoEnabled = false;
226 initialBlockCharFormatIndex = formats.indexForFormat(QTextCharFormat());
227 insertBlock(0, formats.indexForFormat(QTextBlockFormat()), formats.indexForFormat(QTextCharFormat()));
228 undoEnabled = undoState;
229 modified = false;
230 modifiedState = 0;
231}
232
233void QTextDocumentPrivate::clear()
234{
235 Q_Q(QTextDocument);
236 for (int i = 0; i < cursors.count(); ++i) {
237 cursors.at(i)->setPosition(0);
238 cursors.at(i)->currentCharFormat = -1;
239 cursors.at(i)->anchor = 0;
240 cursors.at(i)->adjusted_anchor = 0;
241 }
242
243 QList<QTextCursorPrivate *>oldCursors = cursors;
244 QT_TRY{
245 cursors.clear();
246 changedCursors.clear();
247
248 QMap<int, QTextObject *>::Iterator objectIt = objects.begin();
249 while (objectIt != objects.end()) {
250 if (*objectIt != rtFrame) {
251 delete *objectIt;
252 objectIt = objects.erase(objectIt);
253 } else {
254 ++objectIt;
255 }
256 }
257 // also clear out the remaining root frame pointer
258 // (we're going to delete the object further down)
259 objects.clear();
260
261 title.clear();
262 undoState = 0;
263 truncateUndoStack();
264 text = QString();
265 unreachableCharacterCount = 0;
266 modifiedState = 0;
267 modified = false;
268 formats = QTextFormatCollection();
269 int len = fragments.length();
270 fragments.clear();
271 blocks.clear();
272 cachedResources.clear();
273 delete rtFrame;
274 rtFrame = 0;
275 init();
276 cursors = oldCursors;
277 inContentsChange = true;
278 q->contentsChange(0, len, 0);
279 inContentsChange = false;
280 if (lout)
281 lout->documentChanged(0, len, 0);
282 } QT_CATCH(...) {
283 cursors = oldCursors; // at least recover the cursors
284 QT_RETHROW;
285 }
286}
287
288QTextDocumentPrivate::~QTextDocumentPrivate()
289{
290 for (int i = 0; i < cursors.count(); ++i)
291 cursors.at(i)->priv = 0;
292 cursors.clear();
293 undoState = 0;
294 undoEnabled = true;
295 truncateUndoStack();
296}
297
298void QTextDocumentPrivate::setLayout(QAbstractTextDocumentLayout *layout)
299{
300 Q_Q(QTextDocument);
301 if (lout == layout)
302 return;
303 const bool firstLayout = !lout;
304 delete lout;
305 lout = layout;
306
307 if (!firstLayout)
308 for (BlockMap::Iterator it = blocks.begin(); !it.atEnd(); ++it)
309 it->free();
310
311 emit q->documentLayoutChanged();
312 inContentsChange = true;
313 emit q->contentsChange(0, 0, length());
314 inContentsChange = false;
315 if (lout)
316 lout->documentChanged(0, 0, length());
317}
318
319
320void QTextDocumentPrivate::insert_string(int pos, uint strPos, uint length, int format, QTextUndoCommand::Operation op)
321{
322 // ##### optimise when only appending to the fragment!
323 Q_ASSERT(noBlockInString(text.mid(strPos, length)));
324
325 split(pos);
326 uint x = fragments.insert_single(pos, length);
327 QTextFragmentData *X = fragments.fragment(x);
328 X->format = format;
329 X->stringPosition = strPos;
330 uint w = fragments.previous(x);
331 if (w)
332 unite(w);
333
334 int b = blocks.findNode(pos);
335 blocks.setSize(b, blocks.size(b)+length);
336
337 Q_ASSERT(blocks.length() == fragments.length());
338
339 QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(format));
340 if (frame) {
341 frame->d_func()->fragmentAdded(text.at(strPos), x);
342 framesDirty = true;
343 }
344
345 adjustDocumentChangesAndCursors(pos, length, op);
346}
347
348int QTextDocumentPrivate::insert_block(int pos, uint strPos, int format, int blockFormat, QTextUndoCommand::Operation op, int command)
349{
350 split(pos);
351 uint x = fragments.insert_single(pos, 1);
352 QTextFragmentData *X = fragments.fragment(x);
353 X->format = format;
354 X->stringPosition = strPos;
355 // no need trying to unite, since paragraph separators are always in a fragment of their own
356
357 Q_ASSERT(isValidBlockSeparator(text.at(strPos)));
358 Q_ASSERT(blocks.length()+1 == fragments.length());
359
360 int block_pos = pos;
361 if (blocks.length() && command == QTextUndoCommand::BlockRemoved)
362 ++block_pos;
363 int size = 1;
364 int n = blocks.findNode(block_pos);
365 int key = n ? blocks.position(n) : blocks.length();
366
367 Q_ASSERT(n || (!n && block_pos == blocks.length()));
368 if (key != block_pos) {
369 Q_ASSERT(key < block_pos);
370 int oldSize = blocks.size(n);
371 blocks.setSize(n, block_pos-key);
372 size += oldSize - (block_pos-key);
373 }
374 int b = blocks.insert_single(block_pos, size);
375 QTextBlockData *B = blocks.fragment(b);
376 B->format = blockFormat;
377
378 Q_ASSERT(blocks.length() == fragments.length());
379
380 QTextBlockGroup *group = qobject_cast<QTextBlockGroup *>(objectForFormat(blockFormat));
381 if (group)
382 group->blockInserted(QTextBlock(this, b));
383
384 QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(formats.format(format)));
385 if (frame) {
386 frame->d_func()->fragmentAdded(text.at(strPos), x);
387 framesDirty = true;
388 }
389
390 adjustDocumentChangesAndCursors(pos, 1, op);
391 return x;
392}
393
394int QTextDocumentPrivate::insertBlock(const QChar &blockSeparator,
395 int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation op)
396{
397 Q_ASSERT(formats.format(blockFormat).isBlockFormat());
398 Q_ASSERT(formats.format(charFormat).isCharFormat());
399 Q_ASSERT(pos >= 0 && (pos < fragments.length() || (pos == 0 && fragments.length() == 0)));
400 Q_ASSERT(isValidBlockSeparator(blockSeparator));
401
402 beginEditBlock();
403
404 int strPos = text.length();
405 text.append(blockSeparator);
406
407 int ob = blocks.findNode(pos);
408 bool atBlockEnd = true;
409 bool atBlockStart = true;
410 int oldRevision = 0;
411 if (ob) {
412 atBlockEnd = (pos - blocks.position(ob) == blocks.size(ob)-1);
413 atBlockStart = ((int)blocks.position(ob) == pos);
414 oldRevision = blocks.fragment(ob)->revision;
415 }
416
417 const int fragment = insert_block(pos, strPos, charFormat, blockFormat, op, QTextUndoCommand::BlockRemoved);
418
419 Q_ASSERT(blocks.length() == fragments.length());
420
421 int b = blocks.findNode(pos);
422 QTextBlockData *B = blocks.fragment(b);
423
424 QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::BlockInserted, (editBlock != 0),
425 op, charFormat, strPos, pos, blockFormat,
426 B->revision);
427
428 appendUndoItem(c);
429 Q_ASSERT(undoState == undoStack.size());
430
431 // update revision numbers of the modified blocks.
432 B->revision = (atBlockEnd && !atBlockStart)? oldRevision : revision;
433 b = blocks.next(b);
434 if (b) {
435 B = blocks.fragment(b);
436 B->revision = atBlockStart ? oldRevision : revision;
437 }
438
439 if (formats.charFormat(charFormat).objectIndex() == -1)
440 needsEnsureMaximumBlockCount = true;
441
442 endEditBlock();
443 return fragment;
444}
445
446int QTextDocumentPrivate::insertBlock(int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation op)
447{
448 return insertBlock(QChar::ParagraphSeparator, pos, blockFormat, charFormat, op);
449}
450
451void QTextDocumentPrivate::insert(int pos, int strPos, int strLength, int format)
452{
453 if (strLength <= 0)
454 return;
455
456 Q_ASSERT(pos >= 0 && pos < fragments.length());
457 Q_ASSERT(formats.format(format).isCharFormat());
458
459 insert_string(pos, strPos, strLength, format, QTextUndoCommand::MoveCursor);
460 if (undoEnabled) {
461 int b = blocks.findNode(pos);
462 QTextBlockData *B = blocks.fragment(b);
463
464 QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::Inserted, (editBlock != 0),
465 QTextUndoCommand::MoveCursor, format, strPos, pos, strLength,
466 B->revision);
467 appendUndoItem(c);
468 B->revision = revision;
469 Q_ASSERT(undoState == undoStack.size());
470 }
471 finishEdit();
472}
473
474void QTextDocumentPrivate::insert(int pos, const QString &str, int format)
475{
476 if (str.size() == 0)
477 return;
478
479 Q_ASSERT(noBlockInString(str));
480
481 int strPos = text.length();
482 text.append(str);
483 insert(pos, strPos, str.length(), format);
484}
485
486int QTextDocumentPrivate::remove_string(int pos, uint length, QTextUndoCommand::Operation op)
487{
488 Q_ASSERT(pos >= 0);
489 Q_ASSERT(blocks.length() == fragments.length());
490 Q_ASSERT(blocks.length() >= pos+(int)length);
491
492 int b = blocks.findNode(pos);
493 uint x = fragments.findNode(pos);
494
495 Q_ASSERT(blocks.size(b) > length);
496 Q_ASSERT(x && fragments.position(x) == (uint)pos && fragments.size(x) == length);
497 Q_ASSERT(noBlockInString(text.mid(fragments.fragment(x)->stringPosition, length)));
498
499 blocks.setSize(b, blocks.size(b)-length);
500
501 QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(fragments.fragment(x)->format));
502 if (frame) {
503 frame->d_func()->fragmentRemoved(text.at(fragments.fragment(x)->stringPosition), x);
504 framesDirty = true;
505 }
506
507 const int w = fragments.erase_single(x);
508
509 if (!undoEnabled)
510 unreachableCharacterCount += length;
511
512 adjustDocumentChangesAndCursors(pos, -int(length), op);
513
514 return w;
515}
516
517int QTextDocumentPrivate::remove_block(int pos, int *blockFormat, int command, QTextUndoCommand::Operation op)
518{
519 Q_ASSERT(pos >= 0);
520 Q_ASSERT(blocks.length() == fragments.length());
521 Q_ASSERT(blocks.length() > pos);
522
523 int b = blocks.findNode(pos);
524 uint x = fragments.findNode(pos);
525
526 Q_ASSERT(x && (int)fragments.position(x) == pos);
527 Q_ASSERT(fragments.size(x) == 1);
528 Q_ASSERT(isValidBlockSeparator(text.at(fragments.fragment(x)->stringPosition)));
529 Q_ASSERT(b);
530
531 if (blocks.size(b) == 1 && command == QTextUndoCommand::BlockAdded) {
532 Q_ASSERT((int)blocks.position(b) == pos);
533// qDebug("removing empty block");
534 // empty block remove the block itself
535 } else {
536 // non empty block, merge with next one into this block
537// qDebug("merging block with next");
538 int n = blocks.next(b);
539 Q_ASSERT((int)blocks.position(n) == pos + 1);
540 blocks.setSize(b, blocks.size(b) + blocks.size(n) - 1);
541 b = n;
542 }
543 *blockFormat = blocks.fragment(b)->format;
544
545 QTextBlockGroup *group = qobject_cast<QTextBlockGroup *>(objectForFormat(blocks.fragment(b)->format));
546 if (group)
547 group->blockRemoved(QTextBlock(this, b));
548
549 QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(fragments.fragment(x)->format));
550 if (frame) {
551 frame->d_func()->fragmentRemoved(text.at(fragments.fragment(x)->stringPosition), x);
552 framesDirty = true;
553 }
554
555 blocks.erase_single(b);
556 const int w = fragments.erase_single(x);
557
558 adjustDocumentChangesAndCursors(pos, -1, op);
559
560 return w;
561}
562
563#if !defined(QT_NO_DEBUG)
564static bool isAncestorFrame(QTextFrame *possibleAncestor, QTextFrame *child)
565{
566 while (child) {
567 if (child == possibleAncestor)
568 return true;
569 child = child->parentFrame();
570 }
571 return false;
572}
573#endif
574
575void QTextDocumentPrivate::move(int pos, int to, int length, QTextUndoCommand::Operation op)
576{
577 Q_ASSERT(to <= fragments.length() && to <= pos);
578 Q_ASSERT(pos >= 0 && pos+length <= fragments.length());
579 Q_ASSERT(blocks.length() == fragments.length());
580
581 if (pos == to)
582 return;
583
584 const bool needsInsert = to != -1;
585
586#if !defined(QT_NO_DEBUG)
587 const bool startAndEndInSameFrame = (frameAt(pos) == frameAt(pos + length - 1));
588
589 const bool endIsEndOfChildFrame = (isAncestorFrame(frameAt(pos), frameAt(pos + length - 1))
590 && text.at(find(pos + length - 1)->stringPosition) == QTextEndOfFrame);
591
592 const bool startIsStartOfFrameAndEndIsEndOfFrameWithCommonParent
593 = (text.at(find(pos)->stringPosition) == QTextBeginningOfFrame
594 && text.at(find(pos + length - 1)->stringPosition) == QTextEndOfFrame
595 && frameAt(pos)->parentFrame() == frameAt(pos + length - 1)->parentFrame());
596
597 const bool isFirstTableCell = (qobject_cast<QTextTable *>(frameAt(pos + length - 1))
598 && frameAt(pos + length - 1)->parentFrame() == frameAt(pos));
599
600 Q_ASSERT(startAndEndInSameFrame || endIsEndOfChildFrame || startIsStartOfFrameAndEndIsEndOfFrameWithCommonParent || isFirstTableCell);
601#endif
602
603 split(pos);
604 split(pos+length);
605
606 uint dst = needsInsert ? fragments.findNode(to) : 0;
607 uint dstKey = needsInsert ? fragments.position(dst) : 0;
608
609 uint x = fragments.findNode(pos);
610 uint end = fragments.findNode(pos+length);
611
612 uint w = 0;
613 while (x != end) {
614 uint n = fragments.next(x);
615
616 uint key = fragments.position(x);
617 uint b = blocks.findNode(key+1);
618 QTextBlockData *B = blocks.fragment(b);
619 int blockRevision = B->revision;
620
621 QTextFragmentData *X = fragments.fragment(x);
622 QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::Removed, (editBlock != 0),
623 op, X->format, X->stringPosition, key, X->size_array[0],
624 blockRevision);
625 QT_INIT_TEXTUNDOCOMMAND(cInsert, QTextUndoCommand::Inserted, (editBlock != 0),
626 op, X->format, X->stringPosition, dstKey, X->size_array[0],
627 blockRevision);
628
629 if (key+1 != blocks.position(b)) {
630// qDebug("remove_string from %d length %d", key, X->size_array[0]);
631 Q_ASSERT(noBlockInString(text.mid(X->stringPosition, X->size_array[0])));
632 w = remove_string(key, X->size_array[0], op);
633
634 if (needsInsert) {
635 insert_string(dstKey, X->stringPosition, X->size_array[0], X->format, op);
636 dstKey += X->size_array[0];
637 }
638 } else {
639// qDebug("remove_block at %d", key);
640 Q_ASSERT(X->size_array[0] == 1 && isValidBlockSeparator(text.at(X->stringPosition)));
641 b = blocks.previous(b);
642 B = 0;
643 c.command = blocks.size(b) == 1 ? QTextUndoCommand::BlockDeleted : QTextUndoCommand::BlockRemoved;
644 w = remove_block(key, &c.blockFormat, QTextUndoCommand::BlockAdded, op);
645
646 if (needsInsert) {
647 insert_block(dstKey++, X->stringPosition, X->format, c.blockFormat, op, QTextUndoCommand::BlockRemoved);
648 cInsert.command = blocks.size(b) == 1 ? QTextUndoCommand::BlockAdded : QTextUndoCommand::BlockInserted;
649 cInsert.blockFormat = c.blockFormat;
650 }
651 }
652 appendUndoItem(c);
653 if (B)
654 B->revision = revision;
655 x = n;
656
657 if (needsInsert)
658 appendUndoItem(cInsert);
659 }
660 if (w)
661 unite(w);
662
663 Q_ASSERT(blocks.length() == fragments.length());
664
665 finishEdit();
666}
667
668void QTextDocumentPrivate::remove(int pos, int length, QTextUndoCommand::Operation op)
669{
670 if (length == 0)
671 return;
672 move(pos, -1, length, op);
673}
674
675void QTextDocumentPrivate::setCharFormat(int pos, int length, const QTextCharFormat &newFormat, FormatChangeMode mode)
676{
677 beginEditBlock();
678
679 Q_ASSERT(newFormat.isValid());
680
681 int newFormatIdx = -1;
682 if (mode == SetFormatAndPreserveObjectIndices) {
683 QTextCharFormat cleanFormat = newFormat;
684 cleanFormat.clearProperty(QTextFormat::ObjectIndex);
685 newFormatIdx = formats.indexForFormat(cleanFormat);
686 } else if (mode == SetFormat) {
687 newFormatIdx = formats.indexForFormat(newFormat);
688 }
689
690 if (pos == -1) {
691 if (mode == MergeFormat) {
692 QTextFormat format = formats.format(initialBlockCharFormatIndex);
693 format.merge(newFormat);
694 initialBlockCharFormatIndex = formats.indexForFormat(format);
695 } else if (mode == SetFormatAndPreserveObjectIndices
696 && formats.format(initialBlockCharFormatIndex).objectIndex() != -1) {
697 QTextCharFormat f = newFormat;
698 f.setObjectIndex(formats.format(initialBlockCharFormatIndex).objectIndex());
699 initialBlockCharFormatIndex = formats.indexForFormat(f);
700 } else {
701 initialBlockCharFormatIndex = newFormatIdx;
702 }
703
704 ++pos;
705 --length;
706 }
707
708 const int startPos = pos;
709 const int endPos = pos + length;
710
711 split(startPos);
712 split(endPos);
713
714 while (pos < endPos) {
715 FragmentMap::Iterator it = fragments.find(pos);
716 Q_ASSERT(!it.atEnd());
717
718 QTextFragmentData *fragment = it.value();
719
720 Q_ASSERT(formats.format(fragment->format).type() == QTextFormat::CharFormat);
721
722 int offset = pos - it.position();
723 int length = qMin(endPos - pos, int(fragment->size_array[0] - offset));
724 int oldFormat = fragment->format;
725
726 if (mode == MergeFormat) {
727 QTextFormat format = formats.format(fragment->format);
728 format.merge(newFormat);
729 fragment->format = formats.indexForFormat(format);
730 } else if (mode == SetFormatAndPreserveObjectIndices
731 && formats.format(oldFormat).objectIndex() != -1) {
732 QTextCharFormat f = newFormat;
733 f.setObjectIndex(formats.format(oldFormat).objectIndex());
734 fragment->format = formats.indexForFormat(f);
735 } else {
736 fragment->format = newFormatIdx;
737 }
738
739 QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::CharFormatChanged, true, QTextUndoCommand::MoveCursor, oldFormat,
740 0, pos, length, 0);
741 appendUndoItem(c);
742
743 pos += length;
744 Q_ASSERT(pos == (int)(it.position() + fragment->size_array[0]) || pos >= endPos);
745 }
746
747 int n = fragments.findNode(startPos - 1);
748 if (n)
749 unite(n);
750
751 n = fragments.findNode(endPos);
752 if (n)
753 unite(n);
754
755 QTextBlock blockIt = blocksFind(startPos);
756 QTextBlock endIt = blocksFind(endPos);
757 if (endIt.isValid())
758 endIt = endIt.next();
759 for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
760 QTextDocumentPrivate::block(blockIt)->invalidate();
761
762 documentChange(startPos, length);
763
764 endEditBlock();
765}
766
767void QTextDocumentPrivate::setBlockFormat(const QTextBlock &from, const QTextBlock &to,
768 const QTextBlockFormat &newFormat, FormatChangeMode mode)
769{
770 beginEditBlock();
771
772 Q_ASSERT(mode != SetFormatAndPreserveObjectIndices); // only implemented for setCharFormat
773
774 Q_ASSERT(newFormat.isValid());
775
776 int newFormatIdx = -1;
777 if (mode == SetFormat)
778 newFormatIdx = formats.indexForFormat(newFormat);
779 QTextBlockGroup *group = qobject_cast<QTextBlockGroup *>(objectForFormat(newFormat));
780
781 QTextBlock it = from;
782 QTextBlock end = to;
783 if (end.isValid())
784 end = end.next();
785
786 for (; it != end; it = it.next()) {
787 int oldFormat = block(it)->format;
788 QTextBlockFormat format = formats.blockFormat(oldFormat);
789 QTextBlockGroup *oldGroup = qobject_cast<QTextBlockGroup *>(objectForFormat(format));
790 if (mode == MergeFormat) {
791 format.merge(newFormat);
792 newFormatIdx = formats.indexForFormat(format);
793 group = qobject_cast<QTextBlockGroup *>(objectForFormat(format));
794 }
795 block(it)->format = newFormatIdx;
796
797 block(it)->invalidate();
798
799 QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::BlockFormatChanged, true, QTextUndoCommand::MoveCursor, oldFormat,
800 0, it.position(), 1, 0);
801 appendUndoItem(c);
802
803 if (group != oldGroup) {
804 if (oldGroup)
805 oldGroup->blockRemoved(it);
806 if (group)
807 group->blockInserted(it);
808 } else if (group) {
809 group->blockFormatChanged(it);
810 }
811 }
812
813 documentChange(from.position(), to.position() + to.length() - from.position());
814
815 endEditBlock();
816}
817
818
819bool QTextDocumentPrivate::split(int pos)
820{
821 uint x = fragments.findNode(pos);
822 if (x) {
823 int k = fragments.position(x);
824// qDebug("found fragment with key %d, size_left=%d, size=%d to split at %d",
825// k, (*it)->size_left[0], (*it)->size_array[0], pos);
826 if (k != pos) {
827 Q_ASSERT(k <= pos);
828 // need to resize the first fragment and add a new one
829 QTextFragmentData *X = fragments.fragment(x);
830 int oldsize = X->size_array[0];
831 fragments.setSize(x, pos-k);
832 uint n = fragments.insert_single(pos, oldsize-(pos-k));
833 X = fragments.fragment(x);
834 QTextFragmentData *N = fragments.fragment(n);
835 N->stringPosition = X->stringPosition + pos-k;
836 N->format = X->format;
837 return true;
838 }
839 }
840 return false;
841}
842
843bool QTextDocumentPrivate::unite(uint f)
844{
845 uint n = fragments.next(f);
846 if (!n)
847 return false;
848
849 QTextFragmentData *ff = fragments.fragment(f);
850 QTextFragmentData *nf = fragments.fragment(n);
851
852 if (nf->format == ff->format && (ff->stringPosition + (int)ff->size_array[0] == nf->stringPosition)) {
853 if (isValidBlockSeparator(text.at(ff->stringPosition))
854 || isValidBlockSeparator(text.at(nf->stringPosition)))
855 return false;
856
857 fragments.setSize(f, ff->size_array[0] + nf->size_array[0]);
858 fragments.erase_single(n);
859 return true;
860 }
861 return false;
862}
863
864
865int QTextDocumentPrivate::undoRedo(bool undo)
866{
867 PMDEBUG("%s, undoState=%d, undoStack size=%d", undo ? "undo:" : "redo:", undoState, undoStack.size());
868 if (!undoEnabled || (undo && undoState == 0) || (!undo && undoState == undoStack.size()))
869 return -1;
870
871 undoEnabled = false;
872 beginEditBlock();
873 int editPos = -1;
874 while (1) {
875 if (undo)
876 --undoState;
877 QTextUndoCommand &c = undoStack[undoState];
878 int resetBlockRevision = c.pos;
879
880 switch(c.command) {
881 case QTextUndoCommand::Inserted:
882 remove(c.pos, c.length, (QTextUndoCommand::Operation)c.operation);
883 PMDEBUG(" erase: from %d, length %d", c.pos, c.length);
884 c.command = QTextUndoCommand::Removed;
885 editPos = c.pos;
886 break;
887 case QTextUndoCommand::Removed:
888 PMDEBUG(" insert: format %d (from %d, length %d, strpos=%d)", c.format, c.pos, c.length, c.strPos);
889 insert_string(c.pos, c.strPos, c.length, c.format, (QTextUndoCommand::Operation)c.operation);
890 c.command = QTextUndoCommand::Inserted;
891 editPos = c.pos + c.length;
892 break;
893 case QTextUndoCommand::BlockInserted:
894 case QTextUndoCommand::BlockAdded:
895 remove_block(c.pos, &c.blockFormat, c.command, (QTextUndoCommand::Operation)c.operation);
896 PMDEBUG(" blockremove: from %d", c.pos);
897 if (c.command == QTextUndoCommand::BlockInserted)
898 c.command = QTextUndoCommand::BlockRemoved;
899 else
900 c.command = QTextUndoCommand::BlockDeleted;
901 editPos = c.pos;
902 break;
903 case QTextUndoCommand::BlockRemoved:
904 case QTextUndoCommand::BlockDeleted:
905 PMDEBUG(" blockinsert: charformat %d blockformat %d (pos %d, strpos=%d)", c.format, c.blockFormat, c.pos, c.strPos);
906 insert_block(c.pos, c.strPos, c.format, c.blockFormat, (QTextUndoCommand::Operation)c.operation, c.command);
907 resetBlockRevision += 1;
908 if (c.command == QTextUndoCommand::BlockRemoved)
909 c.command = QTextUndoCommand::BlockInserted;
910 else
911 c.command = QTextUndoCommand::BlockAdded;
912 editPos = c.pos + 1;
913 break;
914 case QTextUndoCommand::CharFormatChanged: {
915 resetBlockRevision = -1; // ## TODO
916 PMDEBUG(" charFormat: format %d (from %d, length %d)", c.format, c.pos, c.length);
917 FragmentIterator it = find(c.pos);
918 Q_ASSERT(!it.atEnd());
919
920 int oldFormat = it.value()->format;
921 setCharFormat(c.pos, c.length, formats.charFormat(c.format));
922 c.format = oldFormat;
923 editPos = c.pos + c.length;
924 break;
925 }
926 case QTextUndoCommand::BlockFormatChanged: {
927 resetBlockRevision = -1; // ## TODO
928 PMDEBUG(" blockformat: format %d pos %d", c.format, c.pos);
929 QTextBlock it = blocksFind(c.pos);
930 Q_ASSERT(it.isValid());
931
932 int oldFormat = block(it)->format;
933 block(it)->format = c.format;
934 QTextBlockGroup *oldGroup = qobject_cast<QTextBlockGroup *>(objectForFormat(formats.blockFormat(oldFormat)));
935 QTextBlockGroup *group = qobject_cast<QTextBlockGroup *>(objectForFormat(formats.blockFormat(c.format)));
936 c.format = oldFormat;
937 if (group != oldGroup) {
938 if (oldGroup)
939 oldGroup->blockRemoved(it);
940 if (group)
941 group->blockInserted(it);
942 } else if (group) {
943 group->blockFormatChanged(it);
944 }
945 documentChange(it.position(), it.length());
946 editPos = -1;
947 break;
948 }
949 case QTextUndoCommand::GroupFormatChange: {
950 resetBlockRevision = -1; // ## TODO
951 PMDEBUG(" group format change");
952 QTextObject *object = objectForIndex(c.objectIndex);
953 int oldFormat = formats.objectFormatIndex(c.objectIndex);
954 changeObjectFormat(object, c.format);
955 c.format = oldFormat;
956 editPos = -1;
957 break;
958 }
959 case QTextUndoCommand::Custom:
960 resetBlockRevision = -1; // ## TODO
961 if (undo)
962 c.custom->undo();
963 else
964 c.custom->redo();
965 editPos = -1;
966 break;
967 default:
968 Q_ASSERT(false);
969 }
970
971 if (resetBlockRevision >= 0) {
972 int b = blocks.findNode(resetBlockRevision);
973 QTextBlockData *B = blocks.fragment(b);
974 B->revision = c.revision;
975 }
976
977 if (!undo)
978 ++undoState;
979
980 bool inBlock = (
981 undoState > 0
982 && undoState < undoStack.size()
983 && undoStack[undoState].block_part
984 && undoStack[undoState-1].block_part
985 && !undoStack[undoState-1].block_end
986 );
987 if (!inBlock)
988 break;
989 }
990 undoEnabled = true;
991 if (editPos < 0 && docChangeFrom >= 0) {
992 editPos = qMin(docChangeFrom + docChangeLength, length() - 1);
993 }
994 endEditBlock();
995 emitUndoAvailable(isUndoAvailable());
996 emitRedoAvailable(isRedoAvailable());
997 return editPos;
998}
999
1000/*!
1001 Appends a custom undo \a item to the undo stack.
1002*/
1003void QTextDocumentPrivate::appendUndoItem(QAbstractUndoItem *item)
1004{
1005 if (!undoEnabled) {
1006 delete item;
1007 return;
1008 }
1009
1010 QTextUndoCommand c;
1011 c.command = QTextUndoCommand::Custom;
1012 c.block_part = editBlock != 0;
1013 c.block_end = 0;
1014 c.operation = QTextUndoCommand::MoveCursor;
1015 c.format = 0;
1016 c.strPos = 0;
1017 c.pos = 0;
1018 c.blockFormat = 0;
1019
1020 c.custom = item;
1021 appendUndoItem(c);
1022}
1023
1024void QTextDocumentPrivate::appendUndoItem(const QTextUndoCommand &c)
1025{
1026 PMDEBUG("appendUndoItem, command=%d enabled=%d", c.command, undoEnabled);
1027 if (!undoEnabled)
1028 return;
1029 if (undoState < undoStack.size())
1030 truncateUndoStack();
1031
1032 if (!undoStack.isEmpty() && modified) {
1033 QTextUndoCommand &last = undoStack[undoState - 1];
1034
1035 if ( (last.block_part && c.block_part && !last.block_end) // part of the same block => can merge
1036 || (!c.block_part && !last.block_part)) { // two single undo items => can merge
1037
1038 if (last.tryMerge(c))
1039 return;
1040 }
1041 }
1042 if (modifiedState > undoState)
1043 modifiedState = -1;
1044 undoStack.append(c);
1045 undoState++;
1046 emitUndoAvailable(true);
1047 emitRedoAvailable(false);
1048
1049 if (!c.block_part)
1050 emit document()->undoCommandAdded();
1051}
1052
1053void QTextDocumentPrivate::truncateUndoStack()
1054{
1055 if (undoState == undoStack.size())
1056 return;
1057
1058 for (int i = undoState; i < undoStack.size(); ++i) {
1059 QTextUndoCommand c = undoStack[i];
1060 if (c.command & QTextUndoCommand::Removed) {
1061 // ########
1062// QTextFragment *f = c.fragment_list;
1063// while (f) {
1064// QTextFragment *n = f->right;
1065// delete f;
1066// f = n;
1067// }
1068 } else if (c.command & QTextUndoCommand::Custom) {
1069 delete c.custom;
1070 }
1071 }
1072 undoStack.resize(undoState);
1073}
1074
1075void QTextDocumentPrivate::emitUndoAvailable(bool available)
1076{
1077 if (available != wasUndoAvailable) {
1078 Q_Q(QTextDocument);
1079 emit q->undoAvailable(available);
1080 wasUndoAvailable = available;
1081 }
1082}
1083
1084void QTextDocumentPrivate::emitRedoAvailable(bool available)
1085{
1086 if (available != wasRedoAvailable) {
1087 Q_Q(QTextDocument);
1088 emit q->redoAvailable(available);
1089 wasRedoAvailable = available;
1090 }
1091}
1092
1093void QTextDocumentPrivate::enableUndoRedo(bool enable)
1094{
1095 if (enable && maximumBlockCount > 0)
1096 return;
1097
1098 if (!enable) {
1099 undoState = 0;
1100 truncateUndoStack();
1101 emitUndoAvailable(false);
1102 emitRedoAvailable(false);
1103 }
1104 modifiedState = modified ? -1 : undoState;
1105 undoEnabled = enable;
1106 if (!undoEnabled)
1107 compressPieceTable();
1108}
1109
1110void QTextDocumentPrivate::joinPreviousEditBlock()
1111{
1112 beginEditBlock();
1113
1114 if (undoEnabled && undoState)
1115 undoStack[undoState - 1].block_end = false;
1116}
1117
1118void QTextDocumentPrivate::endEditBlock()
1119{
1120 Q_ASSERT(editBlock > 0);
1121 if (--editBlock)
1122 return;
1123
1124 if (undoEnabled && undoState > 0) {
1125 const bool wasBlocking = !undoStack[undoState - 1].block_end;
1126 if (undoStack[undoState - 1].block_part) {
1127 undoStack[undoState - 1].block_end = true;
1128 if (wasBlocking)
1129 emit document()->undoCommandAdded();
1130 }
1131 }
1132
1133 finishEdit();
1134}
1135
1136void QTextDocumentPrivate::finishEdit()
1137{
1138 Q_Q(QTextDocument);
1139
1140 if (editBlock)
1141 return;
1142
1143 if (framesDirty)
1144 scan_frames(docChangeFrom, docChangeOldLength, docChangeLength);
1145
1146 if (lout && docChangeFrom >= 0) {
1147 if (!inContentsChange) {
1148 inContentsChange = true;
1149 emit q->contentsChange(docChangeFrom, docChangeOldLength, docChangeLength);
1150 inContentsChange = false;
1151 }
1152 lout->documentChanged(docChangeFrom, docChangeOldLength, docChangeLength);
1153 }
1154
1155 docChangeFrom = -1;
1156
1157 if (needsEnsureMaximumBlockCount) {
1158 needsEnsureMaximumBlockCount = false;
1159 if (ensureMaximumBlockCount()) {
1160 // if ensureMaximumBlockCount() returns true
1161 // it will have called endEditBlock() and
1162 // compressPieceTable() itself, so we return here
1163 // to prevent getting two contentsChanged emits
1164 return;
1165 }
1166 }
1167
1168 while (!changedCursors.isEmpty()) {
1169 QTextCursorPrivate *curs = changedCursors.takeFirst();
1170 emit q->cursorPositionChanged(QTextCursor(curs));
1171 }
1172
1173 contentsChanged();
1174
1175 if (blocks.numNodes() != lastBlockCount) {
1176 lastBlockCount = blocks.numNodes();
1177 emit q->blockCountChanged(lastBlockCount);
1178 }
1179
1180 if (!undoEnabled && unreachableCharacterCount)
1181 compressPieceTable();
1182}
1183
1184void QTextDocumentPrivate::documentChange(int from, int length)
1185{
1186// qDebug("QTextDocumentPrivate::documentChange: from=%d,length=%d", from, length);
1187 if (docChangeFrom < 0) {
1188 docChangeFrom = from;
1189 docChangeOldLength = length;
1190 docChangeLength = length;
1191 return;
1192 }
1193 int start = qMin(from, docChangeFrom);
1194 int end = qMax(from + length, docChangeFrom + docChangeLength);
1195 int diff = qMax(0, end - start - docChangeLength);
1196 docChangeFrom = start;
1197 docChangeOldLength += diff;
1198 docChangeLength += diff;
1199}
1200
1201/*
1202 adjustDocumentChangesAndCursors is called whenever there is an insert or remove of characters.
1203 param from is the cursor position in the document
1204 param addedOrRemoved is the amount of characters added or removed. A negative number means characters are removed.
1205
1206 The function stores information to be emitted when finishEdit() is called.
1207*/
1208void QTextDocumentPrivate::adjustDocumentChangesAndCursors(int from, int addedOrRemoved, QTextUndoCommand::Operation op)
1209{
1210 if (!editBlock)
1211 ++revision;
1212
1213 for (int i = 0; i < cursors.size(); ++i) {
1214 QTextCursorPrivate *curs = cursors.at(i);
1215 if (curs->adjustPosition(from, addedOrRemoved, op) == QTextCursorPrivate::CursorMoved) {
1216 if (!changedCursors.contains(curs))
1217 changedCursors.append(curs);
1218 }
1219 }
1220
1221// qDebug("QTextDocumentPrivate::adjustDocumentChanges: from=%d,addedOrRemoved=%d", from, addedOrRemoved);
1222 if (docChangeFrom < 0) {
1223 docChangeFrom = from;
1224 if (addedOrRemoved > 0) {
1225 docChangeOldLength = 0;
1226 docChangeLength = addedOrRemoved;
1227 } else {
1228 docChangeOldLength = -addedOrRemoved;
1229 docChangeLength = 0;
1230 }
1231// qDebug("adjustDocumentChanges:");
1232// qDebug(" -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength);
1233 return;
1234 }
1235
1236 // have to merge the new change with the already existing one.
1237 int added = qMax(0, addedOrRemoved);
1238 int removed = qMax(0, -addedOrRemoved);
1239
1240 int diff = 0;
1241 if(from + removed < docChangeFrom)
1242 diff = docChangeFrom - from - removed;
1243 else if(from > docChangeFrom + docChangeLength)
1244 diff = from - (docChangeFrom + docChangeLength);
1245
1246 int overlap_start = qMax(from, docChangeFrom);
1247 int overlap_end = qMin(from + removed, docChangeFrom + docChangeLength);
1248 int removedInside = qMax(0, overlap_end - overlap_start);
1249 removed -= removedInside;
1250
1251// qDebug("adjustDocumentChanges: from=%d, addedOrRemoved=%d, diff=%d, removedInside=%d", from, addedOrRemoved, diff, removedInside);
1252 docChangeFrom = qMin(docChangeFrom, from);
1253 docChangeOldLength += removed + diff;
1254 docChangeLength += added - removedInside + diff;
1255// qDebug(" -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength);
1256
1257}
1258
1259
1260QString QTextDocumentPrivate::plainText() const
1261{
1262 QString result;
1263 result.resize(length());
1264 const QChar *text_unicode = text.unicode();
1265 QChar *data = result.data();
1266 for (QTextDocumentPrivate::FragmentIterator it = begin(); it != end(); ++it) {
1267 const QTextFragmentData *f = *it;
1268 ::memcpy(data, text_unicode + f->stringPosition, f->size_array[0] * sizeof(QChar));
1269 data += f->size_array[0];
1270 }
1271 // remove trailing block separator
1272 result.chop(1);
1273 return result;
1274}
1275
1276int QTextDocumentPrivate::blockCharFormatIndex(int node) const
1277{
1278 int pos = blocks.position(node);
1279 if (pos == 0)
1280 return initialBlockCharFormatIndex;
1281
1282 return fragments.find(pos - 1)->format;
1283}
1284
1285int QTextDocumentPrivate::nextCursorPosition(int position, QTextLayout::CursorMode mode) const
1286{
1287 if (position == length()-1)
1288 return position;
1289
1290 QTextBlock it = blocksFind(position);
1291 int start = it.position();
1292 int end = start + it.length() - 1;
1293 if (position == end)
1294 return end + 1;
1295
1296 return it.layout()->nextCursorPosition(position-start, mode) + start;
1297}
1298
1299int QTextDocumentPrivate::previousCursorPosition(int position, QTextLayout::CursorMode mode) const
1300{
1301 if (position == 0)
1302 return position;
1303
1304 QTextBlock it = blocksFind(position);
1305 int start = it.position();
1306 if (position == start)
1307 return start - 1;
1308
1309 return it.layout()->previousCursorPosition(position-start, mode) + start;
1310}
1311
1312void QTextDocumentPrivate::changeObjectFormat(QTextObject *obj, int format)
1313{
1314 beginEditBlock();
1315 int objectIndex = obj->objectIndex();
1316 int oldFormatIndex = formats.objectFormatIndex(objectIndex);
1317 formats.setObjectFormatIndex(objectIndex, format);
1318
1319 QTextBlockGroup *b = qobject_cast<QTextBlockGroup *>(obj);
1320 if (b) {
1321 b->d_func()->markBlocksDirty();
1322 }
1323 QTextFrame *f = qobject_cast<QTextFrame *>(obj);
1324 if (f)
1325 documentChange(f->firstPosition(), f->lastPosition() - f->firstPosition());
1326
1327 QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::GroupFormatChange, (editBlock != 0), QTextUndoCommand::MoveCursor, oldFormatIndex,
1328 0, 0, obj->d_func()->objectIndex, 0);
1329 appendUndoItem(c);
1330
1331 endEditBlock();
1332}
1333
1334static QTextFrame *findChildFrame(QTextFrame *f, int pos)
1335{
1336 // ##### use binary search
1337 QList<QTextFrame *> children = f->childFrames();
1338 for (int i = 0; i < children.size(); ++i) {
1339 QTextFrame *c = children.at(i);
1340 if (pos >= c->firstPosition() && pos <= c->lastPosition())
1341 return c;
1342 }
1343 return 0;
1344}
1345
1346QTextFrame *QTextDocumentPrivate::rootFrame() const
1347{
1348 if (!rtFrame) {
1349 QTextFrameFormat defaultRootFrameFormat;
1350 defaultRootFrameFormat.setMargin(documentMargin);
1351 rtFrame = qobject_cast<QTextFrame *>(const_cast<QTextDocumentPrivate *>(this)->createObject(defaultRootFrameFormat));
1352 }
1353 return rtFrame;
1354}
1355
1356QTextFrame *QTextDocumentPrivate::frameAt(int pos) const
1357{
1358 QTextFrame *f = rootFrame();
1359
1360 while (1) {
1361 QTextFrame *c = findChildFrame(f, pos);
1362 if (!c)
1363 return f;
1364 f = c;
1365 }
1366}
1367
1368void QTextDocumentPrivate::clearFrame(QTextFrame *f)
1369{
1370 for (int i = 0; i < f->d_func()->childFrames.count(); ++i)
1371 clearFrame(f->d_func()->childFrames.at(i));
1372 f->d_func()->childFrames.clear();
1373 f->d_func()->parentFrame = 0;
1374}
1375
1376void QTextDocumentPrivate::scan_frames(int pos, int charsRemoved, int charsAdded)
1377{
1378 // ###### optimise
1379 Q_UNUSED(pos);
1380 Q_UNUSED(charsRemoved);
1381 Q_UNUSED(charsAdded);
1382
1383 QTextFrame *f = rootFrame();
1384 clearFrame(f);
1385
1386 for (FragmentIterator it = begin(); it != end(); ++it) {
1387 // QTextFormat fmt = formats.format(it->format);
1388 QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(it->format));
1389 if (!frame)
1390 continue;
1391
1392 Q_ASSERT(it.size() == 1);
1393 QChar ch = text.at(it->stringPosition);
1394
1395 if (ch == QTextBeginningOfFrame) {
1396 if (f != frame) {
1397 // f == frame happens for tables
1398 Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0);
1399 frame->d_func()->parentFrame = f;
1400 f->d_func()->childFrames.append(frame);
1401 f = frame;
1402 }
1403 } else if (ch == QTextEndOfFrame) {
1404 Q_ASSERT(f == frame);
1405 Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0);
1406 f = frame->d_func()->parentFrame;
1407 } else if (ch == QChar::ObjectReplacementCharacter) {
1408 Q_ASSERT(f != frame);
1409 Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0);
1410 Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0);
1411 frame->d_func()->parentFrame = f;
1412 f->d_func()->childFrames.append(frame);
1413 } else {
1414 Q_ASSERT(false);
1415 }
1416 }
1417 Q_ASSERT(f == rtFrame);
1418 framesDirty = false;
1419}
1420
1421void QTextDocumentPrivate::insert_frame(QTextFrame *f)
1422{
1423 int start = f->firstPosition();
1424 int end = f->lastPosition();
1425 QTextFrame *parent = frameAt(start-1);
1426 Q_ASSERT(parent == frameAt(end+1));
1427
1428 if (start != end) {
1429 // iterator over the parent and move all children contained in my frame to myself
1430 for (int i = 0; i < parent->d_func()->childFrames.size(); ++i) {
1431 QTextFrame *c = parent->d_func()->childFrames.at(i);
1432 if (start < c->firstPosition() && end > c->lastPosition()) {
1433 parent->d_func()->childFrames.removeAt(i);
1434 f->d_func()->childFrames.append(c);
1435 c->d_func()->parentFrame = f;
1436 }
1437 }
1438 }
1439 // insert at the correct position
1440 int i = 0;
1441 for (; i < parent->d_func()->childFrames.size(); ++i) {
1442 QTextFrame *c = parent->d_func()->childFrames.at(i);
1443 if (c->firstPosition() > end)
1444 break;
1445 }
1446 parent->d_func()->childFrames.insert(i, f);
1447 f->d_func()->parentFrame = parent;
1448}
1449
1450QTextFrame *QTextDocumentPrivate::insertFrame(int start, int end, const QTextFrameFormat &format)
1451{
1452 Q_ASSERT(start >= 0 && start < length());
1453 Q_ASSERT(end >= 0 && end < length());
1454 Q_ASSERT(start <= end || end == -1);
1455
1456 if (start != end && frameAt(start) != frameAt(end))
1457 return 0;
1458
1459 beginEditBlock();
1460
1461 QTextFrame *frame = qobject_cast<QTextFrame *>(createObject(format));
1462 Q_ASSERT(frame);
1463
1464 // #### using the default block and char format below might be wrong
1465 int idx = formats.indexForFormat(QTextBlockFormat());
1466 QTextCharFormat cfmt;
1467 cfmt.setObjectIndex(frame->objectIndex());
1468 int charIdx = formats.indexForFormat(cfmt);
1469
1470 insertBlock(QTextBeginningOfFrame, start, idx, charIdx, QTextUndoCommand::MoveCursor);
1471 insertBlock(QTextEndOfFrame, ++end, idx, charIdx, QTextUndoCommand::KeepCursor);
1472
1473 frame->d_func()->fragment_start = find(start).n;
1474 frame->d_func()->fragment_end = find(end).n;
1475
1476 insert_frame(frame);
1477
1478 endEditBlock();
1479
1480 return frame;
1481}
1482
1483void QTextDocumentPrivate::removeFrame(QTextFrame *frame)
1484{
1485 QTextFrame *parent = frame->d_func()->parentFrame;
1486 if (!parent)
1487 return;
1488
1489 int start = frame->firstPosition();
1490 int end = frame->lastPosition();
1491 Q_ASSERT(end >= start);
1492
1493 beginEditBlock();
1494
1495 // remove already removes the frames from the tree
1496 remove(end, 1);
1497 remove(start-1, 1);
1498
1499 endEditBlock();
1500}
1501
1502QTextObject *QTextDocumentPrivate::objectForIndex(int objectIndex) const
1503{
1504 if (objectIndex < 0)
1505 return 0;
1506
1507 QTextObject *object = objects.value(objectIndex, 0);
1508 if (!object) {
1509 QTextDocumentPrivate *that = const_cast<QTextDocumentPrivate *>(this);
1510 QTextFormat fmt = formats.objectFormat(objectIndex);
1511 object = that->createObject(fmt, objectIndex);
1512 }
1513 return object;
1514}
1515
1516QTextObject *QTextDocumentPrivate::objectForFormat(int formatIndex) const
1517{
1518 int objectIndex = formats.format(formatIndex).objectIndex();
1519 return objectForIndex(objectIndex);
1520}
1521
1522QTextObject *QTextDocumentPrivate::objectForFormat(const QTextFormat &f) const
1523{
1524 return objectForIndex(f.objectIndex());
1525}
1526
1527QTextObject *QTextDocumentPrivate::createObject(const QTextFormat &f, int objectIndex)
1528{
1529 QTextObject *obj = document()->createObject(f);
1530
1531 if (obj) {
1532 obj->d_func()->objectIndex = objectIndex == -1 ? formats.createObjectIndex(f) : objectIndex;
1533 objects[obj->d_func()->objectIndex] = obj;
1534 }
1535
1536 return obj;
1537}
1538
1539void QTextDocumentPrivate::deleteObject(QTextObject *object)
1540{
1541 const int objIdx = object->d_func()->objectIndex;
1542 objects.remove(objIdx);
1543 delete object;
1544}
1545
1546void QTextDocumentPrivate::contentsChanged()
1547{
1548 Q_Q(QTextDocument);
1549 if (editBlock)
1550 return;
1551
1552 bool m = undoEnabled ? (modifiedState != undoState) : true;
1553 if (modified != m) {
1554 modified = m;
1555 emit q->modificationChanged(modified);
1556 }
1557
1558 emit q->contentsChanged();
1559}
1560
1561void QTextDocumentPrivate::compressPieceTable()
1562{
1563 if (undoEnabled)
1564 return;
1565
1566 const uint garbageCollectionThreshold = 96 * 1024; // bytes
1567
1568 //qDebug() << "unreachable bytes:" << unreachableCharacterCount * sizeof(QChar) << " -- limit" << garbageCollectionThreshold << "text size =" << text.size() << "capacity:" << text.capacity();
1569
1570 bool compressTable = unreachableCharacterCount * sizeof(QChar) > garbageCollectionThreshold
1571 && text.size() >= text.capacity() * 0.9;
1572 if (!compressTable)
1573 return;
1574
1575 QString newText;
1576 newText.resize(text.size());
1577 QChar *newTextPtr = newText.data();
1578 int newLen = 0;
1579
1580 for (FragmentMap::Iterator it = fragments.begin(); !it.atEnd(); ++it) {
1581 qMemCopy(newTextPtr, text.constData() + it->stringPosition, it->size_array[0] * sizeof(QChar));
1582 it->stringPosition = newLen;
1583 newTextPtr += it->size_array[0];
1584 newLen += it->size_array[0];
1585 }
1586
1587 newText.resize(newLen);
1588 newText.squeeze();
1589 //qDebug() << "removed" << text.size() - newText.size() << "characters";
1590 text = newText;
1591 unreachableCharacterCount = 0;
1592}
1593
1594void QTextDocumentPrivate::setModified(bool m)
1595{
1596 Q_Q(QTextDocument);
1597 if (m == modified)
1598 return;
1599
1600 modified = m;
1601 if (!modified)
1602 modifiedState = undoState;
1603 else
1604 modifiedState = -1;
1605
1606 emit q->modificationChanged(modified);
1607}
1608
1609bool QTextDocumentPrivate::ensureMaximumBlockCount()
1610{
1611 if (maximumBlockCount <= 0)
1612 return false;
1613 if (blocks.numNodes() <= maximumBlockCount)
1614 return false;
1615
1616 beginEditBlock();
1617
1618 const int blocksToRemove = blocks.numNodes() - maximumBlockCount;
1619 QTextCursor cursor(this, 0);
1620 cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, blocksToRemove);
1621
1622 unreachableCharacterCount += cursor.selectionEnd() - cursor.selectionStart();
1623
1624 // preserve the char format of the paragraph that is to become the new first one
1625 QTextCharFormat charFmt = cursor.blockCharFormat();
1626 cursor.removeSelectedText();
1627 cursor.setBlockCharFormat(charFmt);
1628
1629 endEditBlock();
1630
1631 compressPieceTable();
1632
1633 return true;
1634}
1635
1636/// This method is called from QTextTable when it is about to remove a table-cell to allow cursors to update their selection.
1637void QTextDocumentPrivate::aboutToRemoveCell(int from, int to)
1638{
1639 Q_ASSERT(from <= to);
1640 for (int i = 0; i < cursors.size(); ++i)
1641 cursors.at(i)->aboutToRemoveCell(from, to);
1642}
1643
1644QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.