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