Coverage Report

Created: 2026-08-25 06:40

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