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