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