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(QStringView 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(nullptr),
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 = nullptr;
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 QSet<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 = nullptr;
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 = nullptr;
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(QStringView{text}.mid(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(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(QStringView{text}.mid(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(QStringView{text}.mid(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 = nullptr;
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, int(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(i);
1110 if (c.command & QTextUndoCommand::Custom)
1111 delete c.custom;
1112 }
1113 undoStack.remove(0, undoState);
1114 undoState = 0;
1115 if (emitSignals)
1116 emitUndoAvailable(false);
1117 } else if (stacksToClear == QTextDocument::RedoStack
1118 && redoCommandsAvailable) {
1119 for (int i = undoState; i < undoStack.size(); ++i) {
1120 QTextUndoCommand c = undoStack.at(i);
1121 if (c.command & QTextUndoCommand::Custom)
1122 delete c.custom;
1123 }
1124 undoStack.resize(undoState);
1125 if (emitSignals)
1126 emitRedoAvailable(false);
1127 } else if (stacksToClear == QTextDocument::UndoAndRedoStacks
1128 && !undoStack.isEmpty()) {
1129 for (int i = 0; i < undoStack.size(); ++i) {
1130 QTextUndoCommand c = undoStack.at(i);
1131 if (c.command & QTextUndoCommand::Custom)
1132 delete c.custom;
1133 }
1134 undoState = 0;
1135 undoStack.clear();
1136 if (emitSignals && undoCommandsAvailable)
1137 emitUndoAvailable(false);
1138 if (emitSignals && redoCommandsAvailable)
1139 emitRedoAvailable(false);
1140 }
1141}
1142
1143void QTextDocumentPrivate::emitUndoAvailable(bool available)
1144{
1145 if (available != wasUndoAvailable) {
1146 Q_Q(QTextDocument);
1147 emit q->undoAvailable(available);
1148 wasUndoAvailable = available;
1149 }
1150}
1151
1152void QTextDocumentPrivate::emitRedoAvailable(bool available)
1153{
1154 if (available != wasRedoAvailable) {
1155 Q_Q(QTextDocument);
1156 emit q->redoAvailable(available);
1157 wasRedoAvailable = available;
1158 }
1159}
1160
1161void QTextDocumentPrivate::enableUndoRedo(bool enable)
1162{
1163 if (enable && maximumBlockCount > 0)
1164 return;
1165
1166 if (!enable) {
1167 undoState = 0;
1168 clearUndoRedoStacks(QTextDocument::RedoStack);
1169 emitUndoAvailable(false);
1170 emitRedoAvailable(false);
1171 }
1172 modifiedState = modified ? -1 : undoState;
1173 undoEnabled = enable;
1174 if (!undoEnabled)
1175 compressPieceTable();
1176}
1177
1178void QTextDocumentPrivate::joinPreviousEditBlock()
1179{
1180 beginEditBlock();
1181
1182 if (undoEnabled && undoState)
1183 undoStack[undoState - 1].block_end = false;
1184}
1185
1186void QTextDocumentPrivate::endEditBlock()
1187{
1188 Q_ASSERT(editBlock > 0);
1189 if (--editBlock)
1190 return;
1191
1192 if (undoEnabled && undoState > 0) {
1193 const bool wasBlocking = !undoStack.at(undoState - 1).block_end;
1194 if (undoStack.at(undoState - 1).block_part) {
1195 undoStack[undoState - 1].block_end = true;
1196 if (wasBlocking)
1197 emit document()->undoCommandAdded();
1198 }
1199 }
1200
1201 editBlockCursorPosition = -1;
1202
1203 finishEdit();
1204}
1205
1206void QTextDocumentPrivate::finishEdit()
1207{
1208 Q_Q(QTextDocument);
1209
1210 if (editBlock)
1211 return;
1212
1213 if (framesDirty)
1214 scan_frames(docChangeFrom, docChangeOldLength, docChangeLength);
1215
1216 if (lout && docChangeFrom >= 0) {
1217 if (!inContentsChange) {
1218 QScopedValueRollback<bool> bg(inContentsChange, true);
1219 emit q->contentsChange(docChangeFrom, docChangeOldLength, docChangeLength);
1220 }
1221 lout->documentChanged(docChangeFrom, docChangeOldLength, docChangeLength);
1222 }
1223
1224 docChangeFrom = -1;
1225
1226 if (needsEnsureMaximumBlockCount) {
1227 needsEnsureMaximumBlockCount = false;
1228 if (ensureMaximumBlockCount()) {
1229 // if ensureMaximumBlockCount() returns true
1230 // it will have called endEditBlock() and
1231 // compressPieceTable() itself, so we return here
1232 // to prevent getting two contentsChanged emits
1233 return;
1234 }
1235 }
1236
1237 QList<QTextCursor> changedCursors;
1238 for (QTextCursorPrivate *curs : qAsConst(cursors)) {
1239 if (curs->changed) {
1240 curs->changed = false;
1241 changedCursors.append(QTextCursor(curs));
1242 }
1243 }
1244 for (const QTextCursor &cursor : qAsConst(changedCursors))
1245 emit q->cursorPositionChanged(cursor);
1246
1247 contentsChanged();
1248
1249 if (blocks.numNodes() != lastBlockCount) {
1250 lastBlockCount = blocks.numNodes();
1251 emit q->blockCountChanged(lastBlockCount);
1252 }
1253
1254 if (!undoEnabled && unreachableCharacterCount)
1255 compressPieceTable();
1256}
1257
1258void QTextDocumentPrivate::documentChange(int from, int length)
1259{
1260// qDebug("QTextDocumentPrivate::documentChange: from=%d,length=%d", from, length);
1261 if (docChangeFrom < 0) {
1262 docChangeFrom = from;
1263 docChangeOldLength = length;
1264 docChangeLength = length;
1265 return;
1266 }
1267 int start = qMin(from, docChangeFrom);
1268 int end = qMax(from + length, docChangeFrom + docChangeLength);
1269 int diff = qMax(0, end - start - docChangeLength);
1270 docChangeFrom = start;
1271 docChangeOldLength += diff;
1272 docChangeLength += diff;
1273}
1274
1275/*
1276 adjustDocumentChangesAndCursors is called whenever there is an insert or remove of characters.
1277 param from is the cursor position in the document
1278 param addedOrRemoved is the amount of characters added or removed. A negative number means characters are removed.
1279
1280 The function stores information to be emitted when finishEdit() is called.
1281*/
1282void QTextDocumentPrivate::adjustDocumentChangesAndCursors(int from, int addedOrRemoved, QTextUndoCommand::Operation op)
1283{
1284 if (!editBlock)
1285 ++revision;
1286
1287 if (blockCursorAdjustment) {
1288 ; // postpone, will be called again from QTextDocumentPrivate::remove()
1289 } else {
1290 for (QTextCursorPrivate *curs : qAsConst(cursors)) {
1291 if (curs->adjustPosition(from, addedOrRemoved, op) == QTextCursorPrivate::CursorMoved) {
1292 curs->changed = true;
1293 }
1294 }
1295 }
1296
1297// qDebug("QTextDocumentPrivate::adjustDocumentChanges: from=%d,addedOrRemoved=%d", from, addedOrRemoved);
1298 if (docChangeFrom < 0) {
1299 docChangeFrom = from;
1300 if (addedOrRemoved > 0) {
1301 docChangeOldLength = 0;
1302 docChangeLength = addedOrRemoved;
1303 } else {
1304 docChangeOldLength = -addedOrRemoved;
1305 docChangeLength = 0;
1306 }
1307// qDebug("adjustDocumentChanges:");
1308// qDebug(" -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength);
1309 return;
1310 }
1311
1312 // have to merge the new change with the already existing one.
1313 int added = qMax(0, addedOrRemoved);
1314 int removed = qMax(0, -addedOrRemoved);
1315
1316 int diff = 0;
1317 if (from + removed < docChangeFrom)
1318 diff = docChangeFrom - from - removed;
1319 else if (from > docChangeFrom + docChangeLength)
1320 diff = from - (docChangeFrom + docChangeLength);
1321
1322 int overlap_start = qMax(from, docChangeFrom);
1323 int overlap_end = qMin(from + removed, docChangeFrom + docChangeLength);
1324 int removedInside = qMax(0, overlap_end - overlap_start);
1325 removed -= removedInside;
1326
1327// qDebug("adjustDocumentChanges: from=%d, addedOrRemoved=%d, diff=%d, removedInside=%d", from, addedOrRemoved, diff, removedInside);
1328 docChangeFrom = qMin(docChangeFrom, from);
1329 docChangeOldLength += removed + diff;
1330 docChangeLength += added - removedInside + diff;
1331// qDebug(" -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength);
1332
1333}
1334
1335
1336QString QTextDocumentPrivate::plainText() const
1337{
1338 QString result;
1339 result.resize(length());
1340 const QChar *text_unicode = text.unicode();
1341 QChar *data = result.data();
1342 for (QTextDocumentPrivate::FragmentIterator it = begin(); it != end(); ++it) {
1343 const QTextFragmentData *f = *it;
1344 ::memcpy(data, text_unicode + f->stringPosition, f->size_array[0] * sizeof(QChar));
1345 data += f->size_array[0];
1346 }
1347 // remove trailing block separator
1348 result.chop(1);
1349 return result;
1350}
1351
1352int QTextDocumentPrivate::blockCharFormatIndex(int node) const
1353{
1354 int pos = blocks.position(node);
1355 if (pos == 0)
1356 return initialBlockCharFormatIndex;
1357
1358 return fragments.find(pos - 1)->format;
1359}
1360
1361int QTextDocumentPrivate::nextCursorPosition(int position, QTextLayout::CursorMode mode) const
1362{
1363 if (position == length()-1)
1364 return position;
1365
1366 QTextBlock it = blocksFind(position);
1367 int start = it.position();
1368 int end = start + it.length() - 1;
1369 if (position == end)
1370 return end + 1;
1371
1372 return it.layout()->nextCursorPosition(position-start, mode) + start;
1373}
1374
1375int QTextDocumentPrivate::previousCursorPosition(int position, QTextLayout::CursorMode mode) const
1376{
1377 if (position == 0)
1378 return position;
1379
1380 QTextBlock it = blocksFind(position);
1381 int start = it.position();
1382 if (position == start)
1383 return start - 1;
1384
1385 return it.layout()->previousCursorPosition(position-start, mode) + start;
1386}
1387
1388int QTextDocumentPrivate::leftCursorPosition(int position) const
1389{
1390 QTextBlock it = blocksFind(position);
1391 int start = it.position();
1392 return it.layout()->leftCursorPosition(position-start) + start;
1393}
1394
1395int QTextDocumentPrivate::rightCursorPosition(int position) const
1396{
1397 QTextBlock it = blocksFind(position);
1398 int start = it.position();
1399 return it.layout()->rightCursorPosition(position-start) + start;
1400}
1401
1402void QTextDocumentPrivate::changeObjectFormat(QTextObject *obj, int format)
1403{
1404 beginEditBlock();
1405 int objectIndex = obj->objectIndex();
1406 int oldFormatIndex = formats.objectFormatIndex(objectIndex);
1407 formats.setObjectFormatIndex(objectIndex, format);
1408
1409 QTextBlockGroup *b = qobject_cast<QTextBlockGroup *>(obj);
1410 if (b) {
1411 b->d_func()->markBlocksDirty();
1412 }
1413 QTextFrame *f = qobject_cast<QTextFrame *>(obj);
1414 if (f)
1415 documentChange(f->firstPosition(), f->lastPosition() - f->firstPosition());
1416
1417 QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::GroupFormatChange, (editBlock != 0), QTextUndoCommand::MoveCursor, oldFormatIndex,
1418 0, 0, obj->d_func()->objectIndex, 0);
1419 appendUndoItem(c);
1420
1421 endEditBlock();
1422}
1423
1424static QTextFrame *findChildFrame(QTextFrame *f, int pos)
1425{
1426 /* Binary search for frame at pos */
1427 const QList<QTextFrame *> children = f->childFrames();
1428 int first = 0;
1429 int last = children.size() - 1;
1430 while (first <= last) {
1431 int mid = (first + last) / 2;
1432 QTextFrame *c = children.at(mid);
1433 if (pos > c->lastPosition())
1434 first = mid + 1;
1435 else if (pos < c->firstPosition())
1436 last = mid - 1;
1437 else
1438 return c;
1439 }
1440 return nullptr;
1441}
1442
1443QTextFrame *QTextDocumentPrivate::rootFrame() const
1444{
1445 if (!rtFrame) {
1446 QTextFrameFormat defaultRootFrameFormat;
1447 defaultRootFrameFormat.setMargin(documentMargin);
1448 rtFrame = qobject_cast<QTextFrame *>(const_cast<QTextDocumentPrivate *>(this)->createObject(defaultRootFrameFormat));
1449 }
1450 return rtFrame;
1451}
1452
1453QTextFrame *QTextDocumentPrivate::frameAt(int pos) const
1454{
1455 QTextFrame *f = rootFrame();
1456
1457 while (1) {
1458 QTextFrame *c = findChildFrame(f, pos);
1459 if (!c)
1460 return f;
1461 f = c;
1462 }
1463}
1464
1465void QTextDocumentPrivate::clearFrame(QTextFrame *f)
1466{
1467 for (int i = 0; i < f->d_func()->childFrames.count(); ++i)
1468 clearFrame(f->d_func()->childFrames.at(i));
1469 f->d_func()->childFrames.clear();
1470 f->d_func()->parentFrame = nullptr;
1471}
1472
1473void QTextDocumentPrivate::scan_frames(int pos, int charsRemoved, int charsAdded)
1474{
1475 // ###### optimize
1476 Q_UNUSED(pos);
1477 Q_UNUSED(charsRemoved);
1478 Q_UNUSED(charsAdded);
1479
1480 QTextFrame *f = rootFrame();
1481 clearFrame(f);
1482
1483 for (FragmentIterator it = begin(); it != end(); ++it) {
1484 // QTextFormat fmt = formats.format(it->format);
1485 QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(it->format));
1486 if (!frame)
1487 continue;
1488
1489 Q_ASSERT(it.size() == 1);
1490 QChar ch = text.at(it->stringPosition);
1491
1492 if (ch == QTextBeginningOfFrame) {
1493 if (f != frame) {
1494 // f == frame happens for tables
1495 Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0);
1496 frame->d_func()->parentFrame = f;
1497 f->d_func()->childFrames.append(frame);
1498 f = frame;
1499 }
1500 } else if (ch == QTextEndOfFrame) {
1501 Q_ASSERT(f == frame);
1502 Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0);
1503 f = frame->d_func()->parentFrame;
1504 } else if (ch == QChar::ObjectReplacementCharacter) {
1505 Q_ASSERT(f != frame);
1506 Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0);
1507 Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0);
1508 frame->d_func()->parentFrame = f;
1509 f->d_func()->childFrames.append(frame);
1510 } else {
1511 Q_ASSERT(false);
1512 }
1513 }
1514 Q_ASSERT(f == rtFrame);
1515 framesDirty = false;
1516}
1517
1518void QTextDocumentPrivate::insert_frame(QTextFrame *f)
1519{
1520 int start = f->firstPosition();
1521 int end = f->lastPosition();
1522 QTextFrame *parent = frameAt(start-1);
1523 Q_ASSERT(parent == frameAt(end+1));
1524
1525 if (start != end) {
1526 // iterator over the parent and move all children contained in my frame to myself
1527 for (int i = 0; i < parent->d_func()->childFrames.size(); ++i) {
1528 QTextFrame *c = parent->d_func()->childFrames.at(i);
1529 if (start < c->firstPosition() && end > c->lastPosition()) {
1530 parent->d_func()->childFrames.removeAt(i);
1531 f->d_func()->childFrames.append(c);
1532 c->d_func()->parentFrame = f;
1533 }
1534 }
1535 }
1536 // insert at the correct position
1537 int i = 0;
1538 for (; i < parent->d_func()->childFrames.size(); ++i) {
1539 QTextFrame *c = parent->d_func()->childFrames.at(i);
1540 if (c->firstPosition() > end)
1541 break;
1542 }
1543 parent->d_func()->childFrames.insert(i, f);
1544 f->d_func()->parentFrame = parent;
1545}
1546
1547QTextFrame *QTextDocumentPrivate::insertFrame(int start, int end, const QTextFrameFormat &format)
1548{
1549 Q_ASSERT(start >= 0 && start < length());
1550 Q_ASSERT(end >= 0 && end < length());
1551 Q_ASSERT(start <= end || end == -1);
1552
1553 if (start != end && frameAt(start) != frameAt(end))
1554 return nullptr;
1555
1556 beginEditBlock();
1557
1558 QTextFrame *frame = qobject_cast<QTextFrame *>(createObject(format));
1559 Q_ASSERT(frame);
1560
1561 // #### using the default block and char format below might be wrong
1562 int idx = formats.indexForFormat(QTextBlockFormat());
1563 QTextCharFormat cfmt;
1564 cfmt.setObjectIndex(frame->objectIndex());
1565 int charIdx = formats.indexForFormat(cfmt);
1566
1567 insertBlock(QTextBeginningOfFrame, start, idx, charIdx, QTextUndoCommand::MoveCursor);
1568 insertBlock(QTextEndOfFrame, ++end, idx, charIdx, QTextUndoCommand::KeepCursor);
1569
1570 frame->d_func()->fragment_start = find(start).n;
1571 frame->d_func()->fragment_end = find(end).n;
1572
1573 insert_frame(frame);
1574
1575 endEditBlock();
1576
1577 return frame;
1578}
1579
1580void QTextDocumentPrivate::removeFrame(QTextFrame *frame)
1581{
1582 QTextFrame *parent = frame->d_func()->parentFrame;
1583 if (!parent)
1584 return;
1585
1586 int start = frame->firstPosition();
1587 int end = frame->lastPosition();
1588 Q_ASSERT(end >= start);
1589
1590 beginEditBlock();
1591
1592 // remove already removes the frames from the tree
1593 remove(end, 1);
1594 remove(start-1, 1);
1595
1596 endEditBlock();
1597}
1598
1599QTextObject *QTextDocumentPrivate::objectForIndex(int objectIndex) const
1600{
1601 if (objectIndex < 0)
1602 return nullptr;
1603
1604 QTextObject *object = objects.value(objectIndex, nullptr);
1605 if (!object) {
1606 QTextDocumentPrivate *that = const_cast<QTextDocumentPrivate *>(this);
1607 QTextFormat fmt = formats.objectFormat(objectIndex);
1608 object = that->createObject(fmt, objectIndex);
1609 }
1610 return object;
1611}
1612
1613QTextObject *QTextDocumentPrivate::objectForFormat(int formatIndex) const
1614{
1615 int objectIndex = formats.format(formatIndex).objectIndex();
1616 return objectForIndex(objectIndex);
1617}
1618
1619QTextObject *QTextDocumentPrivate::objectForFormat(const QTextFormat &f) const
1620{
1621 return objectForIndex(f.objectIndex());
1622}
1623
1624QTextObject *QTextDocumentPrivate::createObject(const QTextFormat &f, int objectIndex)
1625{
1626 QTextObject *obj = document()->createObject(f);
1627
1628 if (obj) {
1629 obj->d_func()->objectIndex = objectIndex == -1 ? formats.createObjectIndex(f) : objectIndex;
1630 objects[obj->d_func()->objectIndex] = obj;
1631 }
1632
1633 return obj;
1634}
1635
1636void QTextDocumentPrivate::deleteObject(QTextObject *object)
1637{
1638 const int objIdx = object->d_func()->objectIndex;
1639 objects.remove(objIdx);
1640 delete object;
1641}
1642
1643void QTextDocumentPrivate::contentsChanged()
1644{
1645 Q_Q(QTextDocument);
1646 if (editBlock)
1647 return;
1648
1649 bool m = undoEnabled ? (modifiedState != undoState) : true;
1650 if (modified != m) {
1651 modified = m;
1652 emit q->modificationChanged(modified);
1653 }
1654
1655 emit q->contentsChanged();
1656}
1657
1658void QTextDocumentPrivate::compressPieceTable()
1659{
1660 if (undoEnabled)
1661 return;
1662
1663 const uint garbageCollectionThreshold = 96 * 1024; // bytes
1664
1665 //qDebug() << "unreachable bytes:" << unreachableCharacterCount * sizeof(QChar) << " -- limit" << garbageCollectionThreshold << "text size =" << text.size() << "capacity:" << text.capacity();
1666
1667 bool compressTable = unreachableCharacterCount * sizeof(QChar) > garbageCollectionThreshold
1668 && text.size() >= text.capacity() * 0.9;
1669 if (!compressTable)
1670 return;
1671
1672 QString newText;
1673 newText.resize(text.size());
1674 QChar *newTextPtr = newText.data();
1675 int newLen = 0;
1676
1677 for (FragmentMap::Iterator it = fragments.begin(); !it.atEnd(); ++it) {
1678 memcpy(newTextPtr, text.constData() + it->stringPosition, it->size_array[0] * sizeof(QChar));
1679 it->stringPosition = newLen;
1680 newTextPtr += it->size_array[0];
1681 newLen += it->size_array[0];
1682 }
1683
1684 newText.resize(newLen);
1685 newText.squeeze();
1686 //qDebug() << "removed" << text.size() - newText.size() << "characters";
1687 text = newText;
1688 unreachableCharacterCount = 0;
1689}
1690
1691void QTextDocumentPrivate::setModified(bool m)
1692{
1693 Q_Q(QTextDocument);
1694 if (m == modified)
1695 return;
1696
1697 modified = m;
1698 if (!modified)
1699 modifiedState = undoState;
1700 else
1701 modifiedState = -1;
1702
1703 emit q->modificationChanged(modified);
1704}
1705
1706bool QTextDocumentPrivate::ensureMaximumBlockCount()
1707{
1708 if (maximumBlockCount <= 0)
1709 return false;
1710 if (blocks.numNodes() <= maximumBlockCount)
1711 return false;
1712
1713 beginEditBlock();
1714
1715 const int blocksToRemove = blocks.numNodes() - maximumBlockCount;
1716 QTextCursor cursor(this, 0);
1717 cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, blocksToRemove);
1718
1719 unreachableCharacterCount += cursor.selectionEnd() - cursor.selectionStart();
1720
1721 // preserve the char format of the paragraph that is to become the new first one
1722 QTextCharFormat charFmt = cursor.blockCharFormat();
1723 cursor.removeSelectedText();
1724 cursor.setBlockCharFormat(charFmt);
1725
1726 endEditBlock();
1727
1728 compressPieceTable();
1729
1730 return true;
1731}
1732
1733/// This method is called from QTextTable when it is about to remove a table-cell to allow cursors to update their selection.
1734void QTextDocumentPrivate::aboutToRemoveCell(int from, int to)
1735{
1736 Q_ASSERT(from <= to);
1737 for (QTextCursorPrivate *curs : qAsConst(cursors))
1738 curs->aboutToRemoveCell(from, to);
1739}
1740
1741QT_END_NAMESPACE
1742