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