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

Last change on this file since 219 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

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