1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qtextcursor.h"
5#include "qtextcursor_p.h"
6#include "qglobal.h"
7#include "qtextdocumentfragment.h"
8#include "qtextdocumentfragment_p.h"
9#include "qtextlist.h"
10#include "qtexttable.h"
11#include "qtexttable_p.h"
12#include "qtextengine_p.h"
13#include "qabstracttextdocumentlayout.h"
14
15#include <qtextlayout.h>
16#include <qdebug.h>
17
18QT_BEGIN_NAMESPACE
19
20enum {
21 AdjustPrev = 0x1,
22 AdjustUp = 0x3,
23 AdjustNext = 0x4,
24 AdjustDown = 0x12
25};
26
27QTextCursorPrivate::QTextCursorPrivate(QTextDocumentPrivate *p)
28 : priv(p), x(0), position(0), anchor(0), adjusted_anchor(0),
29 currentCharFormat(-1), visualNavigation(false), keepPositionOnInsert(false),
30 changed(false)
31{
32 priv->addCursor(c: this);
33}
34
35QTextCursorPrivate::QTextCursorPrivate(const QTextCursorPrivate &rhs)
36 : QSharedData(rhs)
37{
38 position = rhs.position;
39 anchor = rhs.anchor;
40 adjusted_anchor = rhs.adjusted_anchor;
41 priv = rhs.priv;
42 x = rhs.x;
43 currentCharFormat = rhs.currentCharFormat;
44 visualNavigation = rhs.visualNavigation;
45 keepPositionOnInsert = rhs.keepPositionOnInsert;
46 changed = rhs.changed;
47 if (priv != nullptr)
48 priv->addCursor(c: this);
49}
50
51QTextCursorPrivate::~QTextCursorPrivate()
52{
53 if (priv)
54 priv->removeCursor(c: this);
55}
56
57QTextCursorPrivate::AdjustResult QTextCursorPrivate::adjustPosition(int positionOfChange, int charsAddedOrRemoved, QTextUndoCommand::Operation op)
58{
59 QTextCursorPrivate::AdjustResult result = QTextCursorPrivate::CursorMoved;
60 // not(!) <= , so that inserting text adjusts the cursor correctly
61 if (position < positionOfChange
62 || (position == positionOfChange
63 && (op == QTextUndoCommand::KeepCursor
64 || keepPositionOnInsert)
65 )
66 ) {
67 result = CursorUnchanged;
68 } else {
69 if (charsAddedOrRemoved < 0 && position < positionOfChange - charsAddedOrRemoved)
70 position = positionOfChange;
71 else
72 position += charsAddedOrRemoved;
73
74 currentCharFormat = -1;
75 }
76
77 if (anchor >= positionOfChange
78 && (anchor != positionOfChange || op != QTextUndoCommand::KeepCursor)) {
79 if (charsAddedOrRemoved < 0 && anchor < positionOfChange - charsAddedOrRemoved)
80 anchor = positionOfChange;
81 else
82 anchor += charsAddedOrRemoved;
83 }
84
85 if (adjusted_anchor >= positionOfChange
86 && (adjusted_anchor != positionOfChange || op != QTextUndoCommand::KeepCursor)) {
87 if (charsAddedOrRemoved < 0 && adjusted_anchor < positionOfChange - charsAddedOrRemoved)
88 adjusted_anchor = positionOfChange;
89 else
90 adjusted_anchor += charsAddedOrRemoved;
91 }
92
93 return result;
94}
95
96void QTextCursorPrivate::setX()
97{
98 if (priv->isInEditBlock() || priv->inContentsChange) {
99 x = -1; // mark dirty
100 return;
101 }
102
103 QTextBlock block = this->block();
104 const QTextLayout *layout = blockLayout(block);
105 int pos = position - block.position();
106
107 QTextLine line = layout->lineForTextPosition(pos);
108 if (line.isValid())
109 x = line.cursorToX(cursorPos: pos);
110 else
111 x = -1; // delayed init. Makes movePosition() call setX later on again.
112}
113
114void QTextCursorPrivate::remove()
115{
116 if (anchor == position)
117 return;
118 currentCharFormat = -1;
119 int pos1 = position;
120 int pos2 = adjusted_anchor;
121 QTextUndoCommand::Operation op = QTextUndoCommand::KeepCursor;
122 if (pos1 > pos2) {
123 pos1 = adjusted_anchor;
124 pos2 = position;
125 op = QTextUndoCommand::MoveCursor;
126 }
127
128 // deleting inside table? -> delete only content
129 QTextTable *table = complexSelectionTable();
130 if (table) {
131 priv->beginEditBlock();
132 int startRow, startCol, numRows, numCols;
133 selectedTableCells(firstRow: &startRow, numRows: &numRows, firstColumn: &startCol, numColumns: &numCols);
134 clearCells(table, startRow, startCol, numRows, numCols, op);
135 adjusted_anchor = anchor = position;
136 priv->endEditBlock();
137 } else {
138 priv->remove(pos: pos1, length: pos2-pos1, op);
139 adjusted_anchor = anchor = position;
140 }
141
142}
143
144void QTextCursorPrivate::clearCells(QTextTable *table, int startRow, int startCol, int numRows, int numCols, QTextUndoCommand::Operation op)
145{
146 priv->beginEditBlock();
147
148 for (int row = startRow; row < startRow + numRows; ++row)
149 for (int col = startCol; col < startCol + numCols; ++col) {
150 QTextTableCell cell = table->cellAt(row, col);
151 const int startPos = cell.firstPosition();
152 const int endPos = cell.lastPosition();
153 Q_ASSERT(startPos <= endPos);
154 priv->remove(pos: startPos, length: endPos - startPos, op);
155 }
156
157 priv->endEditBlock();
158}
159
160bool QTextCursorPrivate::canDelete(int pos) const
161{
162 QTextDocumentPrivate::FragmentIterator fit = priv->find(pos);
163 QTextCharFormat fmt = priv->formatCollection()->charFormat(index: (*fit)->format);
164 return (fmt.objectIndex() == -1 || fmt.objectType() == QTextFormat::ImageObject);
165}
166
167void QTextCursorPrivate::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat)
168{
169 QTextFormatCollection *formats = priv->formatCollection();
170 int idx = formats->indexForFormat(f: format);
171 Q_ASSERT(formats->format(idx).isBlockFormat());
172
173 priv->insertBlock(pos: position, blockFormat: idx, charFormat: formats->indexForFormat(f: charFormat));
174 currentCharFormat = -1;
175}
176
177void QTextCursorPrivate::adjustCursor(QTextCursor::MoveOperation m)
178{
179 adjusted_anchor = anchor;
180 if (position == anchor)
181 return;
182
183 QTextFrame *f_position = priv->frameAt(pos: position);
184 QTextFrame *f_anchor = priv->frameAt(pos: adjusted_anchor);
185
186 if (f_position != f_anchor) {
187 // find common parent frame
188 QList<QTextFrame *> positionChain;
189 QList<QTextFrame *> anchorChain;
190 QTextFrame *f = f_position;
191 while (f) {
192 positionChain.prepend(t: f);
193 f = f->parentFrame();
194 }
195 f = f_anchor;
196 while (f) {
197 anchorChain.prepend(t: f);
198 f = f->parentFrame();
199 }
200 Q_ASSERT(positionChain.at(0) == anchorChain.at(0));
201 int i = 1;
202 int l = qMin(a: positionChain.size(), b: anchorChain.size());
203 for (; i < l; ++i) {
204 if (positionChain.at(i) != anchorChain.at(i))
205 break;
206 }
207
208 if (m <= QTextCursor::WordLeft) {
209 if (i < positionChain.size())
210 position = positionChain.at(i)->firstPosition() - 1;
211 } else {
212 if (i < positionChain.size())
213 position = positionChain.at(i)->lastPosition() + 1;
214 }
215 if (position < adjusted_anchor) {
216 if (i < anchorChain.size())
217 adjusted_anchor = anchorChain.at(i)->lastPosition() + 1;
218 } else {
219 if (i < anchorChain.size())
220 adjusted_anchor = anchorChain.at(i)->firstPosition() - 1;
221 }
222
223 f_position = positionChain.at(i: i-1);
224 }
225
226 // same frame, either need to adjust to cell boundaries or return
227 QTextTable *table = qobject_cast<QTextTable *>(object: f_position);
228 if (!table)
229 return;
230
231 QTextTableCell c_position = table->cellAt(position);
232 QTextTableCell c_anchor = table->cellAt(position: adjusted_anchor);
233 if (c_position != c_anchor) {
234 position = c_position.firstPosition();
235 if (position < adjusted_anchor)
236 adjusted_anchor = c_anchor.lastPosition();
237 else
238 adjusted_anchor = c_anchor.firstPosition();
239 }
240 currentCharFormat = -1;
241}
242
243void QTextCursorPrivate::aboutToRemoveCell(int from, int to)
244{
245 Q_ASSERT(from <= to);
246 if (position == anchor)
247 return;
248
249 QTextTable *t = qobject_cast<QTextTable *>(object: priv->frameAt(pos: position));
250 if (!t)
251 return;
252 QTextTableCell removedCellFrom = t->cellAt(position: from);
253 QTextTableCell removedCellEnd = t->cellAt(position: to);
254 if (! removedCellFrom.isValid() || !removedCellEnd.isValid())
255 return;
256
257 int curFrom = position;
258 int curTo = adjusted_anchor;
259 if (curTo < curFrom)
260 qSwap(value1&: curFrom, value2&: curTo);
261
262 QTextTableCell cellStart = t->cellAt(position: curFrom);
263 QTextTableCell cellEnd = t->cellAt(position: curTo);
264
265 if (cellStart.row() >= removedCellFrom.row() && cellEnd.row() <= removedCellEnd.row()
266 && cellStart.column() >= removedCellFrom.column()
267 && cellEnd.column() <= removedCellEnd.column()) { // selection is completely removed
268 // find a new position, as close as possible to where we were.
269 QTextTableCell cell;
270 if (removedCellFrom.row() == 0 && removedCellEnd.row() == t->rows()-1) // removed n columns
271 cell = t->cellAt(row: cellStart.row(), col: removedCellEnd.column()+1);
272 else if (removedCellFrom.column() == 0 && removedCellEnd.column() == t->columns()-1) // removed n rows
273 cell = t->cellAt(row: removedCellEnd.row() + 1, col: cellStart.column());
274
275 int newPosition;
276 if (cell.isValid())
277 newPosition = cell.firstPosition();
278 else
279 newPosition = t->lastPosition()+1;
280
281 setPosition(newPosition);
282 anchor = newPosition;
283 adjusted_anchor = newPosition;
284 x = 0;
285 }
286 else if (cellStart.row() >= removedCellFrom.row() && cellStart.row() <= removedCellEnd.row()
287 && cellEnd.row() > removedCellEnd.row()) {
288 int newPosition = t->cellAt(row: removedCellEnd.row() + 1, col: cellStart.column()).firstPosition();
289 if (position < anchor)
290 position = newPosition;
291 else
292 anchor = adjusted_anchor = newPosition;
293 }
294 else if (cellStart.column() >= removedCellFrom.column() && cellStart.column() <= removedCellEnd.column()
295 && cellEnd.column() > removedCellEnd.column()) {
296 int newPosition = t->cellAt(row: cellStart.row(), col: removedCellEnd.column()+1).firstPosition();
297 if (position < anchor)
298 position = newPosition;
299 else
300 anchor = adjusted_anchor = newPosition;
301 }
302}
303
304bool QTextCursorPrivate::movePosition(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode)
305{
306 currentCharFormat = -1;
307 bool adjustX = true;
308 QTextBlock blockIt = block();
309 bool visualMovement = priv->defaultCursorMoveStyle == Qt::VisualMoveStyle;
310
311 if (!blockIt.isValid())
312 return false;
313
314 if (blockIt.textDirection() == Qt::RightToLeft) {
315 if (op == QTextCursor::WordLeft)
316 op = QTextCursor::NextWord;
317 else if (op == QTextCursor::WordRight)
318 op = QTextCursor::PreviousWord;
319
320 if (!visualMovement) {
321 if (op == QTextCursor::Left)
322 op = QTextCursor::NextCharacter;
323 else if (op == QTextCursor::Right)
324 op = QTextCursor::PreviousCharacter;
325 }
326 }
327
328 const QTextLayout *layout = blockLayout(block&: blockIt);
329 int relativePos = position - blockIt.position();
330 QTextLine line;
331 if (!priv->isInEditBlock())
332 line = layout->lineForTextPosition(pos: relativePos);
333
334 Q_ASSERT(priv->frameAt(position) == priv->frameAt(adjusted_anchor));
335
336 int newPosition = position;
337
338 if (mode == QTextCursor::KeepAnchor && complexSelectionTable() != nullptr) {
339 if ((op >= QTextCursor::EndOfLine && op <= QTextCursor::NextWord)
340 || (op >= QTextCursor::Right && op <= QTextCursor::WordRight)) {
341 QTextTable *t = qobject_cast<QTextTable *>(object: priv->frameAt(pos: position));
342 Q_ASSERT(t); // as we have already made sure we have a complex selection
343 QTextTableCell cell_pos = t->cellAt(position);
344 if (cell_pos.column() + cell_pos.columnSpan() != t->columns())
345 op = QTextCursor::NextCell;
346 }
347 }
348
349 if (x == -1 && !priv->isInEditBlock() && (op == QTextCursor::Up || op == QTextCursor::Down))
350 setX();
351
352 switch(op) {
353 case QTextCursor::NoMove:
354 return true;
355
356 case QTextCursor::Start:
357 newPosition = 0;
358 break;
359 case QTextCursor::StartOfLine: {
360 newPosition = blockIt.position();
361 if (line.isValid())
362 newPosition += line.textStart();
363
364 break;
365 }
366 case QTextCursor::StartOfBlock: {
367 newPosition = blockIt.position();
368 break;
369 }
370 case QTextCursor::PreviousBlock: {
371 if (blockIt == priv->blocksBegin())
372 return false;
373 blockIt = blockIt.previous();
374
375 newPosition = blockIt.position();
376 break;
377 }
378 case QTextCursor::PreviousCharacter:
379 if (mode == QTextCursor::MoveAnchor && position != adjusted_anchor)
380 newPosition = qMin(a: position, b: adjusted_anchor);
381 else
382 newPosition = priv->previousCursorPosition(position, mode: QTextLayout::SkipCharacters);
383 break;
384 case QTextCursor::Left:
385 if (mode == QTextCursor::MoveAnchor && position != adjusted_anchor)
386 newPosition = visualMovement ? qMax(a: position, b: adjusted_anchor)
387 : qMin(a: position, b: adjusted_anchor);
388 else
389 newPosition = visualMovement ? priv->leftCursorPosition(position)
390 : priv->previousCursorPosition(position, mode: QTextLayout::SkipCharacters);
391 break;
392 case QTextCursor::StartOfWord: {
393 if (relativePos == 0)
394 break;
395
396 // skip if already at word start
397 QTextEngine *engine = layout->engine();
398 const QCharAttributes *attributes = engine->attributes();
399 if ((relativePos == blockIt.length() - 1)
400 && (attributes[relativePos - 1].whiteSpace || engine->atWordSeparator(position: relativePos - 1)))
401 return false;
402
403 if (relativePos < blockIt.length()-1)
404 ++position;
405
406 Q_FALLTHROUGH();
407 }
408 case QTextCursor::PreviousWord:
409 case QTextCursor::WordLeft:
410 newPosition = priv->previousCursorPosition(position, mode: QTextLayout::SkipWords);
411 break;
412 case QTextCursor::Up: {
413 int i = line.lineNumber() - 1;
414 if (i == -1) {
415 if (blockIt == priv->blocksBegin())
416 return false;
417 int blockPosition = blockIt.position();
418 QTextTable *table = qobject_cast<QTextTable *>(object: priv->frameAt(pos: blockPosition));
419 if (table) {
420 QTextTableCell cell = table->cellAt(position: blockPosition);
421 if (cell.firstPosition() == blockPosition) {
422 int row = cell.row() - 1;
423 if (row >= 0) {
424 blockPosition = table->cellAt(row, col: cell.column()).lastPosition();
425 } else {
426 // move to line above the table
427 blockPosition = table->firstPosition() - 1;
428 }
429 blockIt = priv->blocksFind(pos: blockPosition);
430 } else {
431 blockIt = blockIt.previous();
432 }
433 } else {
434 blockIt = blockIt.previous();
435 }
436 layout = blockLayout(block&: blockIt);
437 i = layout->lineCount()-1;
438 }
439 if (layout->lineCount()) {
440 QTextLine line = layout->lineAt(i);
441 newPosition = line.xToCursor(x) + blockIt.position();
442 } else {
443 newPosition = blockIt.position();
444 }
445 adjustX = false;
446 break;
447 }
448
449 case QTextCursor::End:
450 newPosition = priv->length() - 1;
451 break;
452 case QTextCursor::EndOfLine: {
453 if (!line.isValid() || line.textLength() == 0) {
454 if (blockIt.length() >= 1)
455 // position right before the block separator
456 newPosition = blockIt.position() + blockIt.length() - 1;
457 break;
458 }
459 newPosition = blockIt.position() + line.textStart() + line.textLength();
460 if (newPosition >= priv->length())
461 newPosition = priv->length() - 1;
462 if (line.lineNumber() < layout->lineCount() - 1) {
463 const QString text = blockIt.text();
464 // ###### this relies on spaces being the cause for linebreaks.
465 // this doesn't work with japanese
466 if (text.at(i: line.textStart() + line.textLength() - 1).isSpace())
467 --newPosition;
468 }
469 break;
470 }
471 case QTextCursor::EndOfWord: {
472 QTextEngine *engine = layout->engine();
473 const QCharAttributes *attributes = engine->attributes();
474 const int len = blockIt.length() - 1;
475 if (relativePos >= len)
476 return false;
477 if (engine->atWordSeparator(position: relativePos)) {
478 ++relativePos;
479 while (relativePos < len && engine->atWordSeparator(position: relativePos))
480 ++relativePos;
481 } else {
482 while (relativePos < len && !attributes[relativePos].whiteSpace && !engine->atWordSeparator(position: relativePos))
483 ++relativePos;
484 }
485 newPosition = blockIt.position() + relativePos;
486 break;
487 }
488 case QTextCursor::EndOfBlock:
489 if (blockIt.length() >= 1)
490 // position right before the block separator
491 newPosition = blockIt.position() + blockIt.length() - 1;
492 break;
493 case QTextCursor::NextBlock: {
494 blockIt = blockIt.next();
495 if (!blockIt.isValid())
496 return false;
497
498 newPosition = blockIt.position();
499 break;
500 }
501 case QTextCursor::NextCharacter:
502 if (mode == QTextCursor::MoveAnchor && position != adjusted_anchor)
503 newPosition = qMax(a: position, b: adjusted_anchor);
504 else
505 newPosition = priv->nextCursorPosition(position, mode: QTextLayout::SkipCharacters);
506 break;
507 case QTextCursor::Right:
508 if (mode == QTextCursor::MoveAnchor && position != adjusted_anchor)
509 newPosition = visualMovement ? qMin(a: position, b: adjusted_anchor)
510 : qMax(a: position, b: adjusted_anchor);
511 else
512 newPosition = visualMovement ? priv->rightCursorPosition(position)
513 : priv->nextCursorPosition(position, mode: QTextLayout::SkipCharacters);
514 break;
515 case QTextCursor::NextWord:
516 case QTextCursor::WordRight:
517 newPosition = priv->nextCursorPosition(position, mode: QTextLayout::SkipWords);
518 break;
519
520 case QTextCursor::Down: {
521 int i = line.lineNumber() + 1;
522
523 if (i >= layout->lineCount()) {
524 int blockPosition = blockIt.position() + blockIt.length() - 1;
525 QTextTable *table = qobject_cast<QTextTable *>(object: priv->frameAt(pos: blockPosition));
526 if (table) {
527 QTextTableCell cell = table->cellAt(position: blockPosition);
528 if (cell.lastPosition() == blockPosition) {
529 int row = cell.row() + cell.rowSpan();
530 if (row < table->rows()) {
531 blockPosition = table->cellAt(row, col: cell.column()).firstPosition();
532 } else {
533 // move to line below the table
534 blockPosition = table->lastPosition() + 1;
535 }
536 blockIt = priv->blocksFind(pos: blockPosition);
537 } else {
538 blockIt = blockIt.next();
539 }
540 } else {
541 blockIt = blockIt.next();
542 }
543
544 if (blockIt == priv->blocksEnd())
545 return false;
546 layout = blockLayout(block&: blockIt);
547 i = 0;
548 }
549 if (layout->lineCount()) {
550 QTextLine line = layout->lineAt(i);
551 newPosition = line.xToCursor(x) + blockIt.position();
552 } else {
553 newPosition = blockIt.position();
554 }
555 adjustX = false;
556 break;
557 }
558 case QTextCursor::NextCell:
559 case QTextCursor::PreviousCell:
560 case QTextCursor::NextRow:
561 case QTextCursor::PreviousRow: {
562 QTextTable *table = qobject_cast<QTextTable *>(object: priv->frameAt(pos: position));
563 if (!table)
564 return false;
565
566 QTextTableCell cell = table->cellAt(position);
567 Q_ASSERT(cell.isValid());
568 int column = cell.column();
569 int row = cell.row();
570 const int currentRow = row;
571 if (op == QTextCursor::NextCell || op == QTextCursor::NextRow) {
572 do {
573 column += cell.columnSpan();
574 if (column >= table->columns()) {
575 column = 0;
576 ++row;
577 }
578 cell = table->cellAt(row, col: column);
579 // note we also continue while we have not reached a cell that's not merged with one above us
580 } while (cell.isValid()
581 && ((op == QTextCursor::NextRow && currentRow == cell.row())
582 || cell.row() < row));
583 }
584 else if (op == QTextCursor::PreviousCell || op == QTextCursor::PreviousRow) {
585 do {
586 --column;
587 if (column < 0) {
588 column = table->columns()-1;
589 --row;
590 }
591 cell = table->cellAt(row, col: column);
592 // note we also continue while we have not reached a cell that's not merged with one above us
593 } while (cell.isValid()
594 && ((op == QTextCursor::PreviousRow && currentRow == cell.row())
595 || cell.row() < row));
596 }
597 if (cell.isValid())
598 newPosition = cell.firstPosition();
599 break;
600 }
601 }
602
603 if (mode == QTextCursor::KeepAnchor) {
604 QTextTable *table = qobject_cast<QTextTable *>(object: priv->frameAt(pos: position));
605 if (table && ((op >= QTextCursor::PreviousBlock && op <= QTextCursor::WordLeft)
606 || (op >= QTextCursor::NextBlock && op <= QTextCursor::WordRight))) {
607 int oldColumn = table->cellAt(position).column();
608
609 const QTextTableCell otherCell = table->cellAt(position: newPosition);
610 if (!otherCell.isValid())
611 return false;
612
613 int newColumn = otherCell.column();
614 if ((oldColumn > newColumn && op >= QTextCursor::End)
615 || (oldColumn < newColumn && op <= QTextCursor::WordLeft))
616 return false;
617 }
618 }
619
620 const bool moved = setPosition(newPosition);
621
622 if (mode == QTextCursor::MoveAnchor) {
623 anchor = position;
624 adjusted_anchor = position;
625 } else {
626 adjustCursor(m: op);
627 }
628
629 if (adjustX)
630 setX();
631
632 return moved;
633}
634
635QTextTable *QTextCursorPrivate::complexSelectionTable() const
636{
637 if (position == anchor)
638 return nullptr;
639
640 QTextTable *t = qobject_cast<QTextTable *>(object: priv->frameAt(pos: position));
641 if (t) {
642 QTextTableCell cell_pos = t->cellAt(position);
643 QTextTableCell cell_anchor = t->cellAt(position: adjusted_anchor);
644
645 Q_ASSERT(cell_anchor.isValid());
646
647 if (cell_pos == cell_anchor)
648 t = nullptr;
649 }
650 return t;
651}
652
653void QTextCursorPrivate::selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const
654{
655 *firstRow = -1;
656 *firstColumn = -1;
657 *numRows = -1;
658 *numColumns = -1;
659
660 if (position == anchor)
661 return;
662
663 QTextTable *t = qobject_cast<QTextTable *>(object: priv->frameAt(pos: position));
664 if (!t)
665 return;
666
667 QTextTableCell cell_pos = t->cellAt(position);
668 QTextTableCell cell_anchor = t->cellAt(position: adjusted_anchor);
669
670 Q_ASSERT(cell_anchor.isValid());
671
672 if (cell_pos == cell_anchor)
673 return;
674
675 *firstRow = qMin(a: cell_pos.row(), b: cell_anchor.row());
676 *firstColumn = qMin(a: cell_pos.column(), b: cell_anchor.column());
677 *numRows = qMax(a: cell_pos.row() + cell_pos.rowSpan(), b: cell_anchor.row() + cell_anchor.rowSpan()) - *firstRow;
678 *numColumns = qMax(a: cell_pos.column() + cell_pos.columnSpan(), b: cell_anchor.column() + cell_anchor.columnSpan()) - *firstColumn;
679}
680
681static void setBlockCharFormatHelper(QTextDocumentPrivate *priv, int pos1, int pos2,
682 const QTextCharFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode)
683{
684 QTextBlock it = priv->blocksFind(pos: pos1);
685 QTextBlock end = priv->blocksFind(pos: pos2);
686 if (end.isValid())
687 end = end.next();
688
689 for (; it != end; it = it.next()) {
690 priv->setCharFormat(pos: it.position() - 1, length: 1, newFormat: format, mode: changeMode);
691 }
692}
693
694void QTextCursorPrivate::setBlockCharFormat(const QTextCharFormat &_format,
695 QTextDocumentPrivate::FormatChangeMode changeMode)
696{
697 priv->beginEditBlock();
698
699 QTextCharFormat format = _format;
700 format.clearProperty(propertyId: QTextFormat::ObjectIndex);
701
702 QTextTable *table = complexSelectionTable();
703 if (table) {
704 int row_start, col_start, num_rows, num_cols;
705 selectedTableCells(firstRow: &row_start, numRows: &num_rows, firstColumn: &col_start, numColumns: &num_cols);
706
707 Q_ASSERT(row_start != -1);
708 for (int r = row_start; r < row_start + num_rows; ++r) {
709 for (int c = col_start; c < col_start + num_cols; ++c) {
710 QTextTableCell cell = table->cellAt(row: r, col: c);
711 int rspan = cell.rowSpan();
712 int cspan = cell.columnSpan();
713 if (rspan != 1) {
714 int cr = cell.row();
715 if (cr != r)
716 continue;
717 }
718 if (cspan != 1) {
719 int cc = cell.column();
720 if (cc != c)
721 continue;
722 }
723
724 int pos1 = cell.firstPosition();
725 int pos2 = cell.lastPosition();
726 setBlockCharFormatHelper(priv, pos1, pos2, format, changeMode);
727 }
728 }
729 } else {
730 int pos1 = position;
731 int pos2 = adjusted_anchor;
732 if (pos1 > pos2) {
733 pos1 = adjusted_anchor;
734 pos2 = position;
735 }
736
737 setBlockCharFormatHelper(priv, pos1, pos2, format, changeMode);
738 }
739 priv->endEditBlock();
740}
741
742
743void QTextCursorPrivate::setBlockFormat(const QTextBlockFormat &format, QTextDocumentPrivate::FormatChangeMode changeMode)
744{
745 QTextTable *table = complexSelectionTable();
746 if (table) {
747 priv->beginEditBlock();
748 int row_start, col_start, num_rows, num_cols;
749 selectedTableCells(firstRow: &row_start, numRows: &num_rows, firstColumn: &col_start, numColumns: &num_cols);
750
751 Q_ASSERT(row_start != -1);
752 for (int r = row_start; r < row_start + num_rows; ++r) {
753 for (int c = col_start; c < col_start + num_cols; ++c) {
754 QTextTableCell cell = table->cellAt(row: r, col: c);
755 int rspan = cell.rowSpan();
756 int cspan = cell.columnSpan();
757 if (rspan != 1) {
758 int cr = cell.row();
759 if (cr != r)
760 continue;
761 }
762 if (cspan != 1) {
763 int cc = cell.column();
764 if (cc != c)
765 continue;
766 }
767
768 int pos1 = cell.firstPosition();
769 int pos2 = cell.lastPosition();
770 priv->setBlockFormat(from: priv->blocksFind(pos: pos1), to: priv->blocksFind(pos: pos2), newFormat: format, mode: changeMode);
771 }
772 }
773 priv->endEditBlock();
774 } else {
775 int pos1 = position;
776 int pos2 = adjusted_anchor;
777 if (pos1 > pos2) {
778 pos1 = adjusted_anchor;
779 pos2 = position;
780 }
781
782 priv->setBlockFormat(from: priv->blocksFind(pos: pos1), to: priv->blocksFind(pos: pos2), newFormat: format, mode: changeMode);
783 }
784}
785
786void QTextCursorPrivate::setCharFormat(const QTextCharFormat &_format, QTextDocumentPrivate::FormatChangeMode changeMode)
787{
788 Q_ASSERT(position != anchor);
789
790 QTextCharFormat format = _format;
791 format.clearProperty(propertyId: QTextFormat::ObjectIndex);
792
793 QTextTable *table = complexSelectionTable();
794 if (table) {
795 priv->beginEditBlock();
796 int row_start, col_start, num_rows, num_cols;
797 selectedTableCells(firstRow: &row_start, numRows: &num_rows, firstColumn: &col_start, numColumns: &num_cols);
798
799 Q_ASSERT(row_start != -1);
800 for (int r = row_start; r < row_start + num_rows; ++r) {
801 for (int c = col_start; c < col_start + num_cols; ++c) {
802 QTextTableCell cell = table->cellAt(row: r, col: c);
803 int rspan = cell.rowSpan();
804 int cspan = cell.columnSpan();
805 if (rspan != 1) {
806 int cr = cell.row();
807 if (cr != r)
808 continue;
809 }
810 if (cspan != 1) {
811 int cc = cell.column();
812 if (cc != c)
813 continue;
814 }
815
816 int pos1 = cell.firstPosition();
817 int pos2 = cell.lastPosition();
818 priv->setCharFormat(pos: pos1, length: pos2-pos1, newFormat: format, mode: changeMode);
819 }
820 }
821 priv->endEditBlock();
822 } else {
823 int pos1 = position;
824 int pos2 = adjusted_anchor;
825 if (pos1 > pos2) {
826 pos1 = adjusted_anchor;
827 pos2 = position;
828 }
829
830 priv->setCharFormat(pos: pos1, length: pos2-pos1, newFormat: format, mode: changeMode);
831 }
832}
833
834
835QTextLayout *QTextCursorPrivate::blockLayout(QTextBlock &block) const{
836 QTextLayout *tl = block.layout();
837 if (!tl->lineCount() && priv->layout())
838 priv->layout()->blockBoundingRect(block);
839 return tl;
840}
841
842/*!
843 \class QTextCursor
844 \reentrant
845 \inmodule QtGui
846
847 \brief The QTextCursor class offers an API to access and modify QTextDocuments.
848
849 \ingroup richtext-processing
850 \ingroup shared
851
852 Text cursors are objects that are used to access and modify the
853 contents and underlying structure of text documents via a
854 programming interface that mimics the behavior of a cursor in a
855 text editor. QTextCursor contains information about both the
856 cursor's position within a QTextDocument and any selection that it
857 has made.
858
859 QTextCursor is modeled on the way a text cursor behaves in a text
860 editor, providing a programmatic means of performing standard
861 actions through the user interface. A document can be thought of
862 as a single string of characters. The cursor's current position()
863 then is always either \e between two consecutive characters in the
864 string, or else \e before the very first character or \e after the
865 very last character in the string. Documents can also contain
866 tables, lists, images, and other objects in addition to text but,
867 from the developer's point of view, the document can be treated as
868 one long string. Some portions of that string can be considered
869 to lie within particular blocks (e.g. paragraphs), or within a
870 table's cell, or a list's item, or other structural elements. When
871 we refer to "current character" we mean the character immediately
872 \e before the cursor position() in the document. Similarly, the
873 "current block" is the block that contains the cursor position().
874
875 A QTextCursor also has an anchor() position. The text that is
876 between the anchor() and the position() is the selection. If
877 anchor() == position() there is no selection.
878
879 The cursor position can be changed programmatically using
880 setPosition() and movePosition(); the latter can also be used to
881 select text. For selections see selectionStart(), selectionEnd(),
882 hasSelection(), clearSelection(), and removeSelectedText().
883
884 If the position() is at the start of a block, atBlockStart()
885 returns \c true; and if it is at the end of a block, atBlockEnd() returns
886 true. The format of the current character is returned by
887 charFormat(), and the format of the current block is returned by
888 blockFormat().
889
890 Formatting can be applied to the current text document using the
891 setCharFormat(), mergeCharFormat(), setBlockFormat() and
892 mergeBlockFormat() functions. The 'set' functions will replace the
893 cursor's current character or block format, while the 'merge'
894 functions add the given format properties to the cursor's current
895 format. If the cursor has a selection, the given format is applied
896 to the current selection. Note that when only a part of a block is
897 selected, the block format is applied to the entire block. The text
898 at the current character position can be turned into a list using
899 createList().
900
901 Deletions can be achieved using deleteChar(),
902 deletePreviousChar(), and removeSelectedText().
903
904 Text strings can be inserted into the document with the insertText()
905 function, blocks (representing new paragraphs) can be inserted with
906 insertBlock().
907
908 Existing fragments of text can be inserted with insertFragment() but,
909 if you want to insert pieces of text in various formats, it is usually
910 still easier to use insertText() and supply a character format.
911
912 Various types of higher-level structure can also be inserted into the
913 document with the cursor:
914
915 \list
916 \li Lists are ordered sequences of block elements that are decorated with
917 bullet points or symbols. These are inserted in a specified format
918 with insertList().
919 \li Tables are inserted with the insertTable() function, and can be
920 given an optional format. These contain an array of cells that can
921 be traversed using the cursor.
922 \li Inline images are inserted with insertImage(). The image to be
923 used can be specified in an image format, or by name.
924 \li Frames are inserted by calling insertFrame() with a specified format.
925 \endlist
926
927 Actions can be grouped (i.e. treated as a single action for
928 undo/redo) using beginEditBlock() and endEditBlock().
929
930 Cursor movements are limited to valid cursor positions. In Latin
931 writing this is between any two consecutive characters in the
932 text, before the first character, or after the last character. In
933 some other writing systems cursor movements are limited to
934 "clusters" (e.g. a syllable in Devanagari, or a base letter plus
935 diacritics). Functions such as movePosition() and deleteChar()
936 limit cursor movement to these valid positions.
937
938 \sa {Rich Text Processing}
939
940*/
941
942/*!
943 \enum QTextCursor::MoveOperation
944
945 \value NoMove Keep the cursor where it is
946
947 \value Start Move to the start of the document.
948 \value StartOfLine Move to the start of the current line.
949 \value StartOfBlock Move to the start of the current block.
950 \value StartOfWord Move to the start of the current word.
951 \value PreviousBlock Move to the start of the previous block.
952 \value PreviousCharacter Move to the previous character.
953 \value PreviousWord Move to the beginning of the previous word.
954 \value Up Move up one line.
955 \value Left Move left one character.
956 \value WordLeft Move left one word.
957
958 \value End Move to the end of the document.
959 \value EndOfLine Move to the end of the current line.
960 \value EndOfWord Move to the end of the current word.
961 \value EndOfBlock Move to the end of the current block.
962 \value NextBlock Move to the beginning of the next block.
963 \value NextCharacter Move to the next character.
964 \value NextWord Move to the next word.
965 \value Down Move down one line.
966 \value Right Move right one character.
967 \value WordRight Move right one word.
968
969 \value NextCell Move to the beginning of the next table cell inside the
970 current table. If the current cell is the last cell in the row, the
971 cursor will move to the first cell in the next row.
972 \value PreviousCell Move to the beginning of the previous table cell
973 inside the current table. If the current cell is the first cell in
974 the row, the cursor will move to the last cell in the previous row.
975 \value NextRow Move to the first new cell of the next row in the current
976 table.
977 \value PreviousRow Move to the last cell of the previous row in the
978 current table.
979
980 \sa movePosition()
981*/
982
983/*!
984 \enum QTextCursor::MoveMode
985
986 \value MoveAnchor Moves the anchor to the same position as the cursor itself.
987 \value KeepAnchor Keeps the anchor where it is.
988
989 If the anchor() is kept where it is and the position() is moved,
990 the text in between will be selected.
991*/
992
993/*!
994 \enum QTextCursor::SelectionType
995
996 This enum describes the types of selection that can be applied with the
997 select() function.
998
999 \value Document Selects the entire document.
1000 \value BlockUnderCursor Selects the block of text under the cursor.
1001 \value LineUnderCursor Selects the line of text under the cursor.
1002 \value WordUnderCursor Selects the word under the cursor. If the cursor
1003 is not positioned within a string of selectable characters, no
1004 text is selected.
1005*/
1006
1007/*!
1008 Constructs a null cursor.
1009 */
1010QTextCursor::QTextCursor()
1011 : d(nullptr)
1012{
1013}
1014
1015/*!
1016 Constructs a cursor pointing to the beginning of the \a document.
1017 */
1018QTextCursor::QTextCursor(QTextDocument *document)
1019 : d(new QTextCursorPrivate(QTextDocumentPrivate::get(document)))
1020{
1021}
1022
1023/*!
1024 Constructs a cursor pointing to the beginning of the \a frame.
1025*/
1026QTextCursor::QTextCursor(QTextFrame *frame)
1027 : d(new QTextCursorPrivate(QTextDocumentPrivate::get(document: frame->document())))
1028{
1029 d->adjusted_anchor = d->anchor = d->position = frame->firstPosition();
1030}
1031
1032
1033/*!
1034 Constructs a cursor pointing to the beginning of the \a block.
1035*/
1036QTextCursor::QTextCursor(const QTextBlock &block)
1037 : d(new QTextCursorPrivate(const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(block))))
1038{
1039 d->adjusted_anchor = d->anchor = d->position = block.position();
1040}
1041
1042
1043/*!
1044 \internal
1045 */
1046QTextCursor::QTextCursor(QTextDocumentPrivate *p, int pos)
1047 : d(new QTextCursorPrivate(p))
1048{
1049 d->adjusted_anchor = d->anchor = d->position = pos;
1050
1051 d->setX();
1052}
1053
1054/*!
1055 \internal
1056*/
1057QTextCursor::QTextCursor(QTextCursorPrivate *d)
1058{
1059 Q_ASSERT(d);
1060 this->d = d;
1061}
1062
1063/*!
1064 Constructs a new cursor that is a copy of \a cursor.
1065 */
1066QTextCursor::QTextCursor(const QTextCursor &cursor)
1067{
1068 d = cursor.d;
1069}
1070
1071/*!
1072 Makes a copy of \a cursor and assigns it to this QTextCursor. Note
1073 that QTextCursor is an \l{Implicitly Shared Classes}{implicitly
1074 shared} class.
1075
1076 */
1077QTextCursor &QTextCursor::operator=(const QTextCursor &cursor)
1078{
1079 d = cursor.d;
1080 return *this;
1081}
1082
1083/*!
1084 \fn void QTextCursor::swap(QTextCursor &other)
1085 \since 5.0
1086
1087 Swaps this text cursor instance with \a other. This function is
1088 very fast and never fails.
1089*/
1090
1091/*!
1092 Destroys the QTextCursor.
1093 */
1094QTextCursor::~QTextCursor()
1095{
1096}
1097
1098/*!
1099 Returns \c true if the cursor is null; otherwise returns \c false. A null
1100 cursor is created by the default constructor.
1101 */
1102bool QTextCursor::isNull() const
1103{
1104 return !d || !d->priv;
1105}
1106
1107/*!
1108 Moves the cursor to the absolute position in the document specified by
1109 \a pos using a \c MoveMode specified by \a m. The cursor is positioned
1110 between characters.
1111
1112 \note The "characters" in this case refer to the string of QChar
1113 objects, i.e. 16-bit Unicode characters, and \a pos is considered
1114 an index into this string. This does not necessarily correspond to
1115 individual graphemes in the writing system, as a single grapheme may
1116 be represented by multiple Unicode characters, such as in the case
1117 of surrogate pairs, linguistic ligatures or diacritics. For a more
1118 generic approach to navigating the document, use movePosition(),
1119 which will respect the actual grapheme boundaries in the text.
1120
1121 \sa position(), movePosition(), anchor()
1122*/
1123void QTextCursor::setPosition(int pos, MoveMode m)
1124{
1125 if (!d || !d->priv)
1126 return;
1127
1128 if (pos < 0 || pos >= d->priv->length()) {
1129 qWarning(msg: "QTextCursor::setPosition: Position '%d' out of range", pos);
1130 return;
1131 }
1132
1133 d->setPosition(pos);
1134 if (m == MoveAnchor) {
1135 d->anchor = pos;
1136 d->adjusted_anchor = pos;
1137 } else { // keep anchor
1138 QTextCursor::MoveOperation op;
1139 if (pos < d->anchor)
1140 op = QTextCursor::Left;
1141 else
1142 op = QTextCursor::Right;
1143 d->adjustCursor(m: op);
1144 }
1145 d->setX();
1146}
1147
1148/*!
1149 Returns the absolute position of the cursor within the document.
1150 The cursor is positioned between characters.
1151
1152 \note The "characters" in this case refer to the string of QChar
1153 objects, i.e. 16-bit Unicode characters, and the position is considered
1154 an index into this string. This does not necessarily correspond to
1155 individual graphemes in the writing system, as a single grapheme may
1156 be represented by multiple Unicode characters, such as in the case
1157 of surrogate pairs, linguistic ligatures or diacritics.
1158
1159 \sa setPosition(), movePosition(), anchor(), positionInBlock()
1160*/
1161int QTextCursor::position() const
1162{
1163 if (!d || !d->priv)
1164 return -1;
1165 return d->position;
1166}
1167
1168/*!
1169 \since 4.7
1170 Returns the relative position of the cursor within the block.
1171 The cursor is positioned between characters.
1172
1173 This is equivalent to \c{ position() - block().position()}.
1174
1175 \note The "characters" in this case refer to the string of QChar
1176 objects, i.e. 16-bit Unicode characters, and the position is considered
1177 an index into this string. This does not necessarily correspond to
1178 individual graphemes in the writing system, as a single grapheme may
1179 be represented by multiple Unicode characters, such as in the case
1180 of surrogate pairs, linguistic ligatures or diacritics.
1181
1182 \sa position()
1183*/
1184int QTextCursor::positionInBlock() const
1185{
1186 if (!d || !d->priv)
1187 return 0;
1188 return d->position - d->block().position();
1189}
1190
1191/*!
1192 Returns the anchor position; this is the same as position() unless
1193 there is a selection in which case position() marks one end of the
1194 selection and anchor() marks the other end. Just like the cursor
1195 position, the anchor position is between characters.
1196
1197 \sa position(), setPosition(), movePosition(), selectionStart(), selectionEnd()
1198*/
1199int QTextCursor::anchor() const
1200{
1201 if (!d || !d->priv)
1202 return -1;
1203 return d->anchor;
1204}
1205
1206/*!
1207 \fn bool QTextCursor::movePosition(MoveOperation operation, MoveMode mode, int n)
1208
1209 Moves the cursor by performing the given \a operation \a n times, using the specified
1210 \a mode, and returns \c true if all operations were completed successfully; otherwise
1211 returns \c false.
1212
1213 For example, if this function is repeatedly used to seek to the end of the next
1214 word, it will eventually fail when the end of the document is reached.
1215
1216 By default, the move operation is performed once (\a n = 1).
1217
1218 If \a mode is \c KeepAnchor, the cursor selects the text it moves
1219 over. This is the same effect that the user achieves when they
1220 hold down the Shift key and move the cursor with the cursor keys.
1221
1222 \sa setVisualNavigation()
1223*/
1224bool QTextCursor::movePosition(MoveOperation op, MoveMode mode, int n)
1225{
1226 if (!d || !d->priv)
1227 return false;
1228 switch (op) {
1229 case Start:
1230 case StartOfLine:
1231 case End:
1232 case EndOfLine:
1233 n = 1;
1234 break;
1235 default: break;
1236 }
1237
1238 int previousPosition = d->position;
1239 for (; n > 0; --n) {
1240 if (!d->movePosition(op, mode))
1241 return false;
1242 }
1243
1244 if (d->visualNavigation && !d->block().isVisible()) {
1245 QTextBlock b = d->block();
1246 if (previousPosition < d->position) {
1247 while (!b.next().isVisible())
1248 b = b.next();
1249 d->setPosition(b.position() + b.length() - 1);
1250 } else {
1251 while (!b.previous().isVisible())
1252 b = b.previous();
1253 d->setPosition(b.position());
1254 }
1255 if (mode == QTextCursor::MoveAnchor)
1256 d->anchor = d->position;
1257 while (d->movePosition(op, mode)
1258 && !d->block().isVisible())
1259 ;
1260
1261 }
1262 return true;
1263}
1264
1265/*!
1266 \since 4.4
1267
1268 Returns \c true if the cursor does visual navigation; otherwise
1269 returns \c false.
1270
1271 Visual navigation means skipping over hidden text paragraphs. The
1272 default is false.
1273
1274 \sa setVisualNavigation(), movePosition()
1275 */
1276bool QTextCursor::visualNavigation() const
1277{
1278 return d ? d->visualNavigation : false;
1279}
1280
1281/*!
1282 \since 4.4
1283
1284 Sets visual navigation to \a b.
1285
1286 Visual navigation means skipping over hidden text paragraphs. The
1287 default is false.
1288
1289 \sa visualNavigation(), movePosition()
1290 */
1291void QTextCursor::setVisualNavigation(bool b)
1292{
1293 if (d)
1294 d->visualNavigation = b;
1295}
1296
1297
1298/*!
1299 \since 4.7
1300
1301 Sets the visual x position for vertical cursor movements to \a x.
1302
1303 The vertical movement x position is cleared automatically when the cursor moves horizontally, and kept
1304 unchanged when the cursor moves vertically. The mechanism allows the cursor to move up and down on a
1305 visually straight line with proportional fonts, and to gently "jump" over short lines.
1306
1307 A value of -1 indicates no predefined x position. It will then be set automatically the next time the
1308 cursor moves up or down.
1309
1310 \sa verticalMovementX()
1311 */
1312void QTextCursor::setVerticalMovementX(int x)
1313{
1314 if (d)
1315 d->x = x;
1316}
1317
1318/*! \since 4.7
1319
1320 Returns the visual x position for vertical cursor movements.
1321
1322 A value of -1 indicates no predefined x position. It will then be set automatically the next time the
1323 cursor moves up or down.
1324
1325 \sa setVerticalMovementX()
1326 */
1327int QTextCursor::verticalMovementX() const
1328{
1329 return d ? d->x : -1;
1330}
1331
1332/*!
1333 \since 4.7
1334
1335 Returns whether the cursor should keep its current position when text gets inserted at the position of the
1336 cursor.
1337
1338 The default is false;
1339
1340 \sa setKeepPositionOnInsert()
1341 */
1342bool QTextCursor::keepPositionOnInsert() const
1343{
1344 return d ? d->keepPositionOnInsert : false;
1345}
1346
1347/*!
1348 \since 4.7
1349
1350 Defines whether the cursor should keep its current position when text gets inserted at the current position of the
1351 cursor.
1352
1353 If \a b is true, the cursor keeps its current position when text gets inserted at the positing of the cursor.
1354 If \a b is false, the cursor moves along with the inserted text.
1355
1356 The default is false.
1357
1358 Note that a cursor always moves when text is inserted before the current position of the cursor, and it
1359 always keeps its position when text is inserted after the current position of the cursor.
1360
1361 \sa keepPositionOnInsert()
1362 */
1363void QTextCursor::setKeepPositionOnInsert(bool b)
1364{
1365 if (d)
1366 d->keepPositionOnInsert = b;
1367}
1368
1369
1370
1371/*!
1372 Inserts \a text at the current position, using the current
1373 character format.
1374
1375 If there is a selection, the selection is deleted and replaced by
1376 \a text, for example:
1377 \snippet code/src_gui_text_qtextcursor.cpp 0
1378 This clears any existing selection, selects the word at the cursor
1379 (i.e. from position() forward), and replaces the selection with
1380 the phrase "Hello World".
1381
1382 Any ASCII linefeed characters (\\n) in the inserted text are transformed
1383 into unicode block separators, corresponding to insertBlock() calls.
1384
1385 \sa charFormat(), hasSelection()
1386*/
1387void QTextCursor::insertText(const QString &text)
1388{
1389 QTextCharFormat fmt = charFormat();
1390 fmt.clearProperty(propertyId: QTextFormat::ObjectType);
1391 insertText(text, format: fmt);
1392}
1393
1394/*!
1395 \fn void QTextCursor::insertText(const QString &text, const QTextCharFormat &format)
1396 \overload
1397
1398 Inserts \a text at the current position with the given \a format.
1399*/
1400void QTextCursor::insertText(const QString &text, const QTextCharFormat &_format)
1401{
1402 if (!d || !d->priv)
1403 return;
1404
1405 Q_ASSERT(_format.isValid());
1406
1407 QTextCharFormat format = _format;
1408 format.clearProperty(propertyId: QTextFormat::ObjectIndex);
1409
1410 bool hasEditBlock = false;
1411
1412 if (d->anchor != d->position) {
1413 hasEditBlock = true;
1414 d->priv->beginEditBlock();
1415 d->remove();
1416 }
1417
1418 if (!text.isEmpty()) {
1419 QTextFormatCollection *formats = d->priv->formatCollection();
1420 int formatIdx = formats->indexForFormat(f: format);
1421 Q_ASSERT(formats->format(formatIdx).isCharFormat());
1422
1423 QTextBlockFormat blockFmt = blockFormat();
1424
1425
1426 int textStart = d->priv->text.size();
1427 int blockStart = 0;
1428 d->priv->text += text;
1429 int textEnd = d->priv->text.size();
1430
1431 for (int i = 0; i < text.size(); ++i) {
1432 QChar ch = text.at(i);
1433
1434 const int blockEnd = i;
1435
1436 if (ch == u'\r'
1437 && (i + 1) < text.size()
1438 && text.at(i: i + 1) == u'\n') {
1439 ++i;
1440 ch = text.at(i);
1441 }
1442
1443 if (ch == u'\n'
1444 || ch == QChar::ParagraphSeparator
1445 || ch == QTextBeginningOfFrame
1446 || ch == QTextEndOfFrame
1447 || ch == u'\r') {
1448
1449 if (!hasEditBlock) {
1450 hasEditBlock = true;
1451 d->priv->beginEditBlock();
1452 }
1453
1454 if (blockEnd > blockStart)
1455 d->priv->insert(pos: d->position, strPos: textStart + blockStart, strLength: blockEnd - blockStart, format: formatIdx);
1456
1457 d->insertBlock(format: blockFmt, charFormat: format);
1458 blockStart = i + 1;
1459 }
1460 }
1461 if (textStart + blockStart < textEnd)
1462 d->priv->insert(pos: d->position, strPos: textStart + blockStart, strLength: textEnd - textStart - blockStart, format: formatIdx);
1463 }
1464 if (hasEditBlock)
1465 d->priv->endEditBlock();
1466 d->setX();
1467}
1468
1469/*!
1470 If there is no selected text, deletes the character \e at the
1471 current cursor position; otherwise deletes the selected text.
1472
1473 \sa deletePreviousChar(), hasSelection(), clearSelection()
1474*/
1475void QTextCursor::deleteChar()
1476{
1477 if (!d || !d->priv)
1478 return;
1479
1480 if (d->position != d->anchor) {
1481 removeSelectedText();
1482 return;
1483 }
1484
1485 if (!d->canDelete(pos: d->position))
1486 return;
1487 d->adjusted_anchor = d->anchor =
1488 d->priv->nextCursorPosition(position: d->anchor, mode: QTextLayout::SkipCharacters);
1489 d->remove();
1490 d->setX();
1491}
1492
1493/*!
1494 If there is no selected text, deletes the character \e before the
1495 current cursor position; otherwise deletes the selected text.
1496
1497 \sa deleteChar(), hasSelection(), clearSelection()
1498*/
1499void QTextCursor::deletePreviousChar()
1500{
1501 if (!d || !d->priv)
1502 return;
1503
1504 if (d->position != d->anchor) {
1505 removeSelectedText();
1506 return;
1507 }
1508
1509 if (d->anchor < 1 || !d->canDelete(pos: d->anchor-1))
1510 return;
1511 d->anchor--;
1512
1513 QTextDocumentPrivate::FragmentIterator fragIt = d->priv->find(pos: d->anchor);
1514 const QTextFragmentData * const frag = fragIt.value();
1515 int fpos = fragIt.position();
1516 QChar uc = d->priv->buffer().at(i: d->anchor - fpos + frag->stringPosition);
1517 if (d->anchor > fpos && uc.isLowSurrogate()) {
1518 // second half of a surrogate, check if we have the first half as well,
1519 // if yes delete both at once
1520 uc = d->priv->buffer().at(i: d->anchor - 1 - fpos + frag->stringPosition);
1521 if (uc.isHighSurrogate())
1522 --d->anchor;
1523 }
1524
1525 d->adjusted_anchor = d->anchor;
1526 d->remove();
1527 d->setX();
1528}
1529
1530/*!
1531 Selects text in the document according to the given \a selection.
1532*/
1533void QTextCursor::select(SelectionType selection)
1534{
1535 if (!d || !d->priv)
1536 return;
1537
1538 clearSelection();
1539
1540 const QTextBlock block = d->block();
1541
1542 switch (selection) {
1543 case LineUnderCursor:
1544 movePosition(op: StartOfLine);
1545 movePosition(op: EndOfLine, mode: KeepAnchor);
1546 break;
1547 case WordUnderCursor:
1548 movePosition(op: StartOfWord);
1549 movePosition(op: EndOfWord, mode: KeepAnchor);
1550 break;
1551 case BlockUnderCursor:
1552 if (block.length() == 1) // no content
1553 break;
1554 movePosition(op: StartOfBlock);
1555 // also select the paragraph separator
1556 if (movePosition(op: PreviousBlock)) {
1557 movePosition(op: EndOfBlock);
1558 movePosition(op: NextBlock, mode: KeepAnchor);
1559 }
1560 movePosition(op: EndOfBlock, mode: KeepAnchor);
1561 break;
1562 case Document:
1563 movePosition(op: Start);
1564 movePosition(op: End, mode: KeepAnchor);
1565 break;
1566 }
1567}
1568
1569/*!
1570 Returns \c true if the cursor contains a selection; otherwise returns \c false.
1571*/
1572bool QTextCursor::hasSelection() const
1573{
1574 return !!d && d->position != d->anchor;
1575}
1576
1577
1578/*!
1579 Returns \c true if the cursor contains a selection that is not simply a
1580 range from selectionStart() to selectionEnd(); otherwise returns \c false.
1581
1582 Complex selections are ones that span at least two cells in a table;
1583 their extent is specified by selectedTableCells().
1584*/
1585bool QTextCursor::hasComplexSelection() const
1586{
1587 if (!d)
1588 return false;
1589
1590 return d->complexSelectionTable() != nullptr;
1591}
1592
1593/*!
1594 If the selection spans over table cells, \a firstRow is populated
1595 with the number of the first row in the selection, \a firstColumn
1596 with the number of the first column in the selection, and \a
1597 numRows and \a numColumns with the number of rows and columns in
1598 the selection. If the selection does not span any table cells the
1599 results are harmless but undefined.
1600*/
1601void QTextCursor::selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const
1602{
1603 *firstRow = -1;
1604 *firstColumn = -1;
1605 *numRows = -1;
1606 *numColumns = -1;
1607
1608 if (!d || d->position == d->anchor)
1609 return;
1610
1611 d->selectedTableCells(firstRow, numRows, firstColumn, numColumns);
1612}
1613
1614
1615/*!
1616 Clears the current selection by setting the anchor to the cursor position.
1617
1618 Note that it does \b{not} delete the text of the selection.
1619
1620 \sa removeSelectedText(), hasSelection()
1621*/
1622void QTextCursor::clearSelection()
1623{
1624 if (!d)
1625 return;
1626 d->adjusted_anchor = d->anchor = d->position;
1627 d->currentCharFormat = -1;
1628}
1629
1630/*!
1631 If there is a selection, its content is deleted; otherwise does
1632 nothing.
1633
1634 \sa hasSelection()
1635*/
1636void QTextCursor::removeSelectedText()
1637{
1638 if (!d || !d->priv || d->position == d->anchor)
1639 return;
1640
1641 d->priv->beginEditBlock();
1642 d->remove();
1643 d->priv->endEditBlock();
1644 d->setX();
1645}
1646
1647/*!
1648 Returns the start of the selection or position() if the
1649 cursor doesn't have a selection.
1650
1651 \sa selectionEnd(), position(), anchor()
1652*/
1653int QTextCursor::selectionStart() const
1654{
1655 if (!d || !d->priv)
1656 return -1;
1657 return qMin(a: d->position, b: d->adjusted_anchor);
1658}
1659
1660/*!
1661 Returns the end of the selection or position() if the cursor
1662 doesn't have a selection.
1663
1664 \sa selectionStart(), position(), anchor()
1665*/
1666int QTextCursor::selectionEnd() const
1667{
1668 if (!d || !d->priv)
1669 return -1;
1670 return qMax(a: d->position, b: d->adjusted_anchor);
1671}
1672
1673static void getText(QString &text, QTextDocumentPrivate *priv, const QString &docText, int pos, int end)
1674{
1675 while (pos < end) {
1676 QTextDocumentPrivate::FragmentIterator fragIt = priv->find(pos);
1677 const QTextFragmentData * const frag = fragIt.value();
1678
1679 const int offsetInFragment = qMax(a: 0, b: pos - fragIt.position());
1680 const int len = qMin(a: int(frag->size_array[0] - offsetInFragment), b: end - pos);
1681
1682 text += QString(docText.constData() + frag->stringPosition + offsetInFragment, len);
1683 pos += len;
1684 }
1685}
1686
1687/*!
1688 Returns the current selection's text (which may be empty). This
1689 only returns the text, with no rich text formatting information.
1690 If you want a document fragment (i.e. formatted rich text) use
1691 selection() instead.
1692
1693 \note If the selection obtained from an editor spans a line break,
1694 the text will contain a Unicode U+2029 paragraph separator character
1695 instead of a newline \c{\n} character. Use QString::replace() to
1696 replace these characters with newlines.
1697*/
1698QString QTextCursor::selectedText() const
1699{
1700 if (!d || !d->priv || d->position == d->anchor)
1701 return QString();
1702
1703 const QString docText = d->priv->buffer();
1704 QString text;
1705
1706 QTextTable *table = d->complexSelectionTable();
1707 if (table) {
1708 int row_start, col_start, num_rows, num_cols;
1709 selectedTableCells(firstRow: &row_start, numRows: &num_rows, firstColumn: &col_start, numColumns: &num_cols);
1710
1711 Q_ASSERT(row_start != -1);
1712 for (int r = row_start; r < row_start + num_rows; ++r) {
1713 for (int c = col_start; c < col_start + num_cols; ++c) {
1714 QTextTableCell cell = table->cellAt(row: r, col: c);
1715 int rspan = cell.rowSpan();
1716 int cspan = cell.columnSpan();
1717 if (rspan != 1) {
1718 int cr = cell.row();
1719 if (cr != r)
1720 continue;
1721 }
1722 if (cspan != 1) {
1723 int cc = cell.column();
1724 if (cc != c)
1725 continue;
1726 }
1727
1728 getText(text, priv: d->priv, docText, pos: cell.firstPosition(), end: cell.lastPosition());
1729 }
1730 }
1731 } else {
1732 getText(text, priv: d->priv, docText, pos: selectionStart(), end: selectionEnd());
1733 }
1734
1735 return text;
1736}
1737
1738/*!
1739 Returns the current selection (which may be empty) with all its
1740 formatting information. If you just want the selected text (i.e.
1741 plain text) use selectedText() instead.
1742
1743 \note Unlike QTextDocumentFragment::toPlainText(),
1744 selectedText() may include special unicode characters such as
1745 QChar::ParagraphSeparator.
1746
1747 \sa QTextDocumentFragment::toPlainText()
1748*/
1749QTextDocumentFragment QTextCursor::selection() const
1750{
1751 return QTextDocumentFragment(*this);
1752}
1753
1754/*!
1755 Returns the block that contains the cursor.
1756*/
1757QTextBlock QTextCursor::block() const
1758{
1759 if (!d || !d->priv)
1760 return QTextBlock();
1761 return d->block();
1762}
1763
1764/*!
1765 Returns the block format of the block the cursor is in.
1766
1767 \sa setBlockFormat(), charFormat()
1768 */
1769QTextBlockFormat QTextCursor::blockFormat() const
1770{
1771 if (!d || !d->priv)
1772 return QTextBlockFormat();
1773
1774 return d->block().blockFormat();
1775}
1776
1777/*!
1778 Sets the block format of the current block (or all blocks that
1779 are contained in the selection) to \a format.
1780
1781 \sa blockFormat(), mergeBlockFormat()
1782*/
1783void QTextCursor::setBlockFormat(const QTextBlockFormat &format)
1784{
1785 if (!d || !d->priv)
1786 return;
1787
1788 d->setBlockFormat(format, changeMode: QTextDocumentPrivate::SetFormat);
1789}
1790
1791/*!
1792 Modifies the block format of the current block (or all blocks that
1793 are contained in the selection) with the block format specified by
1794 \a modifier.
1795
1796 \sa setBlockFormat(), blockFormat()
1797*/
1798void QTextCursor::mergeBlockFormat(const QTextBlockFormat &modifier)
1799{
1800 if (!d || !d->priv)
1801 return;
1802
1803 d->setBlockFormat(format: modifier, changeMode: QTextDocumentPrivate::MergeFormat);
1804}
1805
1806/*!
1807 Returns the block character format of the block the cursor is in.
1808
1809 The block char format is the format used when inserting text at the
1810 beginning of an empty block.
1811
1812 \sa setBlockCharFormat()
1813 */
1814QTextCharFormat QTextCursor::blockCharFormat() const
1815{
1816 if (!d || !d->priv)
1817 return QTextCharFormat();
1818
1819 return d->block().charFormat();
1820}
1821
1822/*!
1823 Sets the block char format of the current block (or all blocks that
1824 are contained in the selection) to \a format.
1825
1826 \sa blockCharFormat()
1827*/
1828void QTextCursor::setBlockCharFormat(const QTextCharFormat &format)
1829{
1830 if (!d || !d->priv)
1831 return;
1832
1833 d->setBlockCharFormat(format: format, changeMode: QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
1834}
1835
1836/*!
1837 Modifies the block char format of the current block (or all blocks that
1838 are contained in the selection) with the block format specified by
1839 \a modifier.
1840
1841 \sa setBlockCharFormat()
1842*/
1843void QTextCursor::mergeBlockCharFormat(const QTextCharFormat &modifier)
1844{
1845 if (!d || !d->priv)
1846 return;
1847
1848 d->setBlockCharFormat(format: modifier, changeMode: QTextDocumentPrivate::MergeFormat);
1849}
1850
1851/*!
1852 Returns the format of the character immediately before the cursor
1853 position(). If the cursor is positioned at the beginning of a text
1854 block that is not empty then the format of the character
1855 immediately after the cursor is returned.
1856
1857 \sa insertText(), blockFormat()
1858 */
1859QTextCharFormat QTextCursor::charFormat() const
1860{
1861 if (!d || !d->priv)
1862 return QTextCharFormat();
1863
1864 int idx = d->currentCharFormat;
1865 if (idx == -1) {
1866 QTextBlock block = d->block();
1867
1868 int pos;
1869 if (d->position == block.position()
1870 && block.length() > 1)
1871 pos = d->position;
1872 else
1873 pos = d->position - 1;
1874
1875 if (pos == -1) {
1876 idx = d->priv->blockCharFormatIndex(node: d->priv->blockMap().firstNode());
1877 } else {
1878 Q_ASSERT(pos >= 0 && pos < d->priv->length());
1879
1880 QTextDocumentPrivate::FragmentIterator it = d->priv->find(pos);
1881 Q_ASSERT(!it.atEnd());
1882 idx = it.value()->format;
1883 }
1884 }
1885
1886 QTextCharFormat cfmt = d->priv->formatCollection()->charFormat(index: idx);
1887 cfmt.clearProperty(propertyId: QTextFormat::ObjectIndex);
1888
1889 Q_ASSERT(cfmt.isValid());
1890 return cfmt;
1891}
1892
1893/*!
1894 Sets the cursor's current character format to the given \a
1895 format. If the cursor has a selection, the given \a format is
1896 applied to the current selection.
1897
1898 \sa hasSelection(), mergeCharFormat()
1899*/
1900void QTextCursor::setCharFormat(const QTextCharFormat &format)
1901{
1902 if (!d || !d->priv)
1903 return;
1904 if (d->position == d->anchor) {
1905 d->currentCharFormat = d->priv->formatCollection()->indexForFormat(f: format);
1906 return;
1907 }
1908 d->setCharFormat(format: format, changeMode: QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
1909}
1910
1911/*!
1912 Merges the cursor's current character format with the properties
1913 described by format \a modifier. If the cursor has a selection,
1914 this function applies all the properties set in \a modifier to all
1915 the character formats that are part of the selection.
1916
1917 \sa hasSelection(), setCharFormat()
1918*/
1919void QTextCursor::mergeCharFormat(const QTextCharFormat &modifier)
1920{
1921 if (!d || !d->priv)
1922 return;
1923 if (d->position == d->anchor) {
1924 QTextCharFormat format = charFormat();
1925 format.merge(other: modifier);
1926 d->currentCharFormat = d->priv->formatCollection()->indexForFormat(f: format);
1927 return;
1928 }
1929
1930 d->setCharFormat(format: modifier, changeMode: QTextDocumentPrivate::MergeFormat);
1931}
1932
1933/*!
1934 Returns \c true if the cursor is at the start of a block; otherwise
1935 returns \c false.
1936
1937 \sa atBlockEnd(), atStart()
1938*/
1939bool QTextCursor::atBlockStart() const
1940{
1941 if (!d || !d->priv)
1942 return false;
1943
1944 return d->position == d->block().position();
1945}
1946
1947/*!
1948 Returns \c true if the cursor is at the end of a block; otherwise
1949 returns \c false.
1950
1951 \sa atBlockStart(), atEnd()
1952*/
1953bool QTextCursor::atBlockEnd() const
1954{
1955 if (!d || !d->priv)
1956 return false;
1957
1958 return d->position == d->block().position() + d->block().length() - 1;
1959}
1960
1961/*!
1962 Returns \c true if the cursor is at the start of the document;
1963 otherwise returns \c false.
1964
1965 \sa atBlockStart(), atEnd()
1966*/
1967bool QTextCursor::atStart() const
1968{
1969 if (!d || !d->priv)
1970 return false;
1971
1972 return d->position == 0;
1973}
1974
1975/*!
1976 \since 4.6
1977
1978 Returns \c true if the cursor is at the end of the document;
1979 otherwise returns \c false.
1980
1981 \sa atStart(), atBlockEnd()
1982*/
1983bool QTextCursor::atEnd() const
1984{
1985 if (!d || !d->priv)
1986 return false;
1987
1988 return d->position == d->priv->length() - 1;
1989}
1990
1991/*!
1992 Inserts a new empty block at the cursor position() with the
1993 current blockFormat() and charFormat().
1994
1995 \sa setBlockFormat()
1996*/
1997void QTextCursor::insertBlock()
1998{
1999 insertBlock(format: blockFormat());
2000}
2001
2002/*!
2003 \overload
2004
2005 Inserts a new empty block at the cursor position() with block
2006 format \a format and the current charFormat() as block char format.
2007
2008 \sa setBlockFormat()
2009*/
2010void QTextCursor::insertBlock(const QTextBlockFormat &format)
2011{
2012 QTextCharFormat charFmt = charFormat();
2013 charFmt.clearProperty(propertyId: QTextFormat::ObjectType);
2014 insertBlock(format, charFormat: charFmt);
2015}
2016
2017/*!
2018 \fn void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat)
2019 \overload
2020
2021 Inserts a new empty block at the cursor position() with block
2022 format \a format and \a charFormat as block char format.
2023
2024 \sa setBlockFormat()
2025*/
2026void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &_charFormat)
2027{
2028 if (!d || !d->priv)
2029 return;
2030
2031 QTextCharFormat charFormat = _charFormat;
2032 charFormat.clearProperty(propertyId: QTextFormat::ObjectIndex);
2033
2034 d->priv->beginEditBlock();
2035 d->remove();
2036 d->insertBlock(format, charFormat);
2037 d->priv->endEditBlock();
2038 d->setX();
2039}
2040
2041/*!
2042 Inserts a new block at the current position and makes it the first
2043 list item of a newly created list with the given \a format. Returns
2044 the created list.
2045
2046 \sa currentList(), createList(), insertBlock()
2047 */
2048QTextList *QTextCursor::insertList(const QTextListFormat &format)
2049{
2050 insertBlock();
2051 return createList(format);
2052}
2053
2054/*!
2055 \overload
2056
2057 Inserts a new block at the current position and makes it the first
2058 list item of a newly created list with the given \a style. Returns
2059 the created list.
2060
2061 \sa currentList(), createList(), insertBlock()
2062 */
2063QTextList *QTextCursor::insertList(QTextListFormat::Style style)
2064{
2065 insertBlock();
2066 return createList(style);
2067}
2068
2069/*!
2070 Creates and returns a new list with the given \a format, and makes the
2071 current paragraph the cursor is in the first list item.
2072
2073 \sa insertList(), currentList()
2074 */
2075QTextList *QTextCursor::createList(const QTextListFormat &format)
2076{
2077 if (!d || !d->priv)
2078 return nullptr;
2079
2080 QTextList *list = static_cast<QTextList *>(d->priv->createObject(newFormat: format));
2081 QTextBlockFormat modifier;
2082 modifier.setObjectIndex(list->objectIndex());
2083 mergeBlockFormat(modifier);
2084 return list;
2085}
2086
2087/*!
2088 \overload
2089
2090 Creates and returns a new list with the given \a style, making the
2091 cursor's current paragraph the first list item.
2092
2093 The style to be used is defined by the QTextListFormat::Style enum.
2094
2095 \sa insertList(), currentList()
2096 */
2097QTextList *QTextCursor::createList(QTextListFormat::Style style)
2098{
2099 QTextListFormat fmt;
2100 fmt.setStyle(style);
2101 return createList(format: fmt);
2102}
2103
2104/*!
2105 Returns the current list if the cursor position() is inside a
2106 block that is part of a list; otherwise returns \nullptr.
2107
2108 \sa insertList(), createList()
2109 */
2110QTextList *QTextCursor::currentList() const
2111{
2112 if (!d || !d->priv)
2113 return nullptr;
2114
2115 QTextBlockFormat b = blockFormat();
2116 QTextObject *o = d->priv->objectForFormat(f: b);
2117 return qobject_cast<QTextList *>(object: o);
2118}
2119
2120/*!
2121 \fn QTextTable *QTextCursor::insertTable(int rows, int columns)
2122
2123 \overload
2124
2125 Creates a new table with the given number of \a rows and \a columns,
2126 inserts it at the current cursor position() in the document, and returns
2127 the table object. The cursor is moved to the beginning of the first cell.
2128
2129 There must be at least one row and one column in the table.
2130
2131 \sa currentTable()
2132 */
2133QTextTable *QTextCursor::insertTable(int rows, int cols)
2134{
2135 return insertTable(rows, cols, format: QTextTableFormat());
2136}
2137
2138/*!
2139 \fn QTextTable *QTextCursor::insertTable(int rows, int columns, const QTextTableFormat &format)
2140
2141 Creates a new table with the given number of \a rows and \a columns
2142 in the specified \a format, inserts it at the current cursor position()
2143 in the document, and returns the table object. The cursor is moved to
2144 the beginning of the first cell.
2145
2146 There must be at least one row and one column in the table.
2147
2148 \sa currentTable()
2149*/
2150QTextTable *QTextCursor::insertTable(int rows, int cols, const QTextTableFormat &format)
2151{
2152 if (!d || !d->priv || rows == 0 || cols == 0)
2153 return nullptr;
2154
2155 int pos = d->position;
2156 QTextTable *t = QTextTablePrivate::createTable(d->priv, pos: d->position, rows, cols, tableFormat: format);
2157 d->setPosition(pos+1);
2158 // ##### what should we do if we have a selection?
2159 d->anchor = d->position;
2160 d->adjusted_anchor = d->anchor;
2161 return t;
2162}
2163
2164/*!
2165 Returns a pointer to the current table if the cursor position()
2166 is inside a block that is part of a table; otherwise returns \nullptr.
2167
2168 \sa insertTable()
2169*/
2170QTextTable *QTextCursor::currentTable() const
2171{
2172 if (!d || !d->priv)
2173 return nullptr;
2174
2175 QTextFrame *frame = d->priv->frameAt(pos: d->position);
2176 while (frame) {
2177 QTextTable *table = qobject_cast<QTextTable *>(object: frame);
2178 if (table)
2179 return table;
2180 frame = frame->parentFrame();
2181 }
2182 return nullptr;
2183}
2184
2185/*!
2186 Inserts a frame with the given \a format at the current cursor position(),
2187 moves the cursor position() inside the frame, and returns the frame.
2188
2189 If the cursor holds a selection, the whole selection is moved inside the
2190 frame.
2191
2192 \sa hasSelection()
2193*/
2194QTextFrame *QTextCursor::insertFrame(const QTextFrameFormat &format)
2195{
2196 if (!d || !d->priv)
2197 return nullptr;
2198
2199 return d->priv->insertFrame(start: selectionStart(), end: selectionEnd(), format);
2200}
2201
2202/*!
2203 Returns a pointer to the current frame. Returns \nullptr if the cursor is invalid.
2204
2205 \sa insertFrame()
2206*/
2207QTextFrame *QTextCursor::currentFrame() const
2208{
2209 if (!d || !d->priv)
2210 return nullptr;
2211
2212 return d->priv->frameAt(pos: d->position);
2213}
2214
2215
2216/*!
2217 Inserts the text \a fragment at the current position().
2218*/
2219void QTextCursor::insertFragment(const QTextDocumentFragment &fragment)
2220{
2221 if (!d || !d->priv || fragment.isEmpty())
2222 return;
2223
2224 d->priv->beginEditBlock();
2225 d->remove();
2226 fragment.d->insert(cursor&: *this);
2227 d->priv->endEditBlock();
2228 d->setX();
2229
2230 if (fragment.d && fragment.d->doc)
2231 d->priv->mergeCachedResources(priv: QTextDocumentPrivate::get(document: fragment.d->doc));
2232}
2233
2234/*!
2235 \since 4.2
2236 Inserts the text \a html at the current position(). The text is interpreted as
2237 HTML.
2238
2239 \note When using this function with a style sheet, the style sheet will
2240 only apply to the current block in the document. In order to apply a style
2241 sheet throughout a document, use QTextDocument::setDefaultStyleSheet()
2242 instead.
2243*/
2244
2245#ifndef QT_NO_TEXTHTMLPARSER
2246
2247void QTextCursor::insertHtml(const QString &html)
2248{
2249 if (!d || !d->priv)
2250 return;
2251 QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(html, resourceProvider: d->priv->document());
2252 insertFragment(fragment);
2253}
2254
2255#endif // QT_NO_TEXTHTMLPARSER
2256
2257/*!
2258 \since 6.4
2259 Inserts the \a markdown text at the current position(),
2260 with the specified Markdown \a features. The default is GitHub dialect.
2261*/
2262
2263#if QT_CONFIG(textmarkdownreader)
2264
2265void QTextCursor::insertMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
2266{
2267 if (!d || !d->priv)
2268 return;
2269 QTextDocumentFragment fragment = QTextDocumentFragment::fromMarkdown(markdown, features);
2270 if (markdown.startsWith(c: QLatin1Char('\n')))
2271 insertBlock(format: fragment.d->doc->firstBlock().blockFormat());
2272 insertFragment(fragment);
2273 if (!atEnd() && markdown.endsWith(c: QLatin1Char('\n')))
2274 insertText(text: QLatin1String("\n"));
2275}
2276
2277#endif // textmarkdownreader
2278
2279/*!
2280 \overload
2281 \since 4.2
2282
2283 Inserts the image defined by the given \a format at the cursor's current position
2284 with the specified \a alignment.
2285
2286 \sa position()
2287*/
2288void QTextCursor::insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment)
2289{
2290 if (!d || !d->priv)
2291 return;
2292
2293 QTextFrameFormat ffmt;
2294 ffmt.setPosition(alignment);
2295 QTextObject *obj = d->priv->createObject(newFormat: ffmt);
2296
2297 QTextImageFormat fmt = format;
2298 fmt.setObjectIndex(obj->objectIndex());
2299
2300 d->priv->beginEditBlock();
2301 d->remove();
2302 const int idx = d->priv->formatCollection()->indexForFormat(f: fmt);
2303 d->priv->insert(pos: d->position, text: QString(QChar(QChar::ObjectReplacementCharacter)), format: idx);
2304 d->priv->endEditBlock();
2305}
2306
2307/*!
2308 Inserts the image defined by \a format at the current position().
2309*/
2310void QTextCursor::insertImage(const QTextImageFormat &format)
2311{
2312 insertText(text: QString(QChar::ObjectReplacementCharacter), format: format);
2313}
2314
2315/*!
2316 \overload
2317
2318 Convenience method for inserting the image with the given \a name at the
2319 current position().
2320
2321 \snippet code/src_gui_text_qtextcursor.cpp 1
2322*/
2323void QTextCursor::insertImage(const QString &name)
2324{
2325 QTextImageFormat format;
2326 format.setName(name);
2327 insertImage(format);
2328}
2329
2330/*!
2331 \since 4.5
2332 \overload
2333
2334 Convenience function for inserting the given \a image with an optional
2335 \a name at the current position().
2336*/
2337void QTextCursor::insertImage(const QImage &image, const QString &name)
2338{
2339 if (image.isNull()) {
2340 qWarning(msg: "QTextCursor::insertImage: attempt to add an invalid image");
2341 return;
2342 }
2343 QString imageName = name;
2344 if (name.isEmpty())
2345 imageName = QString::number(image.cacheKey());
2346 d->priv->document()->addResource(type: QTextDocument::ImageResource, name: QUrl(imageName), resource: image);
2347 QTextImageFormat format;
2348 format.setName(imageName);
2349 insertImage(format);
2350}
2351
2352/*!
2353 \fn bool QTextCursor::operator!=(const QTextCursor &other) const
2354
2355 Returns \c true if the \a other cursor is at a different position in
2356 the document as this cursor; otherwise returns \c false.
2357*/
2358bool QTextCursor::operator!=(const QTextCursor &rhs) const
2359{
2360 return !operator==(rhs);
2361}
2362
2363/*!
2364 \fn bool QTextCursor::operator<(const QTextCursor &other) const
2365
2366 Returns \c true if the \a other cursor is positioned later in the
2367 document than this cursor; otherwise returns \c false.
2368*/
2369bool QTextCursor::operator<(const QTextCursor &rhs) const
2370{
2371 if (!d)
2372 return !!rhs.d;
2373
2374 if (!rhs.d)
2375 return false;
2376
2377 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<", "cannot compare cursors attached to different documents");
2378
2379 return d->position < rhs.d->position;
2380}
2381
2382/*!
2383 \fn bool QTextCursor::operator<=(const QTextCursor &other) const
2384
2385 Returns \c true if the \a other cursor is positioned later or at the
2386 same position in the document as this cursor; otherwise returns
2387 false.
2388*/
2389bool QTextCursor::operator<=(const QTextCursor &rhs) const
2390{
2391 if (!d)
2392 return true;
2393
2394 if (!rhs.d)
2395 return false;
2396
2397 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<=", "cannot compare cursors attached to different documents");
2398
2399 return d->position <= rhs.d->position;
2400}
2401
2402/*!
2403 \fn bool QTextCursor::operator==(const QTextCursor &other) const
2404
2405 Returns \c true if the \a other cursor is at the same position in the
2406 document as this cursor; otherwise returns \c false.
2407*/
2408bool QTextCursor::operator==(const QTextCursor &rhs) const
2409{
2410 if (!d)
2411 return !rhs.d;
2412
2413 if (!rhs.d)
2414 return false;
2415
2416 return d->position == rhs.d->position && d->priv == rhs.d->priv;
2417}
2418
2419/*!
2420 \fn bool QTextCursor::operator>=(const QTextCursor &other) const
2421
2422 Returns \c true if the \a other cursor is positioned earlier or at the
2423 same position in the document as this cursor; otherwise returns
2424 false.
2425*/
2426bool QTextCursor::operator>=(const QTextCursor &rhs) const
2427{
2428 if (!d)
2429 return false;
2430
2431 if (!rhs.d)
2432 return true;
2433
2434 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>=", "cannot compare cursors attached to different documents");
2435
2436 return d->position >= rhs.d->position;
2437}
2438
2439/*!
2440 \fn bool QTextCursor::operator>(const QTextCursor &other) const
2441
2442 Returns \c true if the \a other cursor is positioned earlier in the
2443 document than this cursor; otherwise returns \c false.
2444*/
2445bool QTextCursor::operator>(const QTextCursor &rhs) const
2446{
2447 if (!d)
2448 return false;
2449
2450 if (!rhs.d)
2451 return true;
2452
2453 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>", "cannot compare cursors attached to different documents");
2454
2455 return d->position > rhs.d->position;
2456}
2457
2458/*!
2459 Indicates the start of a block of editing operations on the
2460 document that should appear as a single operation from an
2461 undo/redo point of view.
2462
2463 For example:
2464
2465 \snippet code/src_gui_text_qtextcursor.cpp 2
2466
2467 The call to undo() will cause both insertions to be undone,
2468 causing both "World" and "Hello" to be removed.
2469
2470 It is possible to nest calls to beginEditBlock and endEditBlock. The
2471 top-most pair will determine the scope of the undo/redo operation.
2472
2473 \sa endEditBlock()
2474 */
2475void QTextCursor::beginEditBlock()
2476{
2477 if (!d || !d->priv)
2478 return;
2479
2480 if (d->priv->editBlock == 0) // we are the initial edit block, store current cursor position for undo
2481 d->priv->editBlockCursorPosition = d->position;
2482
2483 d->priv->beginEditBlock();
2484}
2485
2486/*!
2487 Like beginEditBlock() indicates the start of a block of editing operations
2488 that should appear as a single operation for undo/redo. However unlike
2489 beginEditBlock() it does not start a new block but reverses the previous call to
2490 endEditBlock() and therefore makes following operations part of the previous edit block created.
2491
2492 For example:
2493
2494 \snippet code/src_gui_text_qtextcursor.cpp 3
2495
2496 The call to undo() will cause all three insertions to be undone.
2497
2498 \sa beginEditBlock(), endEditBlock()
2499 */
2500void QTextCursor::joinPreviousEditBlock()
2501{
2502 if (!d || !d->priv)
2503 return;
2504
2505 d->priv->joinPreviousEditBlock();
2506}
2507
2508/*!
2509 Indicates the end of a block of editing operations on the document
2510 that should appear as a single operation from an undo/redo point
2511 of view.
2512
2513 \sa beginEditBlock()
2514 */
2515
2516void QTextCursor::endEditBlock()
2517{
2518 if (!d || !d->priv)
2519 return;
2520
2521 d->priv->endEditBlock();
2522}
2523
2524/*!
2525 Returns \c true if this cursor and \a other are copies of each other, i.e.
2526 one of them was created as a copy of the other and neither has moved since.
2527 This is much stricter than equality.
2528
2529 \sa operator=(), operator==()
2530*/
2531bool QTextCursor::isCopyOf(const QTextCursor &other) const
2532{
2533 return d == other.d;
2534}
2535
2536/*!
2537 \since 4.2
2538 Returns the number of the block the cursor is in, or 0 if the cursor is invalid.
2539
2540 Note that this function only makes sense in documents without complex objects such
2541 as tables or frames.
2542*/
2543int QTextCursor::blockNumber() const
2544{
2545 if (!d || !d->priv)
2546 return 0;
2547
2548 return d->block().blockNumber();
2549}
2550
2551
2552/*!
2553 \since 4.2
2554 Returns the position of the cursor within its containing line.
2555
2556 Note that this is the column number relative to a wrapped line,
2557 not relative to the block (i.e. the paragraph).
2558
2559 You probably want to call positionInBlock() instead.
2560
2561 \sa positionInBlock()
2562*/
2563int QTextCursor::columnNumber() const
2564{
2565 if (!d || !d->priv)
2566 return 0;
2567
2568 QTextBlock block = d->block();
2569 if (!block.isValid())
2570 return 0;
2571
2572 const QTextLayout *layout = d->blockLayout(block);
2573
2574 const int relativePos = d->position - block.position();
2575
2576 if (layout->lineCount() == 0)
2577 return relativePos;
2578
2579 QTextLine line = layout->lineForTextPosition(pos: relativePos);
2580 if (!line.isValid())
2581 return 0;
2582 return relativePos - line.textStart();
2583}
2584
2585/*!
2586 \since 4.5
2587 Returns the document this cursor is associated with.
2588*/
2589QTextDocument *QTextCursor::document() const
2590{
2591 if (d->priv)
2592 return d->priv->document();
2593 return nullptr; // document went away
2594}
2595
2596QT_END_NAMESPACE
2597

source code of qtbase/src/gui/text/qtextcursor.cpp