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