Coverage Report

Created: 2026-05-16 07:21

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