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. If text is selected, the first call
965 to \c{movePosition(NextCharacter, MoveAnchor)} deselects the text without moving the cursor.
966 \value NextWord Move to the next word.
967 \value Down Move down one line.
968 \value Right Move right one character.
969 \value WordRight Move right one word.
970
971 \value NextCell Move to the beginning of the next table cell inside the
972 current table. If the current cell is the last cell in the row, the
973 cursor will move to the first cell in the next row.
974 \value PreviousCell Move to the beginning of the previous table cell
975 inside the current table. If the current cell is the first cell in
976 the row, the cursor will move to the last cell in the previous row.
977 \value NextRow Move to the first new cell of the next row in the current
978 table.
979 \value PreviousRow Move to the last cell of the previous row in the
980 current table.
981
982 \sa movePosition()
983*/
984
985/*!
986 \enum QTextCursor::MoveMode
987
988 \value MoveAnchor Moves the anchor to the same position as the cursor itself.
989 \value KeepAnchor Keeps the anchor where it is.
990
991 If the anchor() is kept where it is and the position() is moved,
992 the text in between will be selected.
993*/
994
995/*!
996 \enum QTextCursor::SelectionType
997
998 This enum describes the types of selection that can be applied with the
999 select() function.
1000
1001 \value Document Selects the entire document.
1002 \value BlockUnderCursor Selects the block of text under the cursor.
1003 \value LineUnderCursor Selects the line of text under the cursor.
1004 \value WordUnderCursor Selects the word under the cursor. If the cursor
1005 is not positioned within a string of selectable characters, no
1006 text is selected.
1007*/
1008
1009/*!
1010 Constructs a null cursor.
1011 */
1012QTextCursor::QTextCursor()
1013 : d(nullptr)
1014{
1015}
1016
1017/*!
1018 Constructs a cursor pointing to the beginning of the \a document.
1019 */
1020QTextCursor::QTextCursor(QTextDocument *document)
1021 : d(new QTextCursorPrivate(QTextDocumentPrivate::get(document)))
1022{
1023}
1024
1025/*!
1026 Constructs a cursor pointing to the beginning of the \a frame.
1027*/
1028QTextCursor::QTextCursor(QTextFrame *frame)
1029 : d(new QTextCursorPrivate(QTextDocumentPrivate::get(frame->document())))
1030{
1031 d->adjusted_anchor = d->anchor = d->position = frame->firstPosition();
1032}
1033
1034
1035/*!
1036 Constructs a cursor pointing to the beginning of the \a block.
1037*/
1038QTextCursor::QTextCursor(const QTextBlock &block)
1039 : d(new QTextCursorPrivate(const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(block))))
1040{
1041 d->adjusted_anchor = d->anchor = d->position = block.position();
1042}
1043
1044
1045/*!
1046 \internal
1047 */
1048QTextCursor::QTextCursor(QTextDocumentPrivate *p, int pos)
1049 : d(new QTextCursorPrivate(p))
1050{
1051 d->adjusted_anchor = d->anchor = d->position = pos;
1052
1053 d->setX();
1054}
1055
1056/*!
1057 \internal
1058*/
1059QTextCursor::QTextCursor(QTextCursorPrivate *d)
1060{
1061 Q_ASSERT(d);
1062 this->d = d;
1063}
1064
1065/*!
1066 Constructs a new cursor that is a copy of \a cursor.
1067 */
1068QTextCursor::QTextCursor(const QTextCursor &cursor)
1069{
1070 d = cursor.d;
1071}
1072
1073/*!
1074 Makes a copy of \a cursor and assigns it to this QTextCursor. Note
1075 that QTextCursor is an \l{Implicitly Shared Classes}{implicitly
1076 shared} class.
1077
1078 */
1079QTextCursor &QTextCursor::operator=(const QTextCursor &cursor)
1080{
1081 d = cursor.d;
1082 return *this;
1083}
1084
1085/*!
1086 \fn void QTextCursor::swap(QTextCursor &other)
1087 \since 5.0
1088 \memberswap{text cursor instance}
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("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(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(QTextFormat::ObjectType);
1391 insertText(text, 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(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(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 + 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(d->position, textStart + blockStart, blockEnd - blockStart, formatIdx);
1456
1457 d->insertBlock(blockFmt, format);
1458 blockStart = i + 1;
1459 }
1460 }
1461 if (textStart + blockStart < textEnd)
1462 d->priv->insert(d->position, textStart + blockStart, textEnd - textStart - blockStart, 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(d->position))
1486 return;
1487 d->adjusted_anchor = d->anchor =
1488 d->priv->nextCursorPosition(d->anchor, 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(d->anchor-1))
1510 return;
1511
1512 // For emoji sequences, backspace deletes the entire grapheme cluster,
1513 // since individual codepoints are meaningless to the user.
1514 // For other clusters (e.g. Devanagari consonant+vowel), we delete
1515 // only one codepoint at a time, matching the behavior of other major
1516 // text editors.
1517 const int clusterStart =
1518 d->priv->previousCursorPosition(d->anchor, QTextLayout::SkipCharacters);
1519 const int clusterCodeUnits = d->anchor - clusterStart;
1520
1521 if (clusterCodeUnits > 1) {
1522 // Multi-code-unit cluster: use QTextEngine's script analysis
1523 // to determine if this is an emoji sequence. The emoji segmenter
1524 // (run during itemization) tags emoji sequences with Script_Emoji.
1525 // Note: if Qt is built with QT_NO_EMOJISEGMENTER, Script_Emoji is
1526 // never assigned, and we fall through to the per-codepoint path.
1527 QTextBlock block = d->block();
1528 const int posInBlock = clusterStart - block.position();
1529 Q_ASSERT(posInBlock >= 0 && posInBlock < block.length());
1530
1531 QTextEngine *engine = d->blockLayout(block)->engine();
1532 const int itemIndex = engine->findItem(posInBlock);
1533
1534 if (itemIndex >= 0
1535 && engine->layoutData->items[itemIndex].analysis.script
1536 == QFontDatabasePrivate::Script_Emoji) {
1537 d->anchor = clusterStart;
1538 } else {
1539 --d->anchor;
1540 // Step over the high surrogate if we landed on a low surrogate
1541 const QStringView text = engine->layoutData->string;
1542 const int idx = d->anchor - block.position();
1543 if (idx > 0 && text.at(idx).isLowSurrogate()
1544 && text.at(idx - 1).isHighSurrogate()) {
1545 --d->anchor;
1546 }
1547 }
1548 } else {
1549 // Single code unit (ASCII/BMP): just step back one position
1550 --d->anchor;
1551 }
1552
1553 d->adjusted_anchor = d->anchor;
1554 d->remove();
1555 d->setX();
1556}
1557
1558/*!
1559 Selects text in the document according to the given \a selection.
1560*/
1561void QTextCursor::select(SelectionType selection)
1562{
1563 if (!d || !d->priv)
1564 return;
1565
1566 clearSelection();
1567
1568 const QTextBlock block = d->block();
1569
1570 switch (selection) {
1571 case LineUnderCursor:
1572 movePosition(StartOfLine);
1573 movePosition(EndOfLine, KeepAnchor);
1574 break;
1575 case WordUnderCursor:
1576 movePosition(StartOfWord);
1577 movePosition(EndOfWord, KeepAnchor);
1578 break;
1579 case BlockUnderCursor:
1580 if (block.length() == 1) // no content
1581 break;
1582 movePosition(StartOfBlock);
1583 // also select the paragraph separator
1584 if (movePosition(PreviousBlock)) {
1585 movePosition(EndOfBlock);
1586 movePosition(NextBlock, KeepAnchor);
1587 }
1588 movePosition(EndOfBlock, KeepAnchor);
1589 break;
1590 case Document:
1591 movePosition(Start);
1592 movePosition(End, KeepAnchor);
1593 break;
1594 }
1595}
1596
1597/*!
1598 Returns \c true if the cursor contains a selection; otherwise returns \c false.
1599*/
1600bool QTextCursor::hasSelection() const
1601{
1602 return !!d && d->position != d->anchor;
1603}
1604
1605
1606/*!
1607 Returns \c true if the cursor contains a selection that is not simply a
1608 range from selectionStart() to selectionEnd(); otherwise returns \c false.
1609
1610 Complex selections are ones that span at least two cells in a table;
1611 their extent is specified by selectedTableCells().
1612*/
1613bool QTextCursor::hasComplexSelection() const
1614{
1615 if (!d)
1616 return false;
1617
1618 return d->complexSelectionTable() != nullptr;
1619}
1620
1621/*!
1622 If the selection spans over table cells, \a firstRow is populated
1623 with the number of the first row in the selection, \a firstColumn
1624 with the number of the first column in the selection, and \a
1625 numRows and \a numColumns with the number of rows and columns in
1626 the selection. If the selection does not span any table cells the
1627 results are harmless but undefined.
1628*/
1629void QTextCursor::selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const
1630{
1631 *firstRow = -1;
1632 *firstColumn = -1;
1633 *numRows = -1;
1634 *numColumns = -1;
1635
1636 if (!d || d->position == d->anchor)
1637 return;
1638
1639 d->selectedTableCells(firstRow, numRows, firstColumn, numColumns);
1640}
1641
1642
1643/*!
1644 Clears the current selection by setting the anchor to the cursor position.
1645
1646 Note that it does \b{not} delete the text of the selection.
1647
1648 \sa removeSelectedText(), hasSelection()
1649*/
1650void QTextCursor::clearSelection()
1651{
1652 if (!d)
1653 return;
1654 d->adjusted_anchor = d->anchor = d->position;
1655 d->currentCharFormat = -1;
1656}
1657
1658/*!
1659 If there is a selection, its content is deleted; otherwise does
1660 nothing.
1661
1662 \sa hasSelection()
1663*/
1664void QTextCursor::removeSelectedText()
1665{
1666 if (!d || !d->priv || d->position == d->anchor)
1667 return;
1668
1669 d->priv->beginEditBlock();
1670 d->remove();
1671 d->priv->endEditBlock();
1672 d->setX();
1673}
1674
1675/*!
1676 Returns the start of the selection or position() if the
1677 cursor doesn't have a selection.
1678
1679 \sa selectionEnd(), position(), anchor()
1680*/
1681int QTextCursor::selectionStart() const
1682{
1683 if (!d || !d->priv)
1684 return -1;
1685 return qMin(d->position, d->adjusted_anchor);
1686}
1687
1688/*!
1689 Returns the end of the selection or position() if the cursor
1690 doesn't have a selection.
1691
1692 \sa selectionStart(), position(), anchor()
1693*/
1694int QTextCursor::selectionEnd() const
1695{
1696 if (!d || !d->priv)
1697 return -1;
1698 return qMax(d->position, d->adjusted_anchor);
1699}
1700
1701static void getText(QString &text, QTextDocumentPrivate *priv, const QString &docText, int pos, int end)
1702{
1703 while (pos < end) {
1704 QTextDocumentPrivate::FragmentIterator fragIt = priv->find(pos);
1705 const QTextFragmentData * const frag = fragIt.value();
1706
1707 const int offsetInFragment = qMax(0, pos - fragIt.position());
1708 const int len = qMin(int(frag->size_array[0] - offsetInFragment), end - pos);
1709
1710 text += QStringView(docText.constData() + frag->stringPosition + offsetInFragment, len);
1711 pos += len;
1712 }
1713}
1714
1715/*!
1716 Returns the current selection's text (which may be empty). This
1717 only returns the text, with no rich text formatting information.
1718 If you want a document fragment (i.e. formatted rich text) use
1719 selection() instead.
1720
1721 \note If the selection obtained from an editor spans a line break,
1722 the text will contain a Unicode U+2029 paragraph separator character
1723 instead of a newline \c{\n} character. Use QString::replace() to
1724 replace these characters with newlines.
1725*/
1726QString QTextCursor::selectedText() const
1727{
1728 if (!d || !d->priv || d->position == d->anchor)
1729 return QString();
1730
1731 const QString docText = d->priv->buffer();
1732 QString text;
1733
1734 QTextTable *table = d->complexSelectionTable();
1735 if (table) {
1736 int row_start, col_start, num_rows, num_cols;
1737 selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
1738
1739 Q_ASSERT(row_start != -1);
1740 for (int r = row_start; r < row_start + num_rows; ++r) {
1741 for (int c = col_start; c < col_start + num_cols; ++c) {
1742 QTextTableCell cell = table->cellAt(r, c);
1743 int rspan = cell.rowSpan();
1744 int cspan = cell.columnSpan();
1745 if (rspan != 1) {
1746 int cr = cell.row();
1747 if (cr != r)
1748 continue;
1749 }
1750 if (cspan != 1) {
1751 int cc = cell.column();
1752 if (cc != c)
1753 continue;
1754 }
1755
1756 getText(text, d->priv, docText, cell.firstPosition(), cell.lastPosition());
1757 }
1758 }
1759 } else {
1760 getText(text, d->priv, docText, selectionStart(), selectionEnd());
1761 }
1762
1763 return text;
1764}
1765
1766/*!
1767 Returns the current selection (which may be empty) with all its
1768 formatting information. If you just want the selected text (i.e.
1769 plain text) use selectedText() instead.
1770
1771 \note Unlike QTextDocumentFragment::toPlainText(),
1772 selectedText() may include special unicode characters such as
1773 QChar::ParagraphSeparator.
1774
1775 \sa QTextDocumentFragment::toPlainText()
1776*/
1777QTextDocumentFragment QTextCursor::selection() const
1778{
1779 return QTextDocumentFragment(*this);
1780}
1781
1782/*!
1783 Returns the block that contains the cursor.
1784*/
1785QTextBlock QTextCursor::block() const
1786{
1787 if (!d || !d->priv)
1788 return QTextBlock();
1789 return d->block();
1790}
1791
1792/*!
1793 Returns the block format of the block the cursor is in.
1794
1795 \sa setBlockFormat(), charFormat()
1796 */
1797QTextBlockFormat QTextCursor::blockFormat() const
1798{
1799 if (!d || !d->priv)
1800 return QTextBlockFormat();
1801
1802 return d->block().blockFormat();
1803}
1804
1805/*!
1806 Sets the block format of the current block (or all blocks that
1807 are contained in the selection) to \a format.
1808
1809 \sa blockFormat(), mergeBlockFormat()
1810*/
1811void QTextCursor::setBlockFormat(const QTextBlockFormat &format)
1812{
1813 if (!d || !d->priv)
1814 return;
1815
1816 d->setBlockFormat(format, QTextDocumentPrivate::SetFormat);
1817}
1818
1819/*!
1820 Modifies the block format of the current block (or all blocks that
1821 are contained in the selection) with the block format specified by
1822 \a modifier.
1823
1824 \sa setBlockFormat(), blockFormat()
1825*/
1826void QTextCursor::mergeBlockFormat(const QTextBlockFormat &modifier)
1827{
1828 if (!d || !d->priv)
1829 return;
1830
1831 d->setBlockFormat(modifier, QTextDocumentPrivate::MergeFormat);
1832}
1833
1834/*!
1835 Returns the block character format of the block the cursor is in.
1836
1837 The block char format is the format used when inserting text at the
1838 beginning of an empty block.
1839
1840 \sa setBlockCharFormat()
1841 */
1842QTextCharFormat QTextCursor::blockCharFormat() const
1843{
1844 if (!d || !d->priv)
1845 return QTextCharFormat();
1846
1847 return d->block().charFormat();
1848}
1849
1850/*!
1851 Sets the block char format of the current block (or all blocks that
1852 are contained in the selection) to \a format.
1853
1854 \sa blockCharFormat()
1855*/
1856void QTextCursor::setBlockCharFormat(const QTextCharFormat &format)
1857{
1858 if (!d || !d->priv)
1859 return;
1860
1861 d->setBlockCharFormat(format, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
1862}
1863
1864/*!
1865 Modifies the block char format of the current block (or all blocks that
1866 are contained in the selection) with the block format specified by
1867 \a modifier.
1868
1869 \sa setBlockCharFormat()
1870*/
1871void QTextCursor::mergeBlockCharFormat(const QTextCharFormat &modifier)
1872{
1873 if (!d || !d->priv)
1874 return;
1875
1876 d->setBlockCharFormat(modifier, QTextDocumentPrivate::MergeFormat);
1877}
1878
1879/*!
1880 Returns the format of the character immediately before the cursor
1881 position(). If the cursor is positioned at the beginning of a text
1882 block that is not empty then the format of the character
1883 immediately after the cursor is returned.
1884
1885 \sa insertText(), blockFormat()
1886 */
1887QTextCharFormat QTextCursor::charFormat() const
1888{
1889 if (!d || !d->priv)
1890 return QTextCharFormat();
1891
1892 int idx = d->currentCharFormat;
1893 if (idx == -1) {
1894 QTextBlock block = d->block();
1895
1896 int pos;
1897 if (d->position == block.position()
1898 && block.length() > 1)
1899 pos = d->position;
1900 else
1901 pos = d->position - 1;
1902
1903 if (pos == -1) {
1904 idx = d->priv->blockCharFormatIndex(d->priv->blockMap().firstNode());
1905 } else {
1906 Q_ASSERT(pos >= 0 && pos < d->priv->length());
1907
1908 QTextDocumentPrivate::FragmentIterator it = d->priv->find(pos);
1909 Q_ASSERT(!it.atEnd());
1910 idx = it.value()->format;
1911 }
1912 }
1913
1914 QTextCharFormat cfmt = d->priv->formatCollection()->charFormat(idx);
1915 cfmt.clearProperty(QTextFormat::ObjectIndex);
1916
1917 Q_ASSERT(cfmt.isValid());
1918 return cfmt;
1919}
1920
1921/*!
1922 Sets the cursor's current character format to the given \a
1923 format. If the cursor has a selection, the given \a format is
1924 applied to the current selection.
1925
1926 \sa hasSelection(), mergeCharFormat()
1927*/
1928void QTextCursor::setCharFormat(const QTextCharFormat &format)
1929{
1930 if (!d || !d->priv)
1931 return;
1932 if (d->position == d->anchor) {
1933 d->currentCharFormat = d->priv->formatCollection()->indexForFormat(format);
1934 return;
1935 }
1936 d->setCharFormat(format, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
1937}
1938
1939/*!
1940 Merges the cursor's current character format with the properties
1941 described by format \a modifier. If the cursor has a selection,
1942 this function applies all the properties set in \a modifier to all
1943 the character formats that are part of the selection.
1944
1945 \sa hasSelection(), setCharFormat()
1946*/
1947void QTextCursor::mergeCharFormat(const QTextCharFormat &modifier)
1948{
1949 if (!d || !d->priv)
1950 return;
1951 if (d->position == d->anchor) {
1952 QTextCharFormat format = charFormat();
1953 format.merge(modifier);
1954 d->currentCharFormat = d->priv->formatCollection()->indexForFormat(format);
1955 return;
1956 }
1957
1958 d->setCharFormat(modifier, QTextDocumentPrivate::MergeFormat);
1959}
1960
1961/*!
1962 Returns \c true if the cursor is at the start of a block; otherwise
1963 returns \c false.
1964
1965 \sa atBlockEnd(), atStart()
1966*/
1967bool QTextCursor::atBlockStart() const
1968{
1969 if (!d || !d->priv)
1970 return false;
1971
1972 return d->position == d->block().position();
1973}
1974
1975/*!
1976 Returns \c true if the cursor is at the end of a block; otherwise
1977 returns \c false.
1978
1979 \sa atBlockStart(), atEnd()
1980*/
1981bool QTextCursor::atBlockEnd() const
1982{
1983 if (!d || !d->priv)
1984 return false;
1985
1986 return d->position == d->block().position() + d->block().length() - 1;
1987}
1988
1989/*!
1990 Returns \c true if the cursor is at the start of the document;
1991 otherwise returns \c false.
1992
1993 \sa atBlockStart(), atEnd()
1994*/
1995bool QTextCursor::atStart() const
1996{
1997 if (!d || !d->priv)
1998 return false;
1999
2000 return d->position == 0;
2001}
2002
2003/*!
2004 \since 4.6
2005
2006 Returns \c true if the cursor is at the end of the document;
2007 otherwise returns \c false.
2008
2009 \sa atStart(), atBlockEnd()
2010*/
2011bool QTextCursor::atEnd() const
2012{
2013 if (!d || !d->priv)
2014 return false;
2015
2016 return d->position == d->priv->length() - 1;
2017}
2018
2019/*!
2020 Inserts a new empty block at the cursor position() with the
2021 current blockFormat() and charFormat().
2022
2023 \sa setBlockFormat()
2024*/
2025void QTextCursor::insertBlock()
2026{
2027 insertBlock(blockFormat());
2028}
2029
2030/*!
2031 \overload
2032
2033 Inserts a new empty block at the cursor position() with block
2034 format \a format and the current charFormat() as block char format.
2035
2036 \sa setBlockFormat()
2037*/
2038void QTextCursor::insertBlock(const QTextBlockFormat &format)
2039{
2040 QTextCharFormat charFmt = charFormat();
2041 charFmt.clearProperty(QTextFormat::ObjectType);
2042 insertBlock(format, charFmt);
2043}
2044
2045/*!
2046 \fn void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat)
2047 \overload
2048
2049 Inserts a new empty block at the cursor position() with block
2050 format \a format and \a charFormat as block char format.
2051
2052 \sa setBlockFormat()
2053*/
2054void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &_charFormat)
2055{
2056 if (!d || !d->priv)
2057 return;
2058
2059 QTextCharFormat charFormat = _charFormat;
2060 charFormat.clearProperty(QTextFormat::ObjectIndex);
2061
2062 d->priv->beginEditBlock();
2063 d->remove();
2064 d->insertBlock(format, charFormat);
2065 d->priv->endEditBlock();
2066 d->setX();
2067}
2068
2069/*!
2070 Inserts a new block at the current position and makes it the first
2071 list item of a newly created list with the given \a format. Returns
2072 the created list.
2073
2074 \sa currentList(), createList(), insertBlock()
2075 */
2076QTextList *QTextCursor::insertList(const QTextListFormat &format)
2077{
2078 insertBlock();
2079 return createList(format);
2080}
2081
2082/*!
2083 \overload
2084
2085 Inserts a new block at the current position and makes it the first
2086 list item of a newly created list with the given \a style. Returns
2087 the created list.
2088
2089 \sa currentList(), createList(), insertBlock()
2090 */
2091QTextList *QTextCursor::insertList(QTextListFormat::Style style)
2092{
2093 insertBlock();
2094 return createList(style);
2095}
2096
2097/*!
2098 Creates and returns a new list with the given \a format, and makes the
2099 current paragraph the cursor is in the first list item.
2100
2101 \sa insertList(), currentList()
2102 */
2103QTextList *QTextCursor::createList(const QTextListFormat &format)
2104{
2105 if (!d || !d->priv)
2106 return nullptr;
2107
2108 QTextList *list = static_cast<QTextList *>(d->priv->createObject(format));
2109 QTextBlockFormat modifier;
2110 modifier.setObjectIndex(list->objectIndex());
2111 mergeBlockFormat(modifier);
2112 return list;
2113}
2114
2115/*!
2116 \overload
2117
2118 Creates and returns a new list with the given \a style, making the
2119 cursor's current paragraph the first list item.
2120
2121 The style to be used is defined by the QTextListFormat::Style enum.
2122
2123 \sa insertList(), currentList()
2124 */
2125QTextList *QTextCursor::createList(QTextListFormat::Style style)
2126{
2127 QTextListFormat fmt;
2128 fmt.setStyle(style);
2129 return createList(fmt);
2130}
2131
2132/*!
2133 Returns the current list if the cursor position() is inside a
2134 block that is part of a list; otherwise returns \nullptr.
2135
2136 \sa insertList(), createList()
2137 */
2138QTextList *QTextCursor::currentList() const
2139{
2140 if (!d || !d->priv)
2141 return nullptr;
2142
2143 QTextBlockFormat b = blockFormat();
2144 QTextObject *o = d->priv->objectForFormat(b);
2145 return qobject_cast<QTextList *>(o);
2146}
2147
2148/*!
2149 \fn QTextTable *QTextCursor::insertTable(int rows, int columns)
2150
2151 \overload
2152
2153 Creates a new table with the given number of \a rows and \a columns,
2154 inserts it at the current cursor position() in the document, and returns
2155 the table object. The cursor is moved to the beginning of the first cell.
2156
2157 There must be at least one row and one column in the table.
2158
2159 \sa currentTable()
2160 */
2161QTextTable *QTextCursor::insertTable(int rows, int cols)
2162{
2163 return insertTable(rows, cols, QTextTableFormat());
2164}
2165
2166/*!
2167 \fn QTextTable *QTextCursor::insertTable(int rows, int columns, const QTextTableFormat &format)
2168
2169 Creates a new table with the given number of \a rows and \a columns
2170 in the specified \a format, inserts it at the current cursor position()
2171 in the document, and returns the table object. The cursor is moved to
2172 the beginning of the first cell.
2173
2174 There must be at least one row and one column in the table.
2175
2176 \sa currentTable()
2177*/
2178QTextTable *QTextCursor::insertTable(int rows, int cols, const QTextTableFormat &format)
2179{
2180 if (!d || !d->priv || rows == 0 || cols == 0)
2181 return nullptr;
2182
2183 int pos = d->position;
2184 QTextTable *t = QTextTablePrivate::createTable(d->priv, d->position, rows, cols, format);
2185 d->setPosition(pos+1);
2186 // ##### what should we do if we have a selection?
2187 d->anchor = d->position;
2188 d->adjusted_anchor = d->anchor;
2189 return t;
2190}
2191
2192/*!
2193 Returns a pointer to the current table if the cursor position()
2194 is inside a block that is part of a table; otherwise returns \nullptr.
2195
2196 \sa insertTable()
2197*/
2198QTextTable *QTextCursor::currentTable() const
2199{
2200 if (!d || !d->priv)
2201 return nullptr;
2202
2203 QTextFrame *frame = d->priv->frameAt(d->position);
2204 while (frame) {
2205 QTextTable *table = qobject_cast<QTextTable *>(frame);
2206 if (table)
2207 return table;
2208 frame = frame->parentFrame();
2209 }
2210 return nullptr;
2211}
2212
2213/*!
2214 Inserts a frame with the given \a format at the current cursor position(),
2215 moves the cursor position() inside the frame, and returns the frame.
2216
2217 If the cursor holds a selection, the whole selection is moved inside the
2218 frame.
2219
2220 \sa hasSelection()
2221*/
2222QTextFrame *QTextCursor::insertFrame(const QTextFrameFormat &format)
2223{
2224 if (!d || !d->priv)
2225 return nullptr;
2226
2227 return d->priv->insertFrame(selectionStart(), selectionEnd(), format);
2228}
2229
2230/*!
2231 Returns a pointer to the current frame. Returns \nullptr if the cursor is invalid.
2232
2233 \sa insertFrame()
2234*/
2235QTextFrame *QTextCursor::currentFrame() const
2236{
2237 if (!d || !d->priv)
2238 return nullptr;
2239
2240 return d->priv->frameAt(d->position);
2241}
2242
2243
2244/*!
2245 Inserts the text \a fragment at the current position().
2246*/
2247void QTextCursor::insertFragment(const QTextDocumentFragment &fragment)
2248{
2249 if (!d || !d->priv || fragment.isEmpty())
2250 return;
2251
2252 d->priv->beginEditBlock();
2253 d->remove();
2254 fragment.d->insert(*this);
2255 d->priv->endEditBlock();
2256 d->setX();
2257
2258 if (fragment.d && fragment.d->doc)
2259 d->priv->mergeCachedResources(QTextDocumentPrivate::get(fragment.d->doc));
2260}
2261
2262/*!
2263 \since 4.2
2264 Inserts the text \a html at the current position(). The text is interpreted as
2265 HTML.
2266
2267 \note When using this function with a style sheet, the style sheet will
2268 only apply to the current block in the document. In order to apply a style
2269 sheet throughout a document, use QTextDocument::setDefaultStyleSheet()
2270 instead.
2271*/
2272
2273#ifndef QT_NO_TEXTHTMLPARSER
2274
2275void QTextCursor::insertHtml(const QString &html)
2276{
2277 if (!d || !d->priv)
2278 return;
2279 QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(html, d->priv->document());
2280 insertFragment(fragment);
2281}
2282
2283#endif // QT_NO_TEXTHTMLPARSER
2284
2285/*!
2286 \since 6.4
2287 Inserts the \a markdown text at the current position(),
2288 with the specified Markdown \a features. The default is GitHub dialect.
2289*/
2290
2291#if QT_CONFIG(textmarkdownreader)
2292
2293void QTextCursor::insertMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
2294{
2295 if (!d || !d->priv)
2296 return;
2297 QTextDocumentFragment fragment = QTextDocumentFragment::fromMarkdown(markdown, features);
2298 if (markdown.startsWith(QLatin1Char('\n')))
2299 insertBlock(fragment.d->doc->firstBlock().blockFormat());
2300 insertFragment(fragment);
2301 if (!atEnd() && markdown.endsWith(QLatin1Char('\n')))
2302 insertText(QLatin1String("\n"));
2303}
2304
2305#endif // textmarkdownreader
2306
2307/*!
2308 \overload
2309 \since 4.2
2310
2311 Inserts the image defined by the given \a format at the cursor's current position
2312 with the specified \a alignment.
2313
2314 \sa position()
2315*/
2316void QTextCursor::insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment)
2317{
2318 if (!d || !d->priv)
2319 return;
2320
2321 QTextFrameFormat ffmt;
2322 ffmt.setPosition(alignment);
2323 QTextObject *obj = d->priv->createObject(ffmt);
2324
2325 QTextImageFormat fmt = format;
2326 fmt.setObjectIndex(obj->objectIndex());
2327
2328 d->priv->beginEditBlock();
2329 d->remove();
2330 const int idx = d->priv->formatCollection()->indexForFormat(fmt);
2331 d->priv->insert(d->position, QChar(QChar::ObjectReplacementCharacter), idx);
2332 d->priv->endEditBlock();
2333}
2334
2335/*!
2336 Inserts the image defined by \a format at the current position().
2337*/
2338void QTextCursor::insertImage(const QTextImageFormat &format)
2339{
2340 insertText(QString(QChar::ObjectReplacementCharacter), format);
2341}
2342
2343/*!
2344 \overload
2345
2346 Convenience method for inserting the image with the given \a name at the
2347 current position().
2348
2349 \snippet code/src_gui_text_qtextcursor.cpp 1
2350*/
2351void QTextCursor::insertImage(const QString &name)
2352{
2353 QTextImageFormat format;
2354 format.setName(name);
2355 insertImage(format);
2356}
2357
2358/*!
2359 \since 4.5
2360 \overload
2361
2362 Convenience function for inserting the given \a image with an optional
2363 \a name at the current position().
2364*/
2365void QTextCursor::insertImage(const QImage &image, const QString &name)
2366{
2367 if (image.isNull()) {
2368 qWarning("QTextCursor::insertImage: attempt to add an invalid image");
2369 return;
2370 }
2371 QString imageName = name;
2372 if (name.isEmpty())
2373 imageName = QString::number(image.cacheKey());
2374 d->priv->document()->addResource(QTextDocument::ImageResource, QUrl(imageName), image);
2375 QTextImageFormat format;
2376 format.setName(imageName);
2377 insertImage(format);
2378}
2379
2380/*!
2381 \fn bool QTextCursor::operator!=(const QTextCursor &other) const
2382
2383 Returns \c true if the \a other cursor is at a different position in
2384 the document as this cursor; otherwise returns \c false.
2385*/
2386bool QTextCursor::operator!=(const QTextCursor &rhs) const
2387{
2388 return !operator==(rhs);
2389}
2390
2391/*!
2392 \fn bool QTextCursor::operator<(const QTextCursor &other) const
2393
2394 Returns \c true if the \a other cursor is positioned later in the
2395 document than this cursor; otherwise returns \c false.
2396*/
2397bool QTextCursor::operator<(const QTextCursor &rhs) const
2398{
2399 if (!d)
2400 return !!rhs.d;
2401
2402 if (!rhs.d)
2403 return false;
2404
2405 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<", "cannot compare cursors attached to different documents");
2406
2407 return d->position < rhs.d->position;
2408}
2409
2410/*!
2411 \fn bool QTextCursor::operator<=(const QTextCursor &other) const
2412
2413 Returns \c true if the \a other cursor is positioned later or at the
2414 same position in the document as this cursor; otherwise returns
2415 false.
2416*/
2417bool QTextCursor::operator<=(const QTextCursor &rhs) const
2418{
2419 if (!d)
2420 return true;
2421
2422 if (!rhs.d)
2423 return false;
2424
2425 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<=", "cannot compare cursors attached to different documents");
2426
2427 return d->position <= rhs.d->position;
2428}
2429
2430/*!
2431 \fn bool QTextCursor::operator==(const QTextCursor &other) const
2432
2433 Returns \c true if the \a other cursor is at the same position in the
2434 document as this cursor; otherwise returns \c false.
2435*/
2436bool QTextCursor::operator==(const QTextCursor &rhs) const
2437{
2438 if (!d)
2439 return !rhs.d;
2440
2441 if (!rhs.d)
2442 return false;
2443
2444 return d->position == rhs.d->position && d->priv == rhs.d->priv;
2445}
2446
2447/*!
2448 \fn bool QTextCursor::operator>=(const QTextCursor &other) const
2449
2450 Returns \c true if the \a other cursor is positioned earlier or at the
2451 same position in the document as this cursor; otherwise returns
2452 false.
2453*/
2454bool QTextCursor::operator>=(const QTextCursor &rhs) const
2455{
2456 if (!d)
2457 return false;
2458
2459 if (!rhs.d)
2460 return true;
2461
2462 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>=", "cannot compare cursors attached to different documents");
2463
2464 return d->position >= rhs.d->position;
2465}
2466
2467/*!
2468 \fn bool QTextCursor::operator>(const QTextCursor &other) const
2469
2470 Returns \c true if the \a other cursor is positioned earlier in the
2471 document than this cursor; otherwise returns \c false.
2472*/
2473bool QTextCursor::operator>(const QTextCursor &rhs) const
2474{
2475 if (!d)
2476 return false;
2477
2478 if (!rhs.d)
2479 return true;
2480
2481 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>", "cannot compare cursors attached to different documents");
2482
2483 return d->position > rhs.d->position;
2484}
2485
2486/*!
2487 Indicates the start of a block of editing operations on the
2488 document that should appear as a single operation from an
2489 undo/redo point of view.
2490
2491 For example:
2492
2493 \snippet code/src_gui_text_qtextcursor.cpp 2
2494
2495 The call to undo() will cause both insertions to be undone,
2496 causing both "World" and "Hello" to be removed.
2497
2498 It is possible to nest calls to beginEditBlock and endEditBlock. The
2499 top-most pair will determine the scope of the undo/redo operation.
2500
2501 \sa endEditBlock()
2502 */
2503void QTextCursor::beginEditBlock()
2504{
2505 if (!d || !d->priv)
2506 return;
2507
2508 if (d->priv->editBlock == 0) // we are the initial edit block, store current cursor position for undo
2509 d->priv->editBlockCursorPosition = d->position;
2510
2511 d->priv->beginEditBlock();
2512}
2513
2514/*!
2515 Like beginEditBlock() indicates the start of a block of editing operations
2516 that should appear as a single operation for undo/redo. However unlike
2517 beginEditBlock() it does not start a new block but reverses the previous call to
2518 endEditBlock() and therefore makes following operations part of the previous edit block created.
2519
2520 For example:
2521
2522 \snippet code/src_gui_text_qtextcursor.cpp 3
2523
2524 The call to undo() will cause all three insertions to be undone.
2525
2526 \sa beginEditBlock(), endEditBlock()
2527 */
2528void QTextCursor::joinPreviousEditBlock()
2529{
2530 if (!d || !d->priv)
2531 return;
2532
2533 d->priv->joinPreviousEditBlock();
2534}
2535
2536/*!
2537 Indicates the end of a block of editing operations on the document
2538 that should appear as a single operation from an undo/redo point
2539 of view.
2540
2541 \sa beginEditBlock()
2542 */
2543
2544void QTextCursor::endEditBlock()
2545{
2546 if (!d || !d->priv)
2547 return;
2548
2549 d->priv->endEditBlock();
2550}
2551
2552/*!
2553 Returns \c true if this cursor and \a other are copies of each other, i.e.
2554 one of them was created as a copy of the other and neither has moved since.
2555 This is much stricter than equality.
2556
2557 \sa operator=(), operator==()
2558*/
2559bool QTextCursor::isCopyOf(const QTextCursor &other) const
2560{
2561 return d == other.d;
2562}
2563
2564/*!
2565 \since 4.2
2566 Returns the number of the block the cursor is in, or 0 if the cursor is invalid.
2567
2568 Note that this function only makes sense in documents without complex objects such
2569 as tables or frames.
2570*/
2571int QTextCursor::blockNumber() const
2572{
2573 if (!d || !d->priv)
2574 return 0;
2575
2576 return d->block().blockNumber();
2577}
2578
2579
2580/*!
2581 \since 4.2
2582 Returns the position of the cursor within its containing line.
2583
2584 Note that this is the column number relative to a wrapped line,
2585 not relative to the block (i.e. the paragraph).
2586
2587 You probably want to call positionInBlock() instead.
2588
2589 \sa positionInBlock()
2590*/
2591int QTextCursor::columnNumber() const
2592{
2593 if (!d || !d->priv)
2594 return 0;
2595
2596 QTextBlock block = d->block();
2597 if (!block.isValid())
2598 return 0;
2599
2600 const QTextLayout *layout = d->blockLayout(block);
2601
2602 const int relativePos = d->position - block.position();
2603
2604 if (layout->lineCount() == 0)
2605 return relativePos;
2606
2607 QTextLine line = layout->lineForTextPosition(relativePos);
2608 if (!line.isValid())
2609 return 0;
2610 return relativePos - line.textStart();
2611}
2612
2613/*!
2614 \since 4.5
2615 Returns the document this cursor is associated with.
2616*/
2617QTextDocument *QTextCursor::document() const
2618{
2619 if (d->priv)
2620 return d->priv->document();
2621 return nullptr; // document went away
2622}
2623
2624QT_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