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