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().
935
936 Cursor movements are limited to valid cursor positions. In Latin
937 writing this is between any two consecutive characters in the
938 text, before the first character, or after the last character. In
939 some other writing systems cursor movements are limited to
940 "clusters" (e.g. a syllable in Devanagari, or a base letter plus
941 diacritics). Functions such as movePosition() and deleteChar()
942 limit cursor movement to these valid positions.
943
944 \sa {Rich Text Processing}
945
946*/
947
948/*!
949 \enum QTextCursor::MoveOperation
950
951 \value NoMove Keep the cursor where it is
952
953 \value Start Move to the start of the document.
954 \value StartOfLine Move to the start of the current line.
955 \value StartOfBlock Move to the start of the current block.
956 \value StartOfWord Move to the start of the current word.
957 \value PreviousBlock Move to the start of the previous block.
958 \value PreviousCharacter Move to the previous character.
959 \value PreviousWord Move to the beginning of the previous word.
960 \value Up Move up one line.
961 \value Left Move left one character.
962 \value WordLeft Move left one word.
963
964 \value End Move to the end of the document.
965 \value EndOfLine Move to the end of the current line.
966 \value EndOfWord Move to the end of the current word.
967 \value EndOfBlock Move to the end of the current block.
968 \value NextBlock Move to the beginning of the next block.
969 \value NextCharacter Move to the next character. If text is selected, the first call
970 to \c{movePosition(NextCharacter, MoveAnchor)} deselects the text without moving the cursor.
971 \value NextWord Move to the next word.
972 \value Down Move down one line.
973 \value Right Move right one character.
974 \value WordRight Move right one word.
975
976 \value NextCell Move to the beginning of the next table cell inside the
977 current table. If the current cell is the last cell in the row, the
978 cursor will move to the first cell in the next row.
979 \value PreviousCell Move to the beginning of the previous table cell
980 inside the current table. If the current cell is the first cell in
981 the row, the cursor will move to the last cell in the previous row.
982 \value NextRow Move to the first new cell of the next row in the current
983 table.
984 \value PreviousRow Move to the last cell of the previous row in the
985 current table.
986
987 \sa movePosition()
988*/
989
990/*!
991 \enum QTextCursor::MoveMode
992
993 \value MoveAnchor Moves the anchor to the same position as the cursor itself.
994 \value KeepAnchor Keeps the anchor where it is.
995
996 If the anchor() is kept where it is and the position() is moved,
997 the text in between will be selected.
998*/
999
1000/*!
1001 \enum QTextCursor::SelectionType
1002
1003 This enum describes the types of selection that can be applied with the
1004 select() function.
1005
1006 \value Document Selects the entire document.
1007 \value BlockUnderCursor Selects the block of text under the cursor.
1008 \value LineUnderCursor Selects the line of text under the cursor.
1009 \value WordUnderCursor Selects the word under the cursor. If the cursor
1010 is not positioned within a string of selectable characters, no
1011 text is selected.
1012*/
1013
1014/*!
1015 Constructs a null cursor.
1016 */
1017QTextCursor::QTextCursor()
1018 : d(nullptr)
1019{
1020}
1021
1022/*!
1023 Constructs a cursor pointing to the beginning of the \a document.
1024 */
1025QTextCursor::QTextCursor(QTextDocument *document)
1026 : d(new QTextCursorPrivate(QTextDocumentPrivate::get(document)))
1027{
1028}
1029
1030/*!
1031 Constructs a cursor pointing to the beginning of the \a frame.
1032*/
1033QTextCursor::QTextCursor(QTextFrame *frame)
1034 : d(new QTextCursorPrivate(QTextDocumentPrivate::get(frame->document())))
1035{
1036 d->adjusted_anchor = d->anchor = d->position = frame->firstPosition();
1037}
1038
1039
1040/*!
1041 Constructs a cursor pointing to the beginning of the \a block.
1042*/
1043QTextCursor::QTextCursor(const QTextBlock &block)
1044 : d(new QTextCursorPrivate(const_cast<QTextDocumentPrivate *>(QTextDocumentPrivate::get(block))))
1045{
1046 d->adjusted_anchor = d->anchor = d->position = block.position();
1047}
1048
1049
1050/*!
1051 \internal
1052 */
1053QTextCursor::QTextCursor(QTextDocumentPrivate *p, int pos)
1054 : d(new QTextCursorPrivate(p))
1055{
1056 d->adjusted_anchor = d->anchor = d->position = pos;
1057
1058 d->setX();
1059}
1060
1061/*!
1062 \internal
1063*/
1064QTextCursor::QTextCursor(QTextCursorPrivate *d)
1065{
1066 Q_ASSERT(d);
1067 this->d = d;
1068}
1069
1070/*!
1071 Constructs a new cursor that is a copy of \a cursor.
1072 */
1073QTextCursor::QTextCursor(const QTextCursor &cursor)
1074{
1075 d = cursor.d;
1076}
1077
1078/*!
1079 Makes a copy of \a cursor and assigns it to this QTextCursor. Note
1080 that QTextCursor is an \l{Implicitly Shared Classes}{implicitly
1081 shared} class.
1082
1083 */
1084QTextCursor &QTextCursor::operator=(const QTextCursor &cursor)
1085{
1086 d = cursor.d;
1087 return *this;
1088}
1089
1090/*!
1091 \fn void QTextCursor::swap(QTextCursor &other)
1092 \since 5.0
1093 \memberswap{text cursor instance}
1094*/
1095
1096/*!
1097 Destroys the QTextCursor.
1098 */
1099QTextCursor::~QTextCursor()
1100{
1101}
1102
1103/*!
1104 Returns \c true if the cursor is null; otherwise returns \c false. A null
1105 cursor is created by the default constructor.
1106 */
1107bool QTextCursor::isNull() const
1108{
1109 return !d || !d->priv;
1110}
1111
1112/*!
1113 Moves the cursor to the absolute position in the document specified by
1114 \a pos using a \c MoveMode specified by \a m. The cursor is positioned
1115 between characters.
1116
1117 \note The "characters" in this case refer to the string of QChar
1118 objects, i.e. 16-bit Unicode characters, and \a pos is considered
1119 an index into this string. This does not necessarily correspond to
1120 individual graphemes in the writing system, as a single grapheme may
1121 be represented by multiple Unicode characters, such as in the case
1122 of surrogate pairs, linguistic ligatures or diacritics. For a more
1123 generic approach to navigating the document, use movePosition(),
1124 which will respect the actual grapheme boundaries in the text.
1125
1126 \sa position(), movePosition(), anchor()
1127*/
1128void QTextCursor::setPosition(int pos, MoveMode m)
1129{
1130 if (!d || !d->priv)
1131 return;
1132
1133 if (pos < 0 || pos >= d->priv->length()) {
1134 qWarning("QTextCursor::setPosition: Position '%d' out of range", pos);
1135 return;
1136 }
1137
1138 d->setPosition(pos);
1139 if (m == MoveAnchor) {
1140 d->anchor = pos;
1141 d->adjusted_anchor = pos;
1142 } else { // keep anchor
1143 QTextCursor::MoveOperation op;
1144 if (pos < d->anchor)
1145 op = QTextCursor::Left;
1146 else
1147 op = QTextCursor::Right;
1148 d->adjustCursor(op);
1149 }
1150 d->setX();
1151}
1152
1153/*!
1154 Returns the absolute position of the cursor within the document.
1155 The cursor is positioned between characters.
1156
1157 \note The "characters" in this case refer to the string of QChar
1158 objects, i.e. 16-bit Unicode characters, and the position is considered
1159 an index into this string. This does not necessarily correspond to
1160 individual graphemes in the writing system, as a single grapheme may
1161 be represented by multiple Unicode characters, such as in the case
1162 of surrogate pairs, linguistic ligatures or diacritics.
1163
1164 \sa setPosition(), movePosition(), anchor(), positionInBlock()
1165*/
1166int QTextCursor::position() const
1167{
1168 if (!d || !d->priv)
1169 return -1;
1170 return d->position;
1171}
1172
1173/*!
1174 \since 4.7
1175 Returns the relative position of the cursor within the block.
1176 The cursor is positioned between characters.
1177
1178 This is equivalent to \c{ position() - block().position()}.
1179
1180 \note The "characters" in this case refer to the string of QChar
1181 objects, i.e. 16-bit Unicode characters, and the position is considered
1182 an index into this string. This does not necessarily correspond to
1183 individual graphemes in the writing system, as a single grapheme may
1184 be represented by multiple Unicode characters, such as in the case
1185 of surrogate pairs, linguistic ligatures or diacritics.
1186
1187 \sa position()
1188*/
1189int QTextCursor::positionInBlock() const
1190{
1191 if (!d || !d->priv)
1192 return 0;
1193 return d->position - d->block().position();
1194}
1195
1196/*!
1197 Returns the anchor position; this is the same as position() unless
1198 there is a selection in which case position() marks one end of the
1199 selection and anchor() marks the other end. Just like the cursor
1200 position, the anchor position is between characters.
1201
1202 \sa position(), setPosition(), movePosition(), selectionStart(), selectionEnd()
1203*/
1204int QTextCursor::anchor() const
1205{
1206 if (!d || !d->priv)
1207 return -1;
1208 return d->anchor;
1209}
1210
1211/*!
1212 \fn bool QTextCursor::movePosition(MoveOperation operation, MoveMode mode, int n)
1213
1214 Moves the cursor by performing the given \a operation \a n times, using the specified
1215 \a mode, and returns \c true if all operations were completed successfully; otherwise
1216 returns \c false.
1217
1218 For example, if this function is repeatedly used to seek to the end of the next
1219 word, it will eventually fail when the end of the document is reached.
1220
1221 By default, the move operation is performed once (\a n = 1).
1222
1223 If \a mode is \c KeepAnchor, the cursor selects the text it moves
1224 over. This is the same effect that the user achieves when they
1225 hold down the Shift key and move the cursor with the cursor keys.
1226
1227 \sa setVisualNavigation()
1228*/
1229bool QTextCursor::movePosition(MoveOperation op, MoveMode mode, int n)
1230{
1231 if (!d || !d->priv)
1232 return false;
1233 switch (op) {
1234 case Start:
1235 case StartOfLine:
1236 case End:
1237 case EndOfLine:
1238 n = 1;
1239 break;
1240 default: break;
1241 }
1242
1243 int previousPosition = d->position;
1244 for (; n > 0; --n) {
1245 if (!d->movePosition(op, mode))
1246 return false;
1247 }
1248
1249 if (d->visualNavigation && !d->block().isVisible()) {
1250 QTextBlock b = d->block();
1251 if (previousPosition < d->position) {
1252 while (!b.next().isVisible())
1253 b = b.next();
1254 d->setPosition(b.position() + b.length() - 1);
1255 } else {
1256 while (!b.previous().isVisible())
1257 b = b.previous();
1258 d->setPosition(b.position());
1259 }
1260 if (mode == QTextCursor::MoveAnchor)
1261 d->anchor = d->position;
1262 while (d->movePosition(op, mode)
1263 && !d->block().isVisible())
1264 ;
1265
1266 }
1267 return true;
1268}
1269
1270/*!
1271 \since 4.4
1272
1273 Returns \c true if the cursor does visual navigation; otherwise
1274 returns \c false.
1275
1276 Visual navigation means skipping over hidden text paragraphs. The
1277 default is false.
1278
1279 \sa setVisualNavigation(), movePosition()
1280 */
1281bool QTextCursor::visualNavigation() const
1282{
1283 return d ? d->visualNavigation : false;
1284}
1285
1286/*!
1287 \since 4.4
1288
1289 Sets visual navigation to \a b.
1290
1291 Visual navigation means skipping over hidden text paragraphs. The
1292 default is false.
1293
1294 \sa visualNavigation(), movePosition()
1295 */
1296void QTextCursor::setVisualNavigation(bool b)
1297{
1298 if (d)
1299 d->visualNavigation = b;
1300}
1301
1302
1303/*!
1304 \since 4.7
1305
1306 Sets the visual x position for vertical cursor movements to \a x.
1307
1308 The vertical movement x position is cleared automatically when the cursor moves horizontally, and kept
1309 unchanged when the cursor moves vertically. The mechanism allows the cursor to move up and down on a
1310 visually straight line with proportional fonts, and to gently "jump" over short lines.
1311
1312 A value of -1 indicates no predefined x position. It will then be set automatically the next time the
1313 cursor moves up or down.
1314
1315 \sa verticalMovementX()
1316 */
1317void QTextCursor::setVerticalMovementX(int x)
1318{
1319 if (d)
1320 d->x = x;
1321}
1322
1323/*! \since 4.7
1324
1325 Returns the visual x position for vertical cursor movements.
1326
1327 A value of -1 indicates no predefined x position. It will then be set automatically the next time the
1328 cursor moves up or down.
1329
1330 \sa setVerticalMovementX()
1331 */
1332int QTextCursor::verticalMovementX() const
1333{
1334 return d ? d->x : -1;
1335}
1336
1337/*!
1338 \since 4.7
1339
1340 Returns whether the cursor should keep its current position when text gets inserted at the position of the
1341 cursor.
1342
1343 The default is false;
1344
1345 \sa setKeepPositionOnInsert()
1346 */
1347bool QTextCursor::keepPositionOnInsert() const
1348{
1349 return d ? d->keepPositionOnInsert : false;
1350}
1351
1352/*!
1353 \since 4.7
1354
1355 Defines whether the cursor should keep its current position when text gets inserted at the current position of the
1356 cursor.
1357
1358 If \a b is true, the cursor keeps its current position when text gets inserted at the positing of the cursor.
1359 If \a b is false, the cursor moves along with the inserted text.
1360
1361 The default is false.
1362
1363 Note that a cursor always moves when text is inserted before the current position of the cursor, and it
1364 always keeps its position when text is inserted after the current position of the cursor.
1365
1366 \sa keepPositionOnInsert()
1367 */
1368void QTextCursor::setKeepPositionOnInsert(bool b)
1369{
1370 if (d)
1371 d->keepPositionOnInsert = b;
1372}
1373
1374
1375
1376/*!
1377 Inserts \a text at the current position, using the current
1378 character format.
1379
1380 If there is a selection, the selection is deleted and replaced by
1381 \a text, for example:
1382 \snippet code/src_gui_text_qtextcursor.cpp 0
1383 This clears any existing selection, selects the word at the cursor
1384 (i.e. from position() forward), and replaces the selection with
1385 the phrase "Hello World".
1386
1387 Any ASCII linefeed characters (\\n) in the inserted text are transformed
1388 into unicode block separators, corresponding to insertBlock() calls.
1389
1390 \sa charFormat(), hasSelection()
1391*/
1392void QTextCursor::insertText(const QString &text)
1393{
1394 QTextCharFormat fmt = charFormat();
1395 fmt.clearProperty(QTextFormat::ObjectType);
1396 insertText(text, fmt);
1397}
1398
1399/*!
1400 \fn void QTextCursor::insertText(const QString &text, const QTextCharFormat &format)
1401 \overload
1402
1403 Inserts \a text at the current position with the given \a format.
1404*/
1405void QTextCursor::insertText(const QString &text, const QTextCharFormat &_format)
1406{
1407 if (!d || !d->priv)
1408 return;
1409
1410 Q_ASSERT(_format.isValid());
1411
1412 QTextCharFormat format = _format;
1413 format.clearProperty(QTextFormat::ObjectIndex);
1414
1415 bool hasEditBlock = false;
1416
1417 if (d->anchor != d->position) {
1418 hasEditBlock = true;
1419 d->priv->beginEditBlock();
1420 d->remove();
1421 }
1422
1423 if (!text.isEmpty()) {
1424 QTextFormatCollection *formats = d->priv->formatCollection();
1425 int formatIdx = formats->indexForFormat(format);
1426 Q_ASSERT(formats->format(formatIdx).isCharFormat());
1427
1428 QTextBlockFormat blockFmt = blockFormat();
1429
1430
1431 int textStart = d->priv->text.size();
1432 int blockStart = 0;
1433 d->priv->text += text;
1434 int textEnd = d->priv->text.size();
1435
1436 for (int i = 0; i < text.size(); ++i) {
1437 QChar ch = text.at(i);
1438
1439 const int blockEnd = i;
1440
1441 if (ch == u'\r'
1442 && (i + 1) < text.size()
1443 && text.at(i + 1) == u'\n') {
1444 ++i;
1445 ch = text.at(i);
1446 }
1447
1448 if (ch == u'\n'
1449 || ch == QChar::ParagraphSeparator
1450 || ch == QTextBeginningOfFrame
1451 || ch == QTextEndOfFrame
1452 || ch == u'\r') {
1453
1454 if (!hasEditBlock) {
1455 hasEditBlock = true;
1456 d->priv->beginEditBlock();
1457 }
1458
1459 if (blockEnd > blockStart)
1460 d->priv->insert(d->position, textStart + blockStart, blockEnd - blockStart, formatIdx);
1461
1462 d->insertBlock(blockFmt, format);
1463 blockStart = i + 1;
1464 }
1465 }
1466 if (textStart + blockStart < textEnd)
1467 d->priv->insert(d->position, textStart + blockStart, textEnd - textStart - blockStart, formatIdx);
1468 }
1469 if (hasEditBlock)
1470 d->priv->endEditBlock();
1471 d->setX();
1472}
1473
1474/*!
1475 If there is no selected text, deletes the character \e at the
1476 current cursor position; otherwise deletes the selected text.
1477
1478 \sa deletePreviousChar(), hasSelection(), clearSelection()
1479*/
1480void QTextCursor::deleteChar()
1481{
1482 if (!d || !d->priv)
1483 return;
1484
1485 if (d->position != d->anchor) {
1486 removeSelectedText();
1487 return;
1488 }
1489
1490 if (!d->canDelete(d->position))
1491 return;
1492 d->adjusted_anchor = d->anchor =
1493 d->priv->nextCursorPosition(d->anchor, QTextLayout::SkipCharacters);
1494 d->remove();
1495 d->setX();
1496}
1497
1498/*!
1499 If there is no selected text, deletes the character \e before the
1500 current cursor position; otherwise deletes the selected text.
1501
1502 \sa deleteChar(), hasSelection(), clearSelection()
1503*/
1504void QTextCursor::deletePreviousChar()
1505{
1506 if (!d || !d->priv)
1507 return;
1508
1509 if (d->position != d->anchor) {
1510 removeSelectedText();
1511 return;
1512 }
1513
1514 if (d->anchor < 1 || !d->canDelete(d->anchor-1))
1515 return;
1516
1517 // For emoji sequences, backspace deletes the entire grapheme cluster,
1518 // since individual codepoints are meaningless to the user.
1519 // For other clusters (e.g. Devanagari consonant+vowel), we delete
1520 // only one codepoint at a time, matching the behavior of other major
1521 // text editors.
1522 const int clusterStart =
1523 d->priv->previousCursorPosition(d->anchor, QTextLayout::SkipCharacters);
1524 const int clusterCodeUnits = d->anchor - clusterStart;
1525
1526 if (clusterCodeUnits > 1) {
1527 // Multi-code-unit cluster: use QTextEngine's script analysis
1528 // to determine if this is an emoji sequence. The emoji segmenter
1529 // (run during itemization) tags emoji sequences with Script_Emoji.
1530 // Note: if Qt is built with QT_NO_EMOJISEGMENTER, Script_Emoji is
1531 // never assigned, and we fall through to the per-codepoint path.
1532 QTextBlock block = d->block();
1533 const int posInBlock = clusterStart - block.position();
1534 Q_ASSERT(posInBlock >= 0 && posInBlock < block.length());
1535
1536 QTextEngine *engine = d->blockLayout(block)->engine();
1537 const int itemIndex = engine->findItem(posInBlock);
1538
1539 if (itemIndex >= 0
1540 && engine->layoutData->items[itemIndex].analysis.script
1541 == QFontDatabasePrivate::Script_Emoji) {
1542 d->anchor = clusterStart;
1543 } else {
1544 --d->anchor;
1545 // Step over the high surrogate if we landed on a low surrogate
1546 const QStringView text = engine->layoutData->string;
1547 const int idx = d->anchor - block.position();
1548 if (idx > 0 && text.at(idx).isLowSurrogate()
1549 && text.at(idx - 1).isHighSurrogate()) {
1550 --d->anchor;
1551 }
1552 }
1553 } else {
1554 // Single code unit (ASCII/BMP): just step back one position
1555 --d->anchor;
1556 }
1557
1558 d->adjusted_anchor = d->anchor;
1559 d->remove();
1560 d->setX();
1561}
1562
1563/*!
1564 Selects text in the document according to the given \a selection.
1565*/
1566void QTextCursor::select(SelectionType selection)
1567{
1568 if (!d || !d->priv)
1569 return;
1570
1571 clearSelection();
1572
1573 const QTextBlock block = d->block();
1574
1575 switch (selection) {
1576 case LineUnderCursor:
1577 movePosition(StartOfLine);
1578 movePosition(EndOfLine, KeepAnchor);
1579 break;
1580 case WordUnderCursor:
1581 movePosition(StartOfWord);
1582 movePosition(EndOfWord, KeepAnchor);
1583 break;
1584 case BlockUnderCursor:
1585 if (block.length() == 1) // no content
1586 break;
1587 movePosition(StartOfBlock);
1588 // also select the paragraph separator
1589 if (movePosition(PreviousBlock)) {
1590 movePosition(EndOfBlock);
1591 movePosition(NextBlock, KeepAnchor);
1592 }
1593 movePosition(EndOfBlock, KeepAnchor);
1594 break;
1595 case Document:
1596 movePosition(Start);
1597 movePosition(End, KeepAnchor);
1598 break;
1599 }
1600}
1601
1602/*!
1603 Returns \c true if the cursor contains a selection; otherwise returns \c false.
1604*/
1605bool QTextCursor::hasSelection() const
1606{
1607 return !!d && d->position != d->anchor;
1608}
1609
1610
1611/*!
1612 Returns \c true if the cursor contains a selection that is not simply a
1613 range from selectionStart() to selectionEnd(); otherwise returns \c false.
1614
1615 Complex selections are ones that span at least two cells in a table;
1616 their extent is specified by selectedTableCells().
1617*/
1618bool QTextCursor::hasComplexSelection() const
1619{
1620 if (!d)
1621 return false;
1622
1623 return d->complexSelectionTable() != nullptr;
1624}
1625
1626/*!
1627 If the selection spans over table cells, \a firstRow is populated
1628 with the number of the first row in the selection, \a firstColumn
1629 with the number of the first column in the selection, and \a
1630 numRows and \a numColumns with the number of rows and columns in
1631 the selection. If the selection does not span any table cells the
1632 results are harmless but undefined.
1633*/
1634void QTextCursor::selectedTableCells(int *firstRow, int *numRows, int *firstColumn, int *numColumns) const
1635{
1636 *firstRow = -1;
1637 *firstColumn = -1;
1638 *numRows = -1;
1639 *numColumns = -1;
1640
1641 if (!d || d->position == d->anchor)
1642 return;
1643
1644 d->selectedTableCells(firstRow, numRows, firstColumn, numColumns);
1645}
1646
1647
1648/*!
1649 Clears the current selection by setting the anchor to the cursor position.
1650
1651 Note that it does \b{not} delete the text of the selection.
1652
1653 \sa removeSelectedText(), hasSelection()
1654*/
1655void QTextCursor::clearSelection()
1656{
1657 if (!d)
1658 return;
1659 d->adjusted_anchor = d->anchor = d->position;
1660 d->currentCharFormat = -1;
1661}
1662
1663/*!
1664 If there is a selection, its content is deleted; otherwise does
1665 nothing.
1666
1667 \sa hasSelection()
1668*/
1669void QTextCursor::removeSelectedText()
1670{
1671 if (!d || !d->priv || d->position == d->anchor)
1672 return;
1673
1674 d->priv->beginEditBlock();
1675 d->remove();
1676 d->priv->endEditBlock();
1677 d->setX();
1678}
1679
1680/*!
1681 Returns the start of the selection or position() if the
1682 cursor doesn't have a selection.
1683
1684 \sa selectionEnd(), position(), anchor()
1685*/
1686int QTextCursor::selectionStart() const
1687{
1688 if (!d || !d->priv)
1689 return -1;
1690 return qMin(d->position, d->adjusted_anchor);
1691}
1692
1693/*!
1694 Returns the end of the selection or position() if the cursor
1695 doesn't have a selection.
1696
1697 \sa selectionStart(), position(), anchor()
1698*/
1699int QTextCursor::selectionEnd() const
1700{
1701 if (!d || !d->priv)
1702 return -1;
1703 return qMax(d->position, d->adjusted_anchor);
1704}
1705
1706static void getText(QString &text, QTextDocumentPrivate *priv, const QString &docText, int pos, int end)
1707{
1708 while (pos < end) {
1709 QTextDocumentPrivate::FragmentIterator fragIt = priv->find(pos);
1710 const QTextFragmentData * const frag = fragIt.value();
1711
1712 const int offsetInFragment = qMax(0, pos - fragIt.position());
1713 const int len = qMin(int(frag->size_array[0] - offsetInFragment), end - pos);
1714
1715 text += QStringView(docText.constData() + frag->stringPosition + offsetInFragment, len);
1716 pos += len;
1717 }
1718}
1719
1720/*!
1721 Returns the current selection's text (which may be empty). This
1722 only returns the text, with no rich text formatting information.
1723 If you want a document fragment (i.e. formatted rich text) use
1724 selection() instead.
1725
1726 \note If the selection obtained from an editor spans a line break,
1727 the text will contain a Unicode U+2029 paragraph separator character
1728 instead of a newline \c{\n} character. Use QString::replace() to
1729 replace these characters with newlines.
1730*/
1731QString QTextCursor::selectedText() const
1732{
1733 if (!d || !d->priv || d->position == d->anchor)
1734 return QString();
1735
1736 const QString docText = d->priv->buffer();
1737 QString text;
1738
1739 QTextTable *table = d->complexSelectionTable();
1740 if (table) {
1741 int row_start, col_start, num_rows, num_cols;
1742 selectedTableCells(&row_start, &num_rows, &col_start, &num_cols);
1743
1744 Q_ASSERT(row_start != -1);
1745 for (int r = row_start; r < row_start + num_rows; ++r) {
1746 for (int c = col_start; c < col_start + num_cols; ++c) {
1747 QTextTableCell cell = table->cellAt(r, c);
1748 int rspan = cell.rowSpan();
1749 int cspan = cell.columnSpan();
1750 if (rspan != 1) {
1751 int cr = cell.row();
1752 if (cr != r)
1753 continue;
1754 }
1755 if (cspan != 1) {
1756 int cc = cell.column();
1757 if (cc != c)
1758 continue;
1759 }
1760
1761 getText(text, d->priv, docText, cell.firstPosition(), cell.lastPosition());
1762 }
1763 }
1764 } else {
1765 getText(text, d->priv, docText, selectionStart(), selectionEnd());
1766 }
1767
1768 return text;
1769}
1770
1771/*!
1772 Returns the current selection (which may be empty) with all its
1773 formatting information. If you just want the selected text (i.e.
1774 plain text) use selectedText() instead.
1775
1776 \note Unlike QTextDocumentFragment::toPlainText(),
1777 selectedText() may include special unicode characters such as
1778 QChar::ParagraphSeparator.
1779
1780 \sa QTextDocumentFragment::toPlainText()
1781*/
1782QTextDocumentFragment QTextCursor::selection() const
1783{
1784 return QTextDocumentFragment(*this);
1785}
1786
1787/*!
1788 Returns the block that contains the cursor.
1789*/
1790QTextBlock QTextCursor::block() const
1791{
1792 if (!d || !d->priv)
1793 return QTextBlock();
1794 return d->block();
1795}
1796
1797/*!
1798 Returns the block format of the block the cursor is in.
1799
1800 \sa setBlockFormat(), charFormat()
1801 */
1802QTextBlockFormat QTextCursor::blockFormat() const
1803{
1804 if (!d || !d->priv)
1805 return QTextBlockFormat();
1806
1807 return d->block().blockFormat();
1808}
1809
1810/*!
1811 Sets the block format of the current block (or all blocks that
1812 are contained in the selection) to \a format.
1813
1814 \sa blockFormat(), mergeBlockFormat()
1815*/
1816void QTextCursor::setBlockFormat(const QTextBlockFormat &format)
1817{
1818 if (!d || !d->priv)
1819 return;
1820
1821 d->setBlockFormat(format, QTextDocumentPrivate::SetFormat);
1822}
1823
1824/*!
1825 Modifies the block format of the current block (or all blocks that
1826 are contained in the selection) with the block format specified by
1827 \a modifier.
1828
1829 \sa setBlockFormat(), blockFormat()
1830*/
1831void QTextCursor::mergeBlockFormat(const QTextBlockFormat &modifier)
1832{
1833 if (!d || !d->priv)
1834 return;
1835
1836 d->setBlockFormat(modifier, QTextDocumentPrivate::MergeFormat);
1837}
1838
1839/*!
1840 Returns the block character format of the block the cursor is in.
1841
1842 The block char format is the format used when inserting text at the
1843 beginning of an empty block.
1844
1845 \sa setBlockCharFormat()
1846 */
1847QTextCharFormat QTextCursor::blockCharFormat() const
1848{
1849 if (!d || !d->priv)
1850 return QTextCharFormat();
1851
1852 return d->block().charFormat();
1853}
1854
1855/*!
1856 Sets the block char format of the current block (or all blocks that
1857 are contained in the selection) to \a format.
1858
1859 \sa blockCharFormat()
1860*/
1861void QTextCursor::setBlockCharFormat(const QTextCharFormat &format)
1862{
1863 if (!d || !d->priv)
1864 return;
1865
1866 d->setBlockCharFormat(format, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
1867}
1868
1869/*!
1870 Modifies the block char format of the current block (or all blocks that
1871 are contained in the selection) with the block format specified by
1872 \a modifier.
1873
1874 \sa setBlockCharFormat()
1875*/
1876void QTextCursor::mergeBlockCharFormat(const QTextCharFormat &modifier)
1877{
1878 if (!d || !d->priv)
1879 return;
1880
1881 d->setBlockCharFormat(modifier, QTextDocumentPrivate::MergeFormat);
1882}
1883
1884/*!
1885 Returns the format of the character immediately before the cursor
1886 position(). If the cursor is positioned at the beginning of a text
1887 block that is not empty then the format of the character
1888 immediately after the cursor is returned.
1889
1890 \sa insertText(), blockFormat()
1891 */
1892QTextCharFormat QTextCursor::charFormat() const
1893{
1894 if (!d || !d->priv)
1895 return QTextCharFormat();
1896
1897 int idx = d->currentCharFormat;
1898 if (idx == -1) {
1899 QTextBlock block = d->block();
1900
1901 int pos;
1902 if (d->position == block.position()
1903 && block.length() > 1)
1904 pos = d->position;
1905 else
1906 pos = d->position - 1;
1907
1908 if (pos == -1) {
1909 idx = d->priv->blockCharFormatIndex(d->priv->blockMap().firstNode());
1910 } else {
1911 Q_ASSERT(pos >= 0 && pos < d->priv->length());
1912
1913 QTextDocumentPrivate::FragmentIterator it = d->priv->find(pos);
1914 Q_ASSERT(!it.atEnd());
1915 idx = it.value()->format;
1916 }
1917 }
1918
1919 QTextCharFormat cfmt = d->priv->formatCollection()->charFormat(idx);
1920 cfmt.clearProperty(QTextFormat::ObjectIndex);
1921
1922 Q_ASSERT(cfmt.isValid());
1923 return cfmt;
1924}
1925
1926/*!
1927 Sets the cursor's current character format to the given \a
1928 format. If the cursor has a selection, the given \a format is
1929 applied to the current selection.
1930
1931 \sa hasSelection(), mergeCharFormat()
1932*/
1933void QTextCursor::setCharFormat(const QTextCharFormat &format)
1934{
1935 if (!d || !d->priv)
1936 return;
1937 if (d->position == d->anchor) {
1938 d->currentCharFormat = d->priv->formatCollection()->indexForFormat(format);
1939 return;
1940 }
1941 d->setCharFormat(format, QTextDocumentPrivate::SetFormatAndPreserveObjectIndices);
1942}
1943
1944/*!
1945 Merges the cursor's current character format with the properties
1946 described by format \a modifier. If the cursor has a selection,
1947 this function applies all the properties set in \a modifier to all
1948 the character formats that are part of the selection.
1949
1950 \sa hasSelection(), setCharFormat()
1951*/
1952void QTextCursor::mergeCharFormat(const QTextCharFormat &modifier)
1953{
1954 if (!d || !d->priv)
1955 return;
1956 if (d->position == d->anchor) {
1957 QTextCharFormat format = charFormat();
1958 format.merge(modifier);
1959 d->currentCharFormat = d->priv->formatCollection()->indexForFormat(format);
1960 return;
1961 }
1962
1963 d->setCharFormat(modifier, QTextDocumentPrivate::MergeFormat);
1964}
1965
1966/*!
1967 Returns \c true if the cursor is at the start of a block; otherwise
1968 returns \c false.
1969
1970 \sa atBlockEnd(), atStart()
1971*/
1972bool QTextCursor::atBlockStart() const
1973{
1974 if (!d || !d->priv)
1975 return false;
1976
1977 return d->position == d->block().position();
1978}
1979
1980/*!
1981 Returns \c true if the cursor is at the end of a block; otherwise
1982 returns \c false.
1983
1984 \sa atBlockStart(), atEnd()
1985*/
1986bool QTextCursor::atBlockEnd() const
1987{
1988 if (!d || !d->priv)
1989 return false;
1990
1991 return d->position == d->block().position() + d->block().length() - 1;
1992}
1993
1994/*!
1995 Returns \c true if the cursor is at the start of the document;
1996 otherwise returns \c false.
1997
1998 \sa atBlockStart(), atEnd()
1999*/
2000bool QTextCursor::atStart() const
2001{
2002 if (!d || !d->priv)
2003 return false;
2004
2005 return d->position == 0;
2006}
2007
2008/*!
2009 \since 4.6
2010
2011 Returns \c true if the cursor is at the end of the document;
2012 otherwise returns \c false.
2013
2014 \sa atStart(), atBlockEnd()
2015*/
2016bool QTextCursor::atEnd() const
2017{
2018 if (!d || !d->priv)
2019 return false;
2020
2021 return d->position == d->priv->length() - 1;
2022}
2023
2024/*!
2025 Inserts a new empty block at the cursor position() with the
2026 current blockFormat() and charFormat().
2027
2028 \sa setBlockFormat()
2029*/
2030void QTextCursor::insertBlock()
2031{
2032 insertBlock(blockFormat());
2033}
2034
2035/*!
2036 \overload
2037
2038 Inserts a new empty block at the cursor position() with block
2039 format \a format and the current charFormat() as block char format.
2040
2041 \sa setBlockFormat()
2042*/
2043void QTextCursor::insertBlock(const QTextBlockFormat &format)
2044{
2045 QTextCharFormat charFmt = charFormat();
2046 charFmt.clearProperty(QTextFormat::ObjectType);
2047 insertBlock(format, charFmt);
2048}
2049
2050/*!
2051 \fn void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &charFormat)
2052 \overload
2053
2054 Inserts a new empty block at the cursor position() with block
2055 format \a format and \a charFormat as block char format.
2056
2057 \sa setBlockFormat()
2058*/
2059void QTextCursor::insertBlock(const QTextBlockFormat &format, const QTextCharFormat &_charFormat)
2060{
2061 if (!d || !d->priv)
2062 return;
2063
2064 QTextCharFormat charFormat = _charFormat;
2065 charFormat.clearProperty(QTextFormat::ObjectIndex);
2066
2067 d->priv->beginEditBlock();
2068 d->remove();
2069 d->insertBlock(format, charFormat);
2070 d->priv->endEditBlock();
2071 d->setX();
2072}
2073
2074/*!
2075 Inserts a new block at the current position and makes it the first
2076 list item of a newly created list with the given \a format. Returns
2077 the created list.
2078
2079 \sa currentList(), createList(), insertBlock()
2080 */
2081QTextList *QTextCursor::insertList(const QTextListFormat &format)
2082{
2083 insertBlock();
2084 return createList(format);
2085}
2086
2087/*!
2088 \overload
2089
2090 Inserts a new block at the current position and makes it the first
2091 list item of a newly created list with the given \a style. Returns
2092 the created list.
2093
2094 \sa currentList(), createList(), insertBlock()
2095 */
2096QTextList *QTextCursor::insertList(QTextListFormat::Style style)
2097{
2098 insertBlock();
2099 return createList(style);
2100}
2101
2102/*!
2103 Creates and returns a new list with the given \a format, and makes the
2104 current paragraph the cursor is in the first list item.
2105
2106 \sa insertList(), currentList()
2107 */
2108QTextList *QTextCursor::createList(const QTextListFormat &format)
2109{
2110 if (!d || !d->priv)
2111 return nullptr;
2112
2113 QTextList *list = static_cast<QTextList *>(d->priv->createObject(format));
2114 QTextBlockFormat modifier;
2115 modifier.setObjectIndex(list->objectIndex());
2116 mergeBlockFormat(modifier);
2117 return list;
2118}
2119
2120/*!
2121 \overload
2122
2123 Creates and returns a new list with the given \a style, making the
2124 cursor's current paragraph the first list item.
2125
2126 The style to be used is defined by the QTextListFormat::Style enum.
2127
2128 \sa insertList(), currentList()
2129 */
2130QTextList *QTextCursor::createList(QTextListFormat::Style style)
2131{
2132 QTextListFormat fmt;
2133 fmt.setStyle(style);
2134 return createList(fmt);
2135}
2136
2137/*!
2138 Returns the current list if the cursor position() is inside a
2139 block that is part of a list; otherwise returns \nullptr.
2140
2141 \sa insertList(), createList()
2142 */
2143QTextList *QTextCursor::currentList() const
2144{
2145 if (!d || !d->priv)
2146 return nullptr;
2147
2148 QTextBlockFormat b = blockFormat();
2149 QTextObject *o = d->priv->objectForFormat(b);
2150 return qobject_cast<QTextList *>(o);
2151}
2152
2153/*!
2154 \fn QTextTable *QTextCursor::insertTable(int rows, int columns)
2155
2156 \overload
2157
2158 Creates a new table with the given number of \a rows and \a columns,
2159 inserts it at the current cursor position() in the document, and returns
2160 the table object. The cursor is moved to the beginning of the first cell.
2161
2162 There must be at least one row and one column in the table.
2163
2164 \sa currentTable()
2165 */
2166QTextTable *QTextCursor::insertTable(int rows, int cols)
2167{
2168 return insertTable(rows, cols, QTextTableFormat());
2169}
2170
2171/*!
2172 \fn QTextTable *QTextCursor::insertTable(int rows, int columns, const QTextTableFormat &format)
2173
2174 Creates a new table with the given number of \a rows and \a columns
2175 in the specified \a format, inserts it at the current cursor position()
2176 in the document, and returns the table object. The cursor is moved to
2177 the beginning of the first cell.
2178
2179 There must be at least one row and one column in the table.
2180
2181 \sa currentTable()
2182*/
2183QTextTable *QTextCursor::insertTable(int rows, int cols, const QTextTableFormat &format)
2184{
2185 if (!d || !d->priv || rows == 0 || cols == 0)
2186 return nullptr;
2187
2188 int pos = d->position;
2189 QTextTable *t = QTextTablePrivate::createTable(d->priv, d->position, rows, cols, format);
2190 d->setPosition(pos+1);
2191 // ##### what should we do if we have a selection?
2192 d->anchor = d->position;
2193 d->adjusted_anchor = d->anchor;
2194 return t;
2195}
2196
2197/*!
2198 Returns a pointer to the current table if the cursor position()
2199 is inside a block that is part of a table; otherwise returns \nullptr.
2200
2201 \sa insertTable()
2202*/
2203QTextTable *QTextCursor::currentTable() const
2204{
2205 if (!d || !d->priv)
2206 return nullptr;
2207
2208 QTextFrame *frame = d->priv->frameAt(d->position);
2209 while (frame) {
2210 QTextTable *table = qobject_cast<QTextTable *>(frame);
2211 if (table)
2212 return table;
2213 frame = frame->parentFrame();
2214 }
2215 return nullptr;
2216}
2217
2218/*!
2219 Inserts a frame with the given \a format at the current cursor position(),
2220 moves the cursor position() inside the frame, and returns the frame.
2221
2222 If the cursor holds a selection, the whole selection is moved inside the
2223 frame.
2224
2225 \sa hasSelection()
2226*/
2227QTextFrame *QTextCursor::insertFrame(const QTextFrameFormat &format)
2228{
2229 if (!d || !d->priv)
2230 return nullptr;
2231
2232 return d->priv->insertFrame(selectionStart(), selectionEnd(), format);
2233}
2234
2235/*!
2236 Returns a pointer to the current frame. Returns \nullptr if the cursor is invalid.
2237
2238 \sa insertFrame()
2239*/
2240QTextFrame *QTextCursor::currentFrame() const
2241{
2242 if (!d || !d->priv)
2243 return nullptr;
2244
2245 return d->priv->frameAt(d->position);
2246}
2247
2248
2249/*!
2250 Inserts the text \a fragment at the current position().
2251*/
2252void QTextCursor::insertFragment(const QTextDocumentFragment &fragment)
2253{
2254 if (!d || !d->priv || fragment.isEmpty())
2255 return;
2256
2257 d->priv->beginEditBlock();
2258 d->remove();
2259 fragment.d->insert(*this);
2260 d->priv->endEditBlock();
2261 d->setX();
2262
2263 if (fragment.d && fragment.d->doc)
2264 d->priv->mergeCachedResources(QTextDocumentPrivate::get(fragment.d->doc));
2265}
2266
2267/*!
2268 \since 4.2
2269 Inserts the text \a html at the current position(). The text is interpreted as
2270 HTML.
2271
2272 \note When using this function with a style sheet, the style sheet will
2273 only apply to the current block in the document. In order to apply a style
2274 sheet throughout a document, use QTextDocument::setDefaultStyleSheet()
2275 instead.
2276*/
2277
2278#ifndef QT_NO_TEXTHTMLPARSER
2279
2280void QTextCursor::insertHtml(const QString &html)
2281{
2282 if (!d || !d->priv)
2283 return;
2284 QTextDocumentFragment fragment = QTextDocumentFragment::fromHtml(html, d->priv->document());
2285 insertFragment(fragment);
2286}
2287
2288#endif // QT_NO_TEXTHTMLPARSER
2289
2290/*!
2291 \since 6.4
2292 Inserts the \a markdown text at the current position(),
2293 with the specified Markdown \a features. The default is GitHub dialect.
2294*/
2295
2296#if QT_CONFIG(textmarkdownreader)
2297
2298void QTextCursor::insertMarkdown(const QString &markdown, QTextDocument::MarkdownFeatures features)
2299{
2300 if (!d || !d->priv)
2301 return;
2302 QTextDocumentFragment fragment = QTextDocumentFragment::fromMarkdown(markdown, features);
2303 if (markdown.startsWith(QLatin1Char('\n')))
2304 insertBlock(fragment.d->doc->firstBlock().blockFormat());
2305 insertFragment(fragment);
2306 if (!atEnd() && markdown.endsWith(QLatin1Char('\n')))
2307 insertText(QLatin1String("\n"));
2308}
2309
2310#endif // textmarkdownreader
2311
2312/*!
2313 \overload
2314 \since 4.2
2315
2316 Inserts the image defined by the given \a format at the cursor's current position
2317 with the specified \a alignment.
2318
2319 \sa position()
2320*/
2321void QTextCursor::insertImage(const QTextImageFormat &format, QTextFrameFormat::Position alignment)
2322{
2323 if (!d || !d->priv)
2324 return;
2325
2326 QTextFrameFormat ffmt;
2327 ffmt.setPosition(alignment);
2328 QTextObject *obj = d->priv->createObject(ffmt);
2329
2330 QTextImageFormat fmt = format;
2331 fmt.setObjectIndex(obj->objectIndex());
2332
2333 d->priv->beginEditBlock();
2334 d->remove();
2335 const int idx = d->priv->formatCollection()->indexForFormat(fmt);
2336 d->priv->insert(d->position, QChar(QChar::ObjectReplacementCharacter), idx);
2337 d->priv->endEditBlock();
2338}
2339
2340/*!
2341 Inserts the image defined by \a format at the current position().
2342*/
2343void QTextCursor::insertImage(const QTextImageFormat &format)
2344{
2345 insertText(QString(QChar::ObjectReplacementCharacter), format);
2346}
2347
2348/*!
2349 \overload
2350
2351 Convenience method for inserting the image with the given \a name at the
2352 current position().
2353
2354 \snippet code/src_gui_text_qtextcursor.cpp 1
2355*/
2356void QTextCursor::insertImage(const QString &name)
2357{
2358 QTextImageFormat format;
2359 format.setName(name);
2360 insertImage(format);
2361}
2362
2363/*!
2364 \since 4.5
2365 \overload
2366
2367 Convenience function for inserting the given \a image with an optional
2368 \a name at the current position().
2369*/
2370void QTextCursor::insertImage(const QImage &image, const QString &name)
2371{
2372 if (image.isNull()) {
2373 qWarning("QTextCursor::insertImage: attempt to add an invalid image");
2374 return;
2375 }
2376 QString imageName = name;
2377 if (name.isEmpty())
2378 imageName = QString::number(image.cacheKey());
2379 d->priv->document()->addResource(QTextDocument::ImageResource, QUrl(imageName), image);
2380 QTextImageFormat format;
2381 format.setName(imageName);
2382 insertImage(format);
2383}
2384
2385/*!
2386 \fn bool QTextCursor::operator!=(const QTextCursor &other) const
2387
2388 Returns \c true if the \a other cursor is at a different position in
2389 the document as this cursor; otherwise returns \c false.
2390*/
2391bool QTextCursor::operator!=(const QTextCursor &rhs) const
2392{
2393 return !operator==(rhs);
2394}
2395
2396/*!
2397 \fn bool QTextCursor::operator<(const QTextCursor &other) const
2398
2399 Returns \c true if the \a other cursor is positioned later in the
2400 document than this cursor; otherwise returns \c false.
2401*/
2402bool QTextCursor::operator<(const QTextCursor &rhs) const
2403{
2404 if (!d)
2405 return !!rhs.d;
2406
2407 if (!rhs.d)
2408 return false;
2409
2410 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<", "cannot compare cursors attached to different documents");
2411
2412 return d->position < rhs.d->position;
2413}
2414
2415/*!
2416 \fn bool QTextCursor::operator<=(const QTextCursor &other) const
2417
2418 Returns \c true if the \a other cursor is positioned later or at the
2419 same position in the document as this cursor; otherwise returns
2420 false.
2421*/
2422bool QTextCursor::operator<=(const QTextCursor &rhs) const
2423{
2424 if (!d)
2425 return true;
2426
2427 if (!rhs.d)
2428 return false;
2429
2430 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator<=", "cannot compare cursors attached to different documents");
2431
2432 return d->position <= rhs.d->position;
2433}
2434
2435/*!
2436 \fn bool QTextCursor::operator==(const QTextCursor &other) const
2437
2438 Returns \c true if the \a other cursor is at the same position in the
2439 document as this cursor; otherwise returns \c false.
2440*/
2441bool QTextCursor::operator==(const QTextCursor &rhs) const
2442{
2443 if (!d)
2444 return !rhs.d;
2445
2446 if (!rhs.d)
2447 return false;
2448
2449 return d->position == rhs.d->position && d->priv == rhs.d->priv;
2450}
2451
2452/*!
2453 \fn bool QTextCursor::operator>=(const QTextCursor &other) const
2454
2455 Returns \c true if the \a other cursor is positioned earlier or at the
2456 same position in the document as this cursor; otherwise returns
2457 false.
2458*/
2459bool QTextCursor::operator>=(const QTextCursor &rhs) const
2460{
2461 if (!d)
2462 return false;
2463
2464 if (!rhs.d)
2465 return true;
2466
2467 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>=", "cannot compare cursors attached to different documents");
2468
2469 return d->position >= rhs.d->position;
2470}
2471
2472/*!
2473 \fn bool QTextCursor::operator>(const QTextCursor &other) const
2474
2475 Returns \c true if the \a other cursor is positioned earlier in the
2476 document than this cursor; otherwise returns \c false.
2477*/
2478bool QTextCursor::operator>(const QTextCursor &rhs) const
2479{
2480 if (!d)
2481 return false;
2482
2483 if (!rhs.d)
2484 return true;
2485
2486 Q_ASSERT_X(d->priv == rhs.d->priv, "QTextCursor::operator>", "cannot compare cursors attached to different documents");
2487
2488 return d->position > rhs.d->position;
2489}
2490
2491/*!
2492 Indicates the start of a block of editing operations on the
2493 document that should appear as a single operation from an
2494 undo/redo point of view.
2495
2496 For example:
2497
2498 \snippet code/src_gui_text_qtextcursor.cpp 2
2499
2500 The call to undo() will cause both insertions to be undone,
2501 causing both "World" and "Hello" to be removed.
2502
2503 It is possible to nest calls to beginEditBlock and endEditBlock. The
2504 top-most pair will determine the scope of the undo/redo operation.
2505
2506 \sa endEditBlock()
2507 */
2508void QTextCursor::beginEditBlock()
2509{
2510 if (!d || !d->priv)
2511 return;
2512
2513 if (d->priv->editBlock == 0) // we are the initial edit block, store current cursor position for undo
2514 d->priv->editBlockCursorPosition = d->position;
2515
2516 d->priv->beginEditBlock();
2517}
2518
2519/*!
2520 Like beginEditBlock() indicates the start of a block of editing operations
2521 that should appear as a single operation for undo/redo. However unlike
2522 beginEditBlock() it does not start a new block but reverses the previous call to
2523 endEditBlock() and therefore makes following operations part of the previous edit block created.
2524
2525 For example:
2526
2527 \snippet code/src_gui_text_qtextcursor.cpp 3
2528
2529 The call to undo() will cause all three insertions to be undone.
2530
2531 \sa beginEditBlock(), endEditBlock()
2532 */
2533void QTextCursor::joinPreviousEditBlock()
2534{
2535 if (!d || !d->priv)
2536 return;
2537
2538 d->priv->joinPreviousEditBlock();
2539}
2540
2541/*!
2542 Indicates the end of a block of editing operations on the document
2543 that should appear as a single operation from an undo/redo point
2544 of view.
2545
2546 \sa beginEditBlock()
2547 */
2548
2549void QTextCursor::endEditBlock()
2550{
2551 if (!d || !d->priv)
2552 return;
2553
2554 d->priv->endEditBlock();
2555}
2556
2557/*!
2558 Returns \c true if this cursor and \a other are copies of each other, i.e.
2559 one of them was created as a copy of the other and neither has moved since.
2560 This is much stricter than equality.
2561
2562 \sa operator=(), operator==()
2563*/
2564bool QTextCursor::isCopyOf(const QTextCursor &other) const
2565{
2566 return d == other.d;
2567}
2568
2569/*!
2570 \since 4.2
2571 Returns the number of the block the cursor is in, or 0 if the cursor is invalid.
2572
2573 Note that this function only makes sense in documents without complex objects such
2574 as tables or frames.
2575*/
2576int QTextCursor::blockNumber() const
2577{
2578 if (!d || !d->priv)
2579 return 0;
2580
2581 return d->block().blockNumber();
2582}
2583
2584
2585/*!
2586 \since 4.2
2587 Returns the position of the cursor within its containing line.
2588
2589 Note that this is the column number relative to a wrapped line,
2590 not relative to the block (i.e. the paragraph).
2591
2592 You probably want to call positionInBlock() instead.
2593
2594 \sa positionInBlock()
2595*/
2596int QTextCursor::columnNumber() const
2597{
2598 if (!d || !d->priv)
2599 return 0;
2600
2601 QTextBlock block = d->block();
2602 if (!block.isValid())
2603 return 0;
2604
2605 const QTextLayout *layout = d->blockLayout(block);
2606
2607 const int relativePos = d->position - block.position();
2608
2609 if (layout->lineCount() == 0)
2610 return relativePos;
2611
2612 QTextLine line = layout->lineForTextPosition(relativePos);
2613 if (!line.isValid())
2614 return 0;
2615 return relativePos - line.textStart();
2616}
2617
2618/*!
2619 \since 4.5
2620 Returns the document this cursor is associated with.
2621*/
2622QTextDocument *QTextCursor::document() const
2623{
2624 if (d->priv)
2625 return d->priv->document();
2626 return nullptr; // document went away
2627}
2628
2629QT_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