Интерфейс 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"));
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); Столбцы в созданной выше таблице будут занимать определенный процент доступной ширины. Обратите внимание, что формат таблицы необязателен; если вы вставите таблицу без формата, для свойств таблицы будут использоваться некоторые разумные значения по умолчанию.
Поскольку ячейки могут содержать другие элементы документа, их также можно форматировать и стилизовать по мере необходимости.
Текст можно добавить в таблицу, переместив курсор в каждую ячейку и вставив текст.
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(QDate::shortDayName(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-5.9/richtext-cursor.html