Home | All Classes | Main Classes | Annotated | Grouped Classes | Functions |

# Rich Text Processing

- Overview
- Document Structure

- Text Blocks
- Frames
- Tables
- Lists
- Images

- Cursor-based editing

- Using a Cursor
- Grouping Cursor Operations
- Multiple Cursors

- Common Tasks

- Using QTextEdit
- Selecting Text
- Finding Text

See also the list of Text Related Classes.

## Overview

The Scribe framework provides a set of classes for reading and manipulating structured textual documents. Unlike previous rich text support in Qt, the new classes are centered around the `QTextDocument` class rather than raw text information. This enables the developer to create and modify structured rich text documents without having to prepare content in an intermediate markup format.

The information within a document can be accessed via two complementary interfaces: a cursor-based interface for editing, and a structure-based interface for easy navigation that provides a high level overview of the document. The main advantage of this approach is that the text can be edited using operations that mimic the user's interaction with an editor without losing the underlying structure of the document.

## Document Structure

The structured representation of a text document presents its contents as a hierarchy of text blocks, frames, tables, and other objects. These provide both logical structure to the document, and indicate how their contents are to be displayed. Generally, frames and tables are used to group other structures, and text blocks contain the actual textual information.

| Basic structure: The "top level" of a document might be populated in the way shown. Each document always contains a root frame, and this contains always contains at least one text block. For documents with some textual content, the root frame usually contains a sequence of blocks and other elements. Sequences of frames and tables are always separated by text blocks in a document, even if the text blocks contain no information. This ensures that new elements can always be inserted between existing structures.

New elements are created and inserted into the document using a `QTextCursor`, or via an editor such as `QTextEdit`. Elements can be given a particular format when they are created; otherwise they take the cursor's current format for the element.

### Text Blocks

Text blocks typically contain a number of text fragments, each displayed using a different character format. Fragments are created when text is inserted into the document, and more are added when the document is edited. The document splits, merges, and removes fragments in order to efficiently represent the different styles of text in the block.

The block itself can have its own character format that can be used for drawing block-level decoration, such as the bullet points found alongside list items. The formatting for the block itself is described by the `QTextBlockFormat` class, and describes properties such as text alignment, indentation, and background color:

            QTextBlockFormat backgroundFormat = blockFormat;
            backgroundFormat.setBackgroundColor(QColor("#dddddd"));

            cursor.setBlockFormat(backgroundFormat);

The fragments within the block can be read with an iterator:

            QTextBlock::iterator it;
            for (it = currentBlock.begin(); !(it.atEnd()); ++it) {

                QTextFragment fragment = it.fragment();
                if (fragment.isValid()) {
                    ...
                }
            }

Although the document may contain complex structures, we can always extract just the text blocks in the order in which they are written.

            QTextBlock currentBlock = textDocument->begin();

            while (currentBlock.isValid()) {
                ...
                currentBlock = currentBlock.next();
            }

This method is useful for when you want to extract just the rich text from a document.

### Frames

Text frames group together blocks of text, providing document structures that are larger than paragraphs in the document. The format of a frame specifies how it is rendered and positioned on the page. Frames are either inserted into the text flow, or they float to the left or right hand side of the page.

| Inserting a frame: The following code shows how a frame can be inserted between two text blocks in a document's root frame:
        cursor.insertText(...);
            QTextFrame *mainFrame = cursor.currentFrame();

            QTextFrameFormat frameFormat;
            frameFormat.setMargin(32);
            frameFormat.setPadding(8);
            frameFormat.setBorder(4);
            cursor.insertFrame(frameFormat);
        cursor.insertText(...);
            cursor = mainFrame->lastCursorPosition();
        cursor.insertText(...);

### Tables

Tables are collections of cells that are arranged in rows and columns in the usual way. Cells can contain other elements, such as frames and text blocks, and are constructed by the document in the same way as other structures:

        QTextTable *table = cursor.insertTable(rows, columns);

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;
            QVector<QTextLength> constraints;
            constraints << QTextLength(QTextLength::PercentageLength, 16);
            constraints << QTextLength(QTextLength::PercentageLength, 28);
            constraints << QTextLength(QTextLength::PercentageLength, 28);
            constraints << QTextLength(QTextLength::PercentageLength, 28);
            tableFormat.setColumnWidthConstraints(constraints);
            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.

Table cells are automatically created when the table is constructed or when extra rows or columns are added. 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"));

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 %1").arg(column));
            }

            for (row = 1; row < rows; ++row) {
                cell = table->cellAt(row, 0);
                cellCursor = cell.firstCursorPosition();
                cellCursor.insertText(tr("%1").arg(row));

                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"));
                    }
                }
            }

Since cells contain other regular elements, they too can be formatted and styled to give the document an appropriate appearance.

### Lists

Lists are sequences of text blocks that are formatted in the usual way, but which also provide the standard list decorations, such as bullet points and enumerated items in various styles. Lists can be nested, and will be indented if the list's format specifies a non-zero indentation.

            QTextListFormat listFormat;
            listFormat.setStyle(QTextListFormat::ListDisc);
            listFormat.setIndent(cursor.blockFormat().indent() + 1);
            cursor.createList(listFormat);

By increasing the indentation of the new list relative to the indentation of the current block, we can create nested lists. 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;
            imageFormat.setName(":/images/advert.png");
            cursor.insertImage(imageFormat);

The image name refers to an entry in the application's resource file. How this name is derived is beyond the scope of this document.

## 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, or we can obtain a cursor directly from a document:

            QTextDocument *document = new QTextDocument;
            QTextCursor cursor(document);

The cursor is positioned at the start of the document so that we can write into the first (empty) block in the document. 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(QObject::tr("Character formats"),
                              headingFormat);

            cursor.insertBlock();

            cursor.insertText(QObject::tr("Text can be displayed in a variety of "
                                          "different character formats. "), plainFormat);
            cursor.insertText(QObject::tr("We can emphasize text by "));
            cursor.insertText(QObject::tr("making it italic"), emphasisFormat);

Once the character format has been used, that format becomes the current format and will be used for any following text inserted into the document until another character format is specified.

### 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:

            cursor.beginEditBlock();
            cursor.movePosition(QTextCursor::StartOfWord);
            cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
            cursor.endEditBlock();

### 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.

            QTextDocument *document = editor->document();
            QTextCursor redCursor(document);
            QTextCursor blueCursor(document);

## Common Tasks

There are a number of tasks that are often performed by developers when editing and processing text documents using Qt. These include the use of display widgets such as `QTextBrowser` and `QTextEdit`, creation of documents with `QTextDocument`, editing using a `QTextCursor`, and exporting the document structure. This document outlines some of the more common ways of using the Scribe classes to perform these tasks, showing convenient patterns that can be reused in your own applications.

### Using QTextEdit

A text editor widget can be constructed and used to display HTML in the following way:

            QTextEdit *editor = new QTextEdit(parent);
            editor->setHtml(aStringContainingHTMLtext);
            editor->show();

By default, the text editor contains a document with a root frame, inside which is an empty text block. This document can be obtained so that it can be modified directly by the application:

            QTextDocument *document = editor->document();

The text editor's cursor may be used to edit a document:

            QTextCursor cursor = editor->textCursor();

Although a document can be edited using many cursors at once, a `QTextEdit` only displays a single cursor at a time. Therefore, if we want to update the editor to display a particular cursor or its selection, we need to set the editor's cursor after we have modified the document:

            editor->setTextCursor(cursor);

### Selecting Text

Text is selected by moving the cursor using operations that are similar to those that the user would make to select text in an editor. To select text between two points in the document, we need to position the cursor at the first point then move it using a special QTextCursor::MoveMode with a QTextCursor::MoveOperation. When we select the text, we leave the selection anchor at the old cursor position just as the user might do by holding down the Shift key when selecting text:

            cursor.movePosition(QTextCursor::StartOfWord);
            cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);

In the above code, a whole word is selected using this method. `QTextCursor` provides a number of common move operations.

### Finding Text

`QTextDocument` provides a cursor-based interface for searching, making it easy to find and modify text in the style of a text editor. The following code finds all the instances of a particular word in a document, and changes the color of each:

            QTextCursor newCursor(document);

            while (!newCursor.isNull() && !newCursor.atEnd()) {
                newCursor = document->find(searchString, newCursor);

                if (!newCursor.isNull()) {
                    newCursor.movePosition(QTextCursor::WordRight,
                                              QTextCursor::KeepAnchor);

                    QString text = newCursor.selectedText();
                    newCursor.removeSelectedText();
                    newCursor.insertText(text, colorFormat);
                }
            }

Note that the cursor does not have to be moved after each search and replace operation; it is always positioned at the end of the word that was just replaced.