Coverage Report

Created: 2026-01-25 07:18

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