Интерфейс QTextCursor
Документы могут быть отредактированы через интерфейс, предоставляемый классом QTextCursor; курсоры создаются с помощью конструктора или получаются из виджета редактора. Курсор используется для выполнения операций редактирования, которые точно соответствуют операциям, которые пользователь может выполнить в редакторе. В результате, информация о структуре документа также доступна через курсор, что позволяет изменять структуру. Использование курсорно-ориентированного интерфейса для редактирования упрощает процесс написания пользовательского редактора для разработчиков, так как операции редактирования легко визуализируются.
Класс QTextCursor также сохраняет информацию о любом выбранном тексте в документе, опять же, следуя модели, концептуально аналогичной действиям пользователя по выделению текста в редакторе.
В документах с форматированием могут быть ассоциированы несколько курсоров, и каждый из них содержит информацию о своей позиции в документе и любых выделениях, которые он может содержать. Эта парадигма на основе курсора делает общие операции, такие как вырезание и вставка текста, простыми для программной реализации, но также позволяет выполнять более сложные операции редактирования документа.
В этой главе описаны большинство распространенных операций редактирования, которые вам необходимо выполнить с помощью курсора, от базовой вставки текста и элементов документа до более сложного изменения структуры документа.
Редактирование с использованием курсора
На самом простом уровне текстовые документы состоят из последовательности символов, помеченных каким-либо образом для представления блочной структуры текста в документе. QTextCursor предоставляет курсорно-ориентированный интерфейс, который позволяет манипулировать содержимым QTextDocument на уровне символов. Поскольку элементы (блоки, фреймы, таблицы и т. д.) также закодированы в потоке символов, структура документа сама может быть изменена курсором.
Курсор отслеживает свое положение в родительском документе и может сообщать информацию об окружающей структуре, такой как содержащий текстовый блок, фрейм, таблицу или список. Форматы содержащих структур также могут быть получены непосредственно через курсор.
Использование курсора
Основное использование курсора — вставка или изменение текста в блоке. Мы можем использовать курсор текстового редактора для этого:
QTextEdit *editor = new QTextEdit();
QTextCursor cursor(editor->textCursor()); В качестве альтернативы, мы можем получить курсор непосредственно из документа:
QTextDocument *document = new QTextDocument(editor);
QTextCursor cursor(document); Курсор позиционируется в начале документа, чтобы мы могли записать в первый (пустой) блок в документе.
Группировка операций с курсором
Последовательность операций редактирования может быть упакована вместе, чтобы их можно было воспроизвести или отменить вместе в одном действии. Это достигается с помощью функций beginEditBlock() и endEditBlock() следующим образом, как показано в следующем примере, где мы выделяем слово, содержащее курсор:
cursor.beginEditBlock();
cursor.movePosition(QTextCursor::StartOfWord);
cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
cursor.endEditBlock(); Если операции редактирования не сгруппированы, документ автоматически записывает отдельные операции, чтобы их можно было отменить позже. Группировка операций в более крупные пакеты может сделать редактирование более эффективным как для пользователя, так и для приложения, но следует проявлять осторожность, чтобы не группировать слишком много операций вместе, так как пользователь может захотеть более детальный контроль над процессом отмены.
Множественные курсоры
Можно использовать несколько курсоров для одновременного редактирования одного и того же документа, хотя только один будет виден пользователю в виджете QTextEdit. QTextDocument гарантирует, что каждый курсор записывает текст правильно и не мешает другим.
Вставка элементов документа
QTextCursor предоставляет несколько функций, которые можно использовать для изменения структуры документа с форматированием. Как правило, эти функции позволяют создавать элементы документа с соответствующей информацией о форматировании и вставлять их в документ в позиции курсора.
Первая группа функций вставляет элементы уровня блока и обновляет позицию курсора, но не возвращает вставленный элемент:
- insertBlock() вставляет новый текстовый блок (абзац) в документ в позиции курсора и перемещает курсор в начало нового блока.
- insertFragment() вставляет существующий фрагмент текста в документ в позиции курсора.
- insertImage() вставляет изображение в документ в позиции курсора.
- insertText() вставляет текст в документ в позиции курсора.
Вы можете изучить содержимое вставленного элемента через интерфейс курсора.
Вторая группа функций вставляет элементы, которые структурируют документ, и возвращает вставленную структуру:
- insertFrame() вставляет фрейм в документ после текущего блока курсора и перемещает курсор в начало пустого блока в новом фрейме.
- insertList() вставляет список в документ в позиции курсора и перемещает курсор в начало первого элемента в списке.
- insertTable() вставляет таблицу в документ после текущего блока курсора и перемещает курсор в начало блока, следующего за таблицей.
Эти элементы либо содержат, либо группируют другие элементы в документе.
Текст и фрагменты текста
Текст можно вставить в текущий блок в текущем формате символов или в пользовательском формате, который указан с текстом:
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); После использования формата символов с курсором этот формат становится стандартным форматом для любого текста, вставляемого с этим курсором, до тех пор, пока не будет указан другой формат символов.
Если курсор используется для вставки текста без указания формата символов, тексту будет присвоен формат символов, используемый в этой позиции в документе.
Блоки
Текстовые блоки вставляются в документ с помощью функции insertBlock().
QTextBlockFormat backgroundFormat = blockFormat;
backgroundFormat.setBackground(QColor("lightGray"));
cursor.setBlockFormat(backgroundFormat); Курсор позиционируется в начале нового блока.
Фреймы
Фреймы вставляются в документ с помощью курсора и будут размещены внутри текущего фрейма курсора после текущего блока. Следующий код показывает, как фрейм может быть вставлен между двумя текстовыми блоками в корневом фрейме документа. Мы начинаем с поиска текущего фрейма курсора:
QTextFrame *mainFrame = cursor.currentFrame();
cursor.insertText(...); Мы вставляем текст в этот фрейм, затем устанавливаем формат фрейма для дочернего фрейма:
QTextFrameFormat frameFormat;
frameFormat.setMargin(32);
frameFormat.setPadding(8);
frameFormat.setBorder(4); Формат фрейма задаст внешнюю рамку фрейма в 32 пикселя, внутренний отступ в 8 пикселей и границу шириной в 4 пикселя. Дополнительная информация о форматах фреймов содержится в документации QTextFrameFormat.
Фрейм вставляется в документ после предыдущего текста:
cursor.insertFrame(frameFormat);
cursor.insertText(...); Мы добавляем текст в документ сразу после вставки фрейма. Так как текстовый курсор позиционируется внутри фрейма при его вставке в документ, этот текст также будет вставлен внутри фрейма.
Наконец, мы позиционируем курсор вне фрейма, взяв последнюю доступную позицию курсора внутри фрейма, которую мы записали ранее:
cursor = mainFrame->lastCursorPosition();
cursor.insertText(...); Текст, который мы добавляем последним, вставляется после дочернего фрейма в документе. Так как каждый фрейм заполняется текстовыми блоками, это гарантирует, что с помощью курсора всегда можно вставлять больше элементов.
Таблицы
Таблицы вставляются в документ с помощью курсора и будут размещены внутри текущего фрейма курсора после текущего блока:
QTextCursor cursor(editor->textCursor());
QTextTable *table = cursor.insertTable(rows, columns, tableFormat); Таблицы могут быть созданы с определенным форматом, который определяет общие свойства таблицы, такие как ее выравнивание, цвет фона и интервал между ячейками. Он также может определять ограничения для каждого столбца, позволяя каждому из них иметь фиксированную ширину или изменять размер в зависимости от доступного места.
QTextTableFormat tableFormat;
tableFormat.setBackground(QColor("#e0e0e0"));
QList<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); Столбцы в созданной выше таблице займут определенный процент доступной ширины. Обратите внимание, что формат таблицы необязателен; если вы вставляете таблицу без формата, для свойств таблицы будут использованы разумные значения по умолчанию.
Поскольку ячейки могут содержать другие элементы документа, они также могут быть отформатированы и стилизованы по необходимости.
Текст можно добавить в таблицу, переместившись к каждой ячейке с помощью курсора и вставки текста.
cell = table->cellAt(0, 0);
cellCursor = cell.firstCursorPosition();
cellCursor.insertText(tr("Week"), charFormat); Мы можем создать простую таблицу расписания, следуя этому подходу:
for (column = 1; column < columns; ++column) {
cell = table->cellAt(0, column);
cellCursor = cell.firstCursorPosition();
cellCursor.insertText(tr("Team %1").arg(column), charFormat);
}
for (row = 1; row < rows; ++row) {
cell = table->cellAt(row, 0);
cellCursor = cell.firstCursorPosition();
cellCursor.insertText(tr("%1").arg(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);
}
}
} Списки
Списки элементов блока могут быть автоматически созданы и вставлены в документ в текущей позиции курсора. Каждый созданный таким образом список требует указания формата списка:
QTextListFormat listFormat;
if (list) {
listFormat = list->format();
listFormat.setIndent(listFormat.indent() + 1);
}
listFormat.setStyle(QTextListFormat::ListDisc);
cursor.insertList(listFormat); В приведенном выше коде сначала проверяется, находится ли курсор внутри существующего списка, и если да, то формат списка для нового списка получает подходящий уровень отступа. Это позволяет создавать вложенные списки с увеличивающимся уровнем отступа. Более сложная реализация также будет использовать разные типы символов для маркеров в каждом уровне списка.
Изображения
Встраиваемые изображения добавляются в документы с помощью курсора обычным способом. В отличие от многих других элементов, все свойства изображения задаются форматом изображения. Это означает, что объект QTextImageFormat должен быть создан перед вставкой изображения:
QTextImageFormat imageFormat;
imageFormat.setName(":/images/advert.png");
cursor.insertImage(imageFormat); Имя изображения относится к записи в файле ресурсов приложения. Метод получения этого имени описан в Системе ресурсов Qt.
Примеры
Форматированный текст хранится в текстовых документах, которые могут быть созданы путем импорта HTML из внешнего источника или сгенерированы с помощью QTextCursor.
Обработка форматированного текста
Самый простой способ использования документа с форматированием — через класс QTextEdit, который предоставляет редактируемый вид документа. В приведенном ниже коде HTML импортируется в документ и отображается с помощью виджета редактирования текста.
QTextEdit *editor = new QTextEdit(parent); editor->setHtml(aStringContainingHTMLtext); editor->show();
Вы можете получить документ из текстового редактора с помощью функции document(). Затем документ можно программно редактировать с помощью класса QTextCursor. Этот класс моделируется по образцу курсора экрана, и операции редактирования следуют тем же семантикам. В следующем коде первая строка документа изменяется на полужирный шрифт, при этом все другие свойства шрифта остаются неизменными. Редактор автоматически обновится, чтобы отразить внесенные изменения в данные базового документа.
QTextDocument *document = edit->document(); QTextCursor cursor(document); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::EndOfLine, QTextCursor::KeepAnchor); QTextCharFormat format; format.setFontWeight(QFont::Bold); cursor.mergeCharFormat(format);
Обратите внимание, что курсор был перемещен с начала первой строки в конец, но он сохранил якорную точку в начале строки. Это демонстрирует возможности выделения на основе курсора класса QTextCursor.
Генерация календаря
Форматированный текст можно генерировать очень быстро, используя подход на основе курсора. Следующий пример демонстрирует простой календарь в виджете QTextEdit с жирными заголовками для дней недели:
editor = new QTextEdit(this);
QTextCursor cursor(editor->textCursor());
cursor.movePosition(QTextCursor::Start);
QTextCharFormat format(cursor.charFormat());
format.setFontFamily("Courier");
QTextCharFormat boldFormat = format;
boldFormat.setFontWeight(QFont::Bold);
cursor.insertBlock();
cursor.insertText(" ", boldFormat);
QDate date = QDate::currentDate();
int year = date.year(), month = date.month();
for (int weekDay = 1; weekDay <= 7; ++weekDay) {
cursor.insertText(QString("%1 ").arg(QLocale::system().dayName(weekDay), 3),
boldFormat);
}
cursor.insertBlock();
cursor.insertText(" ", format);
for (int column = 1; column < QDate(year, month, 1).dayOfWeek(); ++column) {
cursor.insertText(" ", format);
}
for (int day = 1; day <= date.daysInMonth(); ++day) {
int weekDay = QDate(year, month, day).dayOfWeek();
if (QDate(year, month, day) == date)
cursor.insertText(QString("%1 ").arg(day, 3), boldFormat);
else
cursor.insertText(QString("%1 ").arg(day, 3), format);
if (weekDay == 7) {
cursor.insertBlock();
cursor.insertText(" ", format);
}
} Вышеприведенный пример демонстрирует, насколько просто быстро создавать новые документы с форматированным текстом, используя минимальное количество кода. Хотя мы сгенерировали грубый календарь с фиксированным интервалом, чтобы избежать излишнего кода, Scribe предоставляет гораздо более сложные возможности компоновки и форматирования.
© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/qt-6.1/richtext-cursor.html