Coverage Report

Created: 2026-10-14 06:45

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
    QVarLengthArray<QTextCursor, 4> changedCursors;
208
0
    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
0
    QSet<QTextCursorPrivate *> oldCursors = cursors;
218
0
    QT_TRY{
219
0
        cursors.clear();
220
221
0
        QMap<int, QTextObject *>::Iterator objectIt = objects.begin();
222
0
        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
0
        objects.clear();
233
234
0
        title.clear();
235
0
        clearUndoRedoStacks(QTextDocument::UndoAndRedoStacks);
236
0
        text = QString();
237
0
        unreachableCharacterCount = 0;
238
0
        modifiedState = 0;
239
0
        modified = false;
240
0
        formats.clear();
241
0
        int len = fragments.length();
242
0
        fragments.clear();
243
0
        blocks.clear();
244
0
        cachedResources.clear();
245
0
        delete rtFrame;
246
0
        rtFrame = nullptr;
247
0
        init();
248
0
        cursors = oldCursors;
249
0
        {
250
0
            QScopedValueRollback<bool> bg(inContentsChange, true);
251
0
            emit q->contentsChange(0, len, 0);
252
0
        }
253
0
        if (lout)
254
0
            lout->documentChanged(0, len, 0);
255
0
    } QT_CATCH(...) {
256
0
        cursors = oldCursors; // at least recover the cursors
257
0
        QT_RETHROW;
258
0
    }
259
260
0
    for (const QTextCursor &cursor : std::as_const(changedCursors))
261
0
        emit q->cursorPositionChanged(cursor);
262
0
}
263
264
QTextDocumentPrivate::~QTextDocumentPrivate()
265
0
{
266
0
    for (QTextCursorPrivate *curs : std::as_const(cursors))
267
0
        curs->priv = nullptr;
268
0
    cursors.clear();
269
0
    undoState = 0;
270
0
    undoEnabled = true;
271
0
    clearUndoRedoStacks(QTextDocument::RedoStack);
272
0
}
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
0
{
299
    // ##### optimize when only appending to the fragment!
300
0
    Q_ASSERT(noBlockInString(QStringView{text}.mid(strPos, length)));
301
302
0
    split(pos);
303
0
    uint x = fragments.insert_single(pos, length);
304
0
    QTextFragmentData *X = fragments.fragment(x);
305
0
    X->format = format;
306
0
    X->stringPosition = strPos;
307
0
    uint w = fragments.previous(x);
308
0
    if (w)
309
0
        unite(w);
310
311
0
    int b = blocks.findNode(pos);
312
0
    blocks.setSize(b, blocks.size(b)+length);
313
314
0
    Q_ASSERT(blocks.length() == fragments.length());
315
316
0
    QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(format));
317
0
    if (frame) {
318
0
        frame->d_func()->fragmentAdded(text.at(strPos), x);
319
0
        framesDirty = true;
320
0
    }
321
322
0
    adjustDocumentChangesAndCursors(pos, length, op);
323
0
}
324
325
int QTextDocumentPrivate::insert_block(int pos, uint strPos, int format, int blockFormat, QTextUndoCommand::Operation op, int command)
326
0
{
327
0
    split(pos);
328
0
    uint x = fragments.insert_single(pos, 1);
329
0
    QTextFragmentData *X = fragments.fragment(x);
330
0
    X->format = format;
331
0
    X->stringPosition = strPos;
332
    // no need trying to unite, since paragraph separators are always in a fragment of their own
333
334
0
    Q_ASSERT(isValidBlockSeparator(text.at(strPos)));
335
0
    Q_ASSERT(blocks.length()+1 == fragments.length());
336
337
0
    int block_pos = pos;
338
0
    if (blocks.length() && command == QTextUndoCommand::BlockRemoved)
339
0
        ++block_pos;
340
0
    int size = 1;
341
0
    int n = blocks.findNode(block_pos);
342
0
    int key = n ? blocks.position(n) : blocks.length();
343
344
0
    Q_ASSERT(n || block_pos == blocks.length());
345
0
    if (key != block_pos) {
346
0
        Q_ASSERT(key < block_pos);
347
0
        int oldSize = blocks.size(n);
348
0
        blocks.setSize(n, block_pos-key);
349
0
        size += oldSize - (block_pos-key);
350
0
    }
351
0
    int b = blocks.insert_single(block_pos, size);
352
0
    QTextBlockData *B = blocks.fragment(b);
353
0
    B->format = blockFormat;
354
355
0
    Q_ASSERT(blocks.length() == fragments.length());
356
357
0
    QTextBlockGroup *group = qobject_cast<QTextBlockGroup *>(objectForFormat(blockFormat));
358
0
    if (group) {
359
0
        group->blockInserted(QTextBlock(this, b));
360
0
        if (command != QTextUndoCommand::BlockDeleted) {
361
0
            docChangeOldLength--;
362
0
            docChangeLength--;
363
0
        }
364
0
    }
365
366
0
    QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(formats.format(format)));
367
0
    if (frame) {
368
0
        frame->d_func()->fragmentAdded(text.at(strPos), x);
369
0
        framesDirty = true;
370
0
    }
371
372
0
    adjustDocumentChangesAndCursors(pos, 1, op);
373
0
    return x;
374
0
}
375
376
int QTextDocumentPrivate::insertBlock(QChar blockSeparator,
377
                                  int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation op)
378
0
{
379
0
    Q_ASSERT(formats.format(blockFormat).isBlockFormat());
380
0
    Q_ASSERT(formats.format(charFormat).isCharFormat());
381
0
    Q_ASSERT(pos >= 0 && (pos < fragments.length() || (pos == 0 && fragments.length() == 0)));
382
0
    Q_ASSERT(isValidBlockSeparator(blockSeparator));
383
384
0
    beginEditBlock();
385
386
0
    int strPos = text.size();
387
0
    text.append(blockSeparator);
388
389
0
    int ob = blocks.findNode(pos);
390
0
    bool atBlockEnd = true;
391
0
    bool atBlockStart = true;
392
0
    int oldRevision = 0;
393
0
    if (ob) {
394
0
        atBlockEnd = (pos - blocks.position(ob) == blocks.size(ob)-1);
395
0
        atBlockStart = ((int)blocks.position(ob) == pos);
396
0
        oldRevision = blocks.fragment(ob)->revision;
397
0
    }
398
399
0
    const int fragment = insert_block(pos, strPos, charFormat, blockFormat, op, QTextUndoCommand::BlockRemoved);
400
401
0
    Q_ASSERT(blocks.length() == fragments.length());
402
403
0
    int b = blocks.findNode(pos);
404
0
    QTextBlockData *B = blocks.fragment(b);
405
406
0
    QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::BlockInserted, (editBlock != 0),
407
0
                            op, charFormat, strPos, pos, blockFormat,
408
0
                            B->revision);
409
410
0
    appendUndoItem(c);
411
0
    Q_ASSERT(undoState == undoStack.size());
412
413
    // update revision numbers of the modified blocks.
414
0
    B->revision = (atBlockEnd && !atBlockStart)? oldRevision : revision;
415
0
    b = blocks.next(b);
416
0
    if (b) {
417
0
        B = blocks.fragment(b);
418
0
        B->revision = atBlockStart ? oldRevision : revision;
419
0
    }
420
421
0
    if (formats.charFormat(charFormat).objectIndex() == -1)
422
0
        needsEnsureMaximumBlockCount = true;
423
424
0
    endEditBlock();
425
0
    return fragment;
426
0
}
427
428
int QTextDocumentPrivate::insertBlock(int pos, int blockFormat, int charFormat, QTextUndoCommand::Operation op)
429
0
{
430
0
    return insertBlock(QChar::ParagraphSeparator, pos, blockFormat, charFormat, op);
431
0
}
432
433
void QTextDocumentPrivate::insert(int pos, int strPos, int strLength, int format)
434
0
{
435
0
    if (strLength <= 0)
436
0
        return;
437
438
0
    Q_ASSERT(pos >= 0 && pos < fragments.length());
439
0
    Q_ASSERT(formats.format(format).isCharFormat());
440
441
0
    insert_string(pos, strPos, strLength, format, QTextUndoCommand::MoveCursor);
442
0
    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
0
    finishEdit();
454
0
}
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
0
{
669
0
    beginEditBlock();
670
671
0
    Q_ASSERT(newFormat.isValid());
672
673
0
    int newFormatIdx = -1;
674
0
    if (mode == SetFormatAndPreserveObjectIndices) {
675
0
        QTextCharFormat cleanFormat = newFormat;
676
0
        cleanFormat.clearProperty(QTextFormat::ObjectIndex);
677
0
        newFormatIdx = formats.indexForFormat(cleanFormat);
678
0
    } else if (mode == SetFormat) {
679
0
        newFormatIdx = formats.indexForFormat(newFormat);
680
0
    }
681
682
0
    if (pos == -1) {
683
0
        if (mode == MergeFormat) {
684
0
            QTextFormat format = formats.format(initialBlockCharFormatIndex);
685
0
            format.merge(newFormat);
686
0
            initialBlockCharFormatIndex = formats.indexForFormat(format);
687
0
        } else if (mode == SetFormatAndPreserveObjectIndices
688
0
                   && 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
0
        } else {
693
0
            initialBlockCharFormatIndex = newFormatIdx;
694
0
        }
695
696
0
        ++pos;
697
0
        --length;
698
0
    }
699
700
0
    const int startPos = pos;
701
0
    const int endPos = pos + length;
702
703
0
    split(startPos);
704
0
    split(endPos);
705
706
0
    while (pos < endPos) {
707
0
        FragmentMap::Iterator it = fragments.find(pos);
708
0
        Q_ASSERT(!it.atEnd());
709
710
0
        QTextFragmentData *fragment = it.value();
711
712
0
        Q_ASSERT(formats.format(fragment->format).type() == QTextFormat::CharFormat);
713
714
0
        int offset = pos - it.position();
715
0
        int length = qMin(endPos - pos, int(fragment->size_array[0] - offset));
716
0
        int oldFormat = fragment->format;
717
718
0
        if (mode == MergeFormat) {
719
0
            QTextFormat format = formats.format(fragment->format);
720
0
            format.merge(newFormat);
721
0
            fragment->format = formats.indexForFormat(format);
722
0
        } else if (mode == SetFormatAndPreserveObjectIndices
723
0
                   && formats.format(oldFormat).objectIndex() != -1) {
724
0
            QTextCharFormat f = newFormat;
725
0
            f.setObjectIndex(formats.format(oldFormat).objectIndex());
726
0
            fragment->format = formats.indexForFormat(f);
727
0
        } else {
728
0
            fragment->format = newFormatIdx;
729
0
        }
730
731
0
        QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::CharFormatChanged, true, QTextUndoCommand::MoveCursor, oldFormat,
732
0
                                0, pos, length, 0);
733
0
        appendUndoItem(c);
734
735
0
        pos += length;
736
0
        Q_ASSERT(q20::cmp_equal(pos, (it.position() + fragment->size_array[0])) || pos >= endPos);
737
0
    }
738
739
0
    int n = fragments.findNode(startPos - 1);
740
0
    if (n)
741
0
        unite(n);
742
743
0
    n = fragments.findNode(endPos);
744
0
    if (n)
745
0
        unite(n);
746
747
0
    QTextBlock blockIt = blocksFind(startPos);
748
0
    QTextBlock endIt = blocksFind(endPos);
749
0
    if (endIt.isValid())
750
0
        endIt = endIt.next();
751
0
    for (; blockIt.isValid() && blockIt != endIt; blockIt = blockIt.next())
752
0
        QTextDocumentPrivate::block(blockIt)->invalidate();
753
754
0
    documentChange(startPos, length);
755
756
0
    endEditBlock();
757
0
}
758
759
void QTextDocumentPrivate::setBlockFormat(const QTextBlock &from, const QTextBlock &to,
760
                                          const QTextBlockFormat &newFormat, FormatChangeMode mode)
761
0
{
762
0
    beginEditBlock();
763
764
0
    Q_ASSERT(mode != SetFormatAndPreserveObjectIndices); // only implemented for setCharFormat
765
766
0
    Q_ASSERT(newFormat.isValid());
767
768
0
    int newFormatIdx = -1;
769
0
    if (mode == SetFormat)
770
0
        newFormatIdx = formats.indexForFormat(newFormat);
771
0
    QTextBlockGroup *group = qobject_cast<QTextBlockGroup *>(objectForFormat(newFormat));
772
773
0
    QTextBlock it = from;
774
0
    QTextBlock end = to;
775
0
    if (end.isValid())
776
0
        end = end.next();
777
778
0
    for (; it != end; it = it.next()) {
779
0
        int oldFormat = block(it)->format;
780
0
        QTextBlockFormat format = formats.blockFormat(oldFormat);
781
0
        QTextBlockGroup *oldGroup = qobject_cast<QTextBlockGroup *>(objectForFormat(format));
782
0
        if (mode == MergeFormat) {
783
0
            format.merge(newFormat);
784
0
            newFormatIdx = formats.indexForFormat(format);
785
0
            group = qobject_cast<QTextBlockGroup *>(objectForFormat(format));
786
0
        }
787
0
        block(it)->format = newFormatIdx;
788
789
0
        block(it)->invalidate();
790
791
0
        QT_INIT_TEXTUNDOCOMMAND(c, QTextUndoCommand::BlockFormatChanged, true, QTextUndoCommand::MoveCursor, oldFormat,
792
0
                                0, it.position(), 1, 0);
793
0
        appendUndoItem(c);
794
795
0
        if (group != oldGroup) {
796
0
            if (oldGroup)
797
0
                oldGroup->blockRemoved(it);
798
0
            if (group)
799
0
                group->blockInserted(it);
800
0
        } else if (group) {
801
0
            group->blockFormatChanged(it);
802
0
        }
803
0
    }
804
805
0
    documentChange(from.position(), to.position() + to.length() - from.position());
806
807
0
    endEditBlock();
808
0
}
809
810
811
bool QTextDocumentPrivate::split(int pos)
812
0
{
813
0
    uint x = fragments.findNode(pos);
814
0
    if (x) {
815
0
        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
0
        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
0
    }
832
0
    return false;
833
0
}
834
835
bool QTextDocumentPrivate::unite(uint f)
836
0
{
837
0
    uint n = fragments.next(f);
838
0
    if (!n)
839
0
        return false;
840
841
0
    QTextFragmentData *ff = fragments.fragment(f);
842
0
    QTextFragmentData *nf = fragments.fragment(n);
843
844
0
    if (nf->format == ff->format && (ff->stringPosition + (int)ff->size_array[0] == nf->stringPosition)) {
845
0
        if (isValidBlockSeparator(text.at(ff->stringPosition))
846
0
            || isValidBlockSeparator(text.at(nf->stringPosition)))
847
0
            return false;
848
849
0
        fragments.setSize(f, ff->size_array[0] + nf->size_array[0]);
850
0
        fragments.erase_single(n);
851
0
        return true;
852
0
    }
853
0
    return false;
854
0
}
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
0
{
1041
0
    PMDEBUG("appendUndoItem, command=%d enabled=%d", c.command, undoEnabled);
1042
0
    if (!undoEnabled)
1043
0
        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
0
{
1085
0
    bool undoCommandsAvailable = undoState != 0;
1086
0
    bool redoCommandsAvailable = undoState != undoStack.size();
1087
0
    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
0
    } else if (stacksToClear == QTextDocument::RedoStack
1098
0
               && 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
0
    } else if (stacksToClear == QTextDocument::UndoAndRedoStacks
1108
0
               && !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
0
}
1122
1123
void QTextDocumentPrivate::emitUndoAvailable(bool available)
1124
0
{
1125
0
    if (available != wasUndoAvailable) {
1126
0
        Q_Q(QTextDocument);
1127
0
        emit q->undoAvailable(available);
1128
0
        wasUndoAvailable = available;
1129
0
    }
1130
0
}
1131
1132
void QTextDocumentPrivate::emitRedoAvailable(bool available)
1133
0
{
1134
0
    if (available != wasRedoAvailable) {
1135
0
        Q_Q(QTextDocument);
1136
0
        emit q->redoAvailable(available);
1137
0
        wasRedoAvailable = available;
1138
0
    }
1139
0
}
1140
1141
void QTextDocumentPrivate::enableUndoRedo(bool enable)
1142
0
{
1143
0
    if (enable && maximumBlockCount > 0)
1144
0
        return;
1145
1146
0
    if (!enable) {
1147
0
        undoState = 0;
1148
0
        clearUndoRedoStacks(QTextDocument::RedoStack);
1149
0
        emitUndoAvailable(false);
1150
0
        emitRedoAvailable(false);
1151
0
    }
1152
0
    modifiedState = modified ? -1 : undoState;
1153
0
    undoEnabled = enable;
1154
0
    if (!undoEnabled)
1155
0
        compressPieceTable();
1156
0
}
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
0
{
1168
0
    Q_ASSERT(editBlock > 0);
1169
0
    if (--editBlock)
1170
0
        return;
1171
1172
0
    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
0
    editBlockCursorPosition = -1;
1182
1183
0
    finishEdit();
1184
0
}
1185
1186
void QTextDocumentPrivate::finishEdit()
1187
0
{
1188
0
    Q_Q(QTextDocument);
1189
1190
0
    if (editBlock)
1191
0
        return;
1192
1193
0
    if (framesDirty)
1194
0
        scan_frames(docChangeFrom, docChangeOldLength, docChangeLength);
1195
1196
0
    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
0
    docChangeFrom = -1;
1205
1206
0
    if (needsEnsureMaximumBlockCount) {
1207
0
        needsEnsureMaximumBlockCount = false;
1208
0
        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
0
    }
1216
1217
0
    QList<QTextCursor> changedCursors;
1218
0
    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
0
    for (const QTextCursor &cursor : std::as_const(changedCursors))
1225
0
        emit q->cursorPositionChanged(cursor);
1226
1227
0
    contentsChanged();
1228
1229
0
    if (blocks.numNodes() != lastBlockCount) {
1230
0
        lastBlockCount = blocks.numNodes();
1231
0
        emit q->blockCountChanged(lastBlockCount);
1232
0
    }
1233
1234
0
    if (!undoEnabled && unreachableCharacterCount)
1235
0
        compressPieceTable();
1236
0
}
1237
1238
void QTextDocumentPrivate::documentChange(int from, int length)
1239
0
{
1240
//     qDebug("QTextDocumentPrivate::documentChange: from=%d,length=%d", from, length);
1241
0
    if (docChangeFrom < 0) {
1242
0
        docChangeFrom = from;
1243
0
        docChangeOldLength = length;
1244
0
        docChangeLength = length;
1245
0
        return;
1246
0
    }
1247
0
    int start = qMin(from, docChangeFrom);
1248
0
    int end = qMax(from + length, docChangeFrom + docChangeLength);
1249
0
    int diff = qMax(0, end - start - docChangeLength);
1250
0
    docChangeFrom = start;
1251
0
    docChangeOldLength += diff;
1252
0
    docChangeLength += diff;
1253
0
}
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
0
{
1264
0
    if (!editBlock)
1265
0
        ++revision;
1266
1267
0
    if (blockCursorAdjustment)  {
1268
0
        ; // postpone, will be called again from QTextDocumentPrivate::remove()
1269
0
    } else {
1270
0
        for (QTextCursorPrivate *curs : std::as_const(cursors)) {
1271
0
            if (curs->adjustPosition(from, addedOrRemoved, op) == QTextCursorPrivate::CursorMoved) {
1272
0
                curs->changed = true;
1273
0
            }
1274
0
        }
1275
0
    }
1276
1277
//     qDebug("QTextDocumentPrivate::adjustDocumentChanges: from=%d,addedOrRemoved=%d", from, addedOrRemoved);
1278
0
    if (docChangeFrom < 0) {
1279
0
        docChangeFrom = from;
1280
0
        if (addedOrRemoved > 0) {
1281
0
            docChangeOldLength = 0;
1282
0
            docChangeLength = addedOrRemoved;
1283
0
        } else {
1284
0
            docChangeOldLength = -addedOrRemoved;
1285
0
            docChangeLength = 0;
1286
0
        }
1287
//         qDebug("adjustDocumentChanges:");
1288
//         qDebug("    -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength);
1289
0
        return;
1290
0
    }
1291
1292
    // have to merge the new change with the already existing one.
1293
0
    int added = qMax(0, addedOrRemoved);
1294
0
    int removed = qMax(0, -addedOrRemoved);
1295
1296
0
    int diff = 0;
1297
0
    if (from + removed < docChangeFrom)
1298
0
        diff = docChangeFrom - from - removed;
1299
0
    else if (from > docChangeFrom + docChangeLength)
1300
0
        diff = from - (docChangeFrom + docChangeLength);
1301
1302
0
    int overlap_start = qMax(from, docChangeFrom);
1303
0
    int overlap_end = qMin(from + removed, docChangeFrom + docChangeLength);
1304
0
    int removedInside = qMax(0, overlap_end - overlap_start);
1305
0
    removed -= removedInside;
1306
1307
//     qDebug("adjustDocumentChanges: from=%d, addedOrRemoved=%d, diff=%d, removedInside=%d", from, addedOrRemoved, diff, removedInside);
1308
0
    docChangeFrom = qMin(docChangeFrom, from);
1309
0
    docChangeOldLength += removed + diff;
1310
0
    docChangeLength += added - removedInside + diff;
1311
//     qDebug("    -> %d %d %d", docChangeFrom, docChangeOldLength, docChangeLength);
1312
1313
0
}
1314
1315
1316
QString QTextDocumentPrivate::plainText() const
1317
0
{
1318
0
    QString result;
1319
0
    result.resize(length());
1320
0
    const QChar *text_unicode = text.unicode();
1321
0
    QChar *data = result.data();
1322
0
    for (QTextDocumentPrivate::FragmentIterator it = begin(); it != end(); ++it) {
1323
0
        const QTextFragmentData *f = *it;
1324
0
        ::memcpy(data, text_unicode + f->stringPosition, f->size_array[0] * sizeof(QChar));
1325
0
        data += f->size_array[0];
1326
0
    }
1327
    // remove trailing block separator
1328
0
    result.chop(1);
1329
0
    return result;
1330
0
}
1331
1332
int QTextDocumentPrivate::blockCharFormatIndex(int node) const
1333
0
{
1334
0
    int pos = blocks.position(node);
1335
0
    if (pos == 0)
1336
0
        return initialBlockCharFormatIndex;
1337
1338
0
    return fragments.find(pos - 1)->format;
1339
0
}
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
0
{
1425
0
    if (!rtFrame) {
1426
0
        QTextFrameFormat defaultRootFrameFormat;
1427
0
        defaultRootFrameFormat.setMargin(documentMargin);
1428
0
        rtFrame = qobject_cast<QTextFrame *>(const_cast<QTextDocumentPrivate *>(this)->createObject(defaultRootFrameFormat));
1429
0
    }
1430
0
    return rtFrame;
1431
0
}
1432
1433
void QTextDocumentPrivate::addCursor(QTextCursorPrivate *c)
1434
0
{
1435
0
    cursors.insert(c);
1436
0
}
1437
1438
void QTextDocumentPrivate::removeCursor(QTextCursorPrivate *c)
1439
0
{
1440
0
    cursors.remove(c);
1441
0
}
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
0
{
1457
0
    for (int i = 0; i < f->d_func()->childFrames.size(); ++i)
1458
0
        clearFrame(f->d_func()->childFrames.at(i));
1459
0
    f->d_func()->childFrames.clear();
1460
0
    f->d_func()->parentFrame = nullptr;
1461
0
}
1462
1463
void QTextDocumentPrivate::scan_frames(int pos, int charsRemoved, int charsAdded)
1464
0
{
1465
    // ###### optimize
1466
0
    Q_UNUSED(pos);
1467
0
    Q_UNUSED(charsRemoved);
1468
0
    Q_UNUSED(charsAdded);
1469
1470
0
    QTextFrame *f = rootFrame();
1471
0
    clearFrame(f);
1472
1473
0
    for (FragmentIterator it = begin(); it != end(); ++it) {
1474
        // QTextFormat fmt = formats.format(it->format);
1475
0
        QTextFrame *frame = qobject_cast<QTextFrame *>(objectForFormat(it->format));
1476
0
        if (!frame)
1477
0
            continue;
1478
1479
0
        Q_ASSERT(it.size() == 1);
1480
0
        QChar ch = text.at(it->stringPosition);
1481
1482
0
        if (ch == QTextBeginningOfFrame) {
1483
0
            if (f != frame) {
1484
                // f == frame happens for tables
1485
0
                Q_ASSERT(frame->d_func()->fragment_start == it.n || frame->d_func()->fragment_start == 0);
1486
0
                frame->d_func()->parentFrame = f;
1487
0
                f->d_func()->childFrames.append(frame);
1488
0
                f = frame;
1489
0
            }
1490
0
        } else if (ch == QTextEndOfFrame) {
1491
0
            Q_ASSERT(f == frame);
1492
0
            Q_ASSERT(frame->d_func()->fragment_end == it.n || frame->d_func()->fragment_end == 0);
1493
0
            f = frame->d_func()->parentFrame;
1494
0
        } 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
0
    }
1504
0
    Q_ASSERT(f == rtFrame);
1505
0
    framesDirty = false;
1506
0
}
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
0
{
1591
0
    if (objectIndex < 0)
1592
0
        return nullptr;
1593
1594
0
    QTextObject *object = objects.value(objectIndex, nullptr);
1595
0
    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
0
    return object;
1601
0
}
1602
1603
QTextObject *QTextDocumentPrivate::objectForFormat(int formatIndex) const
1604
0
{
1605
0
    int objectIndex = formats.format(formatIndex).objectIndex();
1606
0
    return objectForIndex(objectIndex);
1607
0
}
1608
1609
QTextObject *QTextDocumentPrivate::objectForFormat(const QTextFormat &f) const
1610
0
{
1611
0
    return objectForIndex(f.objectIndex());
1612
0
}
1613
1614
QTextObject *QTextDocumentPrivate::createObject(const QTextFormat &f, int objectIndex)
1615
0
{
1616
0
    QTextObject *obj = document()->createObject(f);
1617
1618
0
    if (obj) {
1619
0
        obj->d_func()->objectIndex = objectIndex == -1 ? formats.createObjectIndex(f) : objectIndex;
1620
0
        objects[obj->d_func()->objectIndex] = obj;
1621
0
    }
1622
1623
0
    return obj;
1624
0
}
1625
1626
void QTextDocumentPrivate::deleteObject(QTextObject *object)
1627
0
{
1628
0
    const int objIdx = object->d_func()->objectIndex;
1629
0
    objects.remove(objIdx);
1630
0
    delete object;
1631
0
}
1632
1633
void QTextDocumentPrivate::contentsChanged()
1634
0
{
1635
0
    Q_Q(QTextDocument);
1636
0
    if (editBlock)
1637
0
        return;
1638
1639
0
    bool m = undoEnabled ? (modifiedState != undoState) : true;
1640
0
    if (modified != m) {
1641
0
        modified = m;
1642
0
        emit q->modificationChanged(modified);
1643
0
    }
1644
1645
0
    emit q->contentsChanged();
1646
0
}
1647
1648
void QTextDocumentPrivate::compressPieceTable()
1649
0
{
1650
0
    if (undoEnabled)
1651
0
        return;
1652
1653
0
    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
0
    bool compressTable = unreachableCharacterCount * sizeof(QChar) > garbageCollectionThreshold
1658
0
                         && text.size() >= text.capacity() * 0.9;
1659
0
    if (!compressTable)
1660
0
        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
0
{
1698
0
    if (maximumBlockCount <= 0)
1699
0
        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