Home · Examples 


The QTextCursor Interface

[Previous: Rich Text Document Structure][Rich Text Processing][Next: Common Rich Text Editing Tasks]

The QTextCursor interface allows documents and their structure to be edited in a way that should be familiar to most users of text editors and document editing software. Rich text documents can have multiple cursors associated with them, and each of these contains information about their position in the document and any selections that they may hold. This cursor-based paradigm makes common operations, such as cutting and pasting text, simple to implement programmatically, yet it also allows more complex editing operations to be performed on the document.

This chapter describes most of the common editing operations that you will need to perform using a cursor, from basic insertion of text and document elements to more complex manipulation of document structures.

Cursor-Based Editing

At the simplest level, text documents are made up of a string of characters, marked up in some way to represent the block structure of the text within the document. QTextCursor provides a cursor-based interface that allows the contents of a QTextDocument to be manipulated at the character level. Since the elements (blocks, frames, tables, etc.) are also encoded in the character stream, the document structure can itself be changed by the cursor.

The cursor keeps track of its location within its parent document, and can report information about the surrounding structure, such as the enclosing text block, frame, table, or list. The formats of the enclosing structures can also be directly obtained through the cursor.

Using a Cursor

The main use of a cursor is to insert or modify text within a block. We can use a text editor's cursor to do this:
        QTextEdit editor = new QTextEdit();
        QTextCursor cursor = new QTextCursor(editor.textCursor());
    
Alternatively, we can obtain a cursor directly from a document:
        QTextDocument document = new QTextDocument(editor);
        QTextCursor cursor = new QTextCursor(document);
    
The cursor is positioned at the start of the document so that we can write into the first (empty) block in the document.

Grouping Cursor Operations

A series of editing operations can be packaged together so that they can be replayed, or undone together in a single action. This is achieved by using the beginEditBlock() and endEditBlock() functions in the following way, as in the following example where we select the word that contains the cursor:
    cursor.beginEditBlock();

    cursor.movePosition(QTextCursor.MoveOperation.StartOfWord);
    cursor.movePosition(QTextCursor.MoveOperation.EndOfWord,
                        QTextCursor.MoveMode.KeepAnchor);

    cursor.endEditBlock();
If editing operations are not grouped, the document automatically records the individual operations so that they can be undone later. Grouping operations into larger packages can make editing more efficient both for the user and for the application, but care has to be taken not to group too many operations together as the user may want find-grained control over the undo process.

Multiple Cursors

Multiple cursors can be used to simultaneously edit the same document, although only one will be visible to the user in a
QTextEdit widget. The QTextDocument ensures that each cursor writes text correctly and does not interfere with any of the others.

Inserting Document Elements

QTextCursor provides several functions that can be used to change the structure of a rich text document. Generally, these functions allow document elements to be created with relevant formatting information, and they are inserted into the document at the cursor's position.

The first group of functions insert block-level elements, and update the cursor position, but they do not return the element that was inserted:

You can examine the contents of the element that was inserted through the cursor interface.

The second group of functions insert elements that provide structure to the document, and return the structure that was inserted:

These elements either contain or group together other elements in the document.

Text and Text Fragments

Text can be inserted into the current block in the current character format, or in a custom format that is specified with the text:
        cursor.insertText(tr("Character formats"),
                          headingFormat);

        cursor.insertBlock();

        cursor.insertText(tr("Text can be displayed in a variety of " +
                             "different character formats. "), plainFormat);
        cursor.insertText(tr("We can emphasize text by "));
        cursor.insertText(tr("making it italic"), emphasisFormat);
    
Once the character format has been used with a cursor, that format becomes the default format for any text inserted with that cursor until another character format is specified.

If a cursor is used to insert text without specifying a character format, the text will be given the character format used at that position in the document.

Blocks

Text blocks are inserted into the document with the insertBlock() function.
        QTextBlockFormat backgroundFormat = blockFormat;
        backgroundFormat.setBackground(new QBrush(new QColor("lightGray")));

        cursor.setBlockFormat(backgroundFormat);
    
The cursor is positioned at the start of the new block.

Frames

Frames are inserted into a document using the cursor, and will be placed within the cursor's current frame after the current block. The following code shows how a frame can be inserted between two text blocks in a document's root frame. We begin by finding the cursor's current frame:
        QTextFrame mainFrame = cursor.currentFrame();
        cursor.insertText("...");
    
We insert some text in this frame then set up a frame format for the child frame:
        QTextFrameFormat frameFormat = new QTextFrameFormat();
        frameFormat.setMargin(32);
        frameFormat.setPadding(8);
        frameFormat.setBorder(4);
    
The frame format will give the frame an external margin of 32 pixels, internal padding of 8 pixels, and a border that is 4 pixels wide. See the
QTextFrameFormat documentation for more information about frame formats.

The frame is inserted into the document after the preceding text:

        cursor.insertFrame(frameFormat);
        cursor.insertText("...");
    
We add some text to the document immediately after we insert the frame. Since the text cursor is positioned inside the frame when it is inserted into the document, this text will also be inserted inside the frame.

Finally, we position the cursor outside the frame by taking the last available cursor position inside the frame we recorded earlier:

        cursor = mainFrame.lastCursorPosition();
        cursor.insertText("...");
    
The text that we add last is inserted after the child frame in the document. Since each frame is padded with text blocks, this ensures that more elements can always be inserted with a cursor.

Tables

Tables are inserted into the document using the cursor, and will be placed within the cursor's current frame after the current block:
 
        QTextCursor cursor = new QTextCursor(editor.textCursor());
    
        QTextTable table = cursor.insertTable(rows, columns, tableFormat);
     
Tables can be created with a specific format that defines the overall properties of the table, such as its alignment, background color, and the cell spacing used. It can also determine the constraints on each column, allowing each of them to have a fixed width, or resize according to the available space.
        QTextTableFormat tableFormat = new QTextTableFormat();
        tableFormat.setBackground(new QColor("#e0e0e0"));
        List<QTextLength> raints = new Vector<QTextLength>();

       raints.add(new QTextLength(QTextLength.Type.PercentageLength, 16));
       raints.add(new QTextLength(QTextLength.Type.PercentageLength, 28));
       raints.add(new QTextLength(QTextLength.Type.PercentageLength, 28));
       raints.add(new QTextLength(QTextLength.Type.PercentageLength, 28));
       tableFormat.setColumnWidthConstraints(raints);
    
        QTextTable table = cursor.insertTable(rows, columns, tableFormat);
    
The columns in the table created above will each take up a certain percentage of the available width. Note that the table format is optional; if you insert a table without a format, some sensible default values will be used for the table's properties.

Since cells can contain other document elements, they too can be formatted and styled as necessary.

Text can be added to the table by navigating to each cell with the cursor and inserting text.

        cell = table.cellAt(0, 0);
        cellCursor = cell.firstCursorPosition();
        cellCursor.insertText(tr("Week"), charFormat);
    
We can create a simple timetable by following this approach:
        for (column = 1; column < columns; ++column) {
            cell = table.cellAt(0, column);
            cellCursor = cell.firstCursorPosition();
            cellCursor.insertText(tr("Team") + String.valueOf(column), charFormat);
        }

        for (row = 1; row < rows; ++row) {
            cell = table.cellAt(row, 0);
            cellCursor = cell.firstCursorPosition();
            cellCursor.insertText(String.valueOf(row), charFormat);

            for (column = 1; column < columns; ++column) {
                if ((row - 1) % 3 == column - 1) {
    
                    cell = table.cellAt(row, column);
                    QTextCursor cellCursor = cell.firstCursorPosition();
                    cellCursor.insertText(tr("On duty"), charFormat);
                }
    
            }
    
        }
    

Lists

Lists of block elements can be automatically created and inserted into the document at the current cursor position. Each list that is created in this way requires a list format to be specified:
    QTextListFormat listFormat = new QTextListFormat();
    if (list != null) {
        listFormat = list.format();
        listFormat.setIndent(listFormat.indent() + 1);
    }

    listFormat.setStyle(QTextListFormat.Style.ListDisc);
    cursor.insertList(listFormat);
The above code first checks whether the cursor is within an existing list and, if so, gives the list format for the new list a suitable level of indentation. This allows nested lists to be created with increasing levels of indentation. A more sophisticated implementation would also use different kinds of symbol for the bullet points in each level of the list.

Images

Inline images are added to documents through the cursor in the usual manner. Unlike many other elements, all of the image properties are specified by the image's format. This means that a
QTextImageFormat object has to be created before an image can be inserted:
        QTextImageFormat imageFormat = new QTextImageFormat();
        imageFormat.setName("classpath:images/advert.png");
        cursor.insertImage(imageFormat);
    
The image name refers to an entry in the application's resource file. The method used to derive this name is described in The Qt Resource System.


Copyright © 2009 Nokia Corporation and/or its subsidiary(-ies) Trademarks
Qt Jambi 4.5.2_01