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