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