Как отобразить графические объекты позади или на переднем плане текста внутри QTextEdit в Qt?
Я хотел бы отобразить прямоугольник позади слова, которое я выбрал, как это делает Qt Creator:
Я экспериментирую с примером QSyntaxHighlighter. Я могу изменить стили на основе шаблонов ключевых слов. Я хотел бы иметь графику или виджеты для пользовательских списков автозаполнения.
2 ответа
Обратите внимание, что этот ответ еще не охвачен
Я хотел бы иметь графику или виджеты для пользовательских списков автозаполнения.
но я смотрю на это
Редактировать:
Для автозаполнения следуйте Custom Completer Example
или Completer Example
,
Приведенный ниже код следует за первым, который я нагло, без стеснения скопировал и интегрировал в BackgroundHighlighter
класс и main.cpp
,
Этот ответ будет содержать пять файлов в проекте вместе с файлом ресурсов Qt.
highlighter.h
(Highlighter Class для синтаксиса)highlighter.cpp
backgroundHighlighter.h
(BackgroundHighlighter Class)backgroundHighlighter.cpp
main.cpp
res.qrc
(необязательно, не нужно, вы можете жестко закодировать ваш текст)res
(каталог) (необязательно)|- symbols.txt
(необязательно, вы можете установить свой собственный текст по умолчанию)|- wordlist.txt
(необязательно, скопировано из примера, но вы можете использовать свой собственный список слов с разделителями строк и установить его вmain.cpp
сQStringListModel
)
Обратите внимание, что реализация Highlighter
класс для (1) и (2) можно найти в примере выделения синтаксиса Qt. Я оставлю его реализацию в качестве упражнения для читателя.
В вызове BackgroundHighlighter
класс, можно передать ему имя файла, чтобы загрузить текст из файла. (Этого не было в спецификации OP, но его было удобно реализовать из-за большого количества текста, который я хотел протестировать.)
Также обратите внимание, что я интегрировал Custom Completer Example
в класс.
Вот backgroundHighlighter.h
(3) (~45 строк, ~60 строк с дополнением):
#ifndef BACKGROUNDHIGHLIGHTER_H
#define BACKGROUNDHIGHLIGHTER_H
#include <QtWidgets>
#include <QtGui>
// this is the file to your highlighter
#include "myhighlighter.h"
class BackgroundHighlighter : public QTextEdit
{
Q_OBJECT
public:
BackgroundHighlighter(const QString &fileName = QString(), QWidget *parent = nullptr);
void loadFile(const QString &fileName);
void setCompleter(QCompleter *completer);
QCompleter *completer() const;
protected:
void keyPressEvent(QKeyEvent *e) override;
void focusInEvent(QFocusEvent *e) override;
public slots:
void onCursorPositionChanged();
private slots:
void insertCompletion(const QString &completion);
private:
// this is your syntax highlighter
Highlighter *syntaxHighlighter;
// stores the symbol being highlighted
QString highlightSymbol;
// stores the position (front of selection) where the cursor was originally placed
int mainHighlightPosition;
// stores character formats to be used
QTextCharFormat mainFmt;
QTextCharFormat subsidiaryFmt;
QTextCharFormat defaultFmt;
void setWordFormat(const int &position, const QTextCharFormat &format);
void runHighlight();
void clearHighlights();
void highlightMatchingSymbols(const QString &symbol);
// completer, copied from example
QString textUnderCursor() const;
QCompleter *c;
};
#endif // BACKGROUNDHIGHLIGHTER_H
И вот backgroundHighlighter.cpp
(4) (~160 строк, ~250 строк с завершителем):
#include "backgroundhighlighter.h"
#include <QDebug>
// constructor
BackgroundHighlighter::BackgroundHighlighter(const QString &fileName, QWidget *parent) :
QTextEdit(parent)
{
// I like Monaco
setFont(QFont("Monaco"));
setMinimumSize(QSize(500, 200));
// load initial text from a file OR from a hardcoded default
if (!fileName.isEmpty())
loadFile(fileName);
else
{
QString defaultText = "This is a default text implemented by "
"a stackru user. Please upvote his answer "
"at https://stackru.com/a/53351512/10239789.";
setPlainText(defaultText);
}
// set the highlighter here
QTextDocument *doc = document();
syntaxHighlighter = new Highlighter(doc);
// TODO change brush/colours to match theme
mainFmt.setBackground(Qt::yellow);
subsidiaryFmt.setBackground(Qt::lightGray);
defaultFmt.setBackground(Qt::white);
// connect the signal to our handler
connect(this, &QTextEdit::cursorPositionChanged, this, &BackgroundHighlighter::onCursorPositionChanged);
}
// convenience function for reading a file
void BackgroundHighlighter::loadFile(const QString &fileName)
{
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly))
return;
// the file could be in Plain Text OR Html
setText(file.readAll());
}
void BackgroundHighlighter::setCompleter(QCompleter *completer)
{
if (c)
QObject::disconnect(c, 0, this, 0);
c = completer;
if (!c)
return;
c->setWidget(this);
c->setCompletionMode(QCompleter::PopupCompletion);
c->setCaseSensitivity(Qt::CaseInsensitive);
QObject::connect(c, SIGNAL(activated(QString)),
this, SLOT(insertCompletion(QString)));
}
QCompleter *BackgroundHighlighter::completer() const
{
return c;
}
void BackgroundHighlighter::keyPressEvent(QKeyEvent *e)
{
if (c && c->popup()->isVisible()) {
// The following keys are forwarded by the completer to the widget
switch (e->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
case Qt::Key_Escape:
case Qt::Key_Tab:
case Qt::Key_Backtab:
e->ignore();
return; // let the completer do default behavior
default:
break;
}
}
bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E); // CTRL+E
if (!c || !isShortcut) // do not process the shortcut when we have a completer
QTextEdit::keyPressEvent(e);
const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
if (!c || (ctrlOrShift && e->text().isEmpty()))
return;
static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); // end of word
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
QString completionPrefix = textUnderCursor();
if (!isShortcut && (hasModifier || e->text().isEmpty()|| completionPrefix.length() < 3
|| eow.contains(e->text().right(1)))) {
c->popup()->hide();
return;
}
if (completionPrefix != c->completionPrefix()) {
c->setCompletionPrefix(completionPrefix);
c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));
}
QRect cr = cursorRect();
cr.setWidth(c->popup()->sizeHintForColumn(0)
+ c->popup()->verticalScrollBar()->sizeHint().width());
c->complete(cr); // pop it up!
}
void BackgroundHighlighter::focusInEvent(QFocusEvent *e)
{
if (c)
c->setWidget(this);
QTextEdit::focusInEvent(e);
}
// convenience function for setting a `charFmt` at a `position`
void BackgroundHighlighter::setWordFormat(const int &position, const QTextCharFormat &charFmt)
{
QTextCursor cursor = textCursor();
cursor.setPosition(position);
cursor.select(QTextCursor::WordUnderCursor);
cursor.setCharFormat(charFmt);
}
// this will handle the `QTextEdit::cursorPositionChanged()` signal
void BackgroundHighlighter::onCursorPositionChanged()
{
// if cursor landed on different format, the `currentCharFormat` will be changed
// we need to change it back to white
setCurrentCharFormat(defaultFmt);
// this is the function you're looking for
runHighlight();
}
void BackgroundHighlighter::insertCompletion(const QString &completion)
{
if (c->widget() != this)
return;
QTextCursor tc = textCursor();
int extra = completion.length() - c->completionPrefix().length();
tc.movePosition(QTextCursor::Left);
tc.movePosition(QTextCursor::EndOfWord);
tc.insertText(completion.right(extra));
setTextCursor(tc);
}
QString BackgroundHighlighter::textUnderCursor() const
{
QTextCursor tc = textCursor();
tc.select(QTextCursor::WordUnderCursor);
return tc.selectedText();
}
/**
* BRIEF
* Check if new highlighting is needed
* Clear previous highlights
* Check if the word under the cursor is a symbol (i.e. matches ^[A-Za-z0-9_]+$)
* Highlight all relevant symbols
*/
void BackgroundHighlighter::runHighlight()
{
// retrieve cursor
QTextCursor cursor = textCursor();
// retrieve word under cursor
cursor.select(QTextCursor::WordUnderCursor);
QString wordUnder = cursor.selectedText();
qDebug() << "Word Under Cursor:" << wordUnder;
// get front of cursor, used later for storing in `highlightPositions` or `mainHighlightPosition`
int cursorFront = cursor.selectionStart();
// if the word under cursor is the same, then save time
// by skipping the process
if (wordUnder == highlightSymbol)
{
// switch formats
setWordFormat(mainHighlightPosition, subsidiaryFmt); // change previous main to subsidiary
setWordFormat(cursorFront, mainFmt); // change position under cursor to main
// update main position
mainHighlightPosition = cursorFront;
// jump the gun
return;
}
// clear previous highlights
if (mainHighlightPosition != -1)
clearHighlights();
// check if selected word is a symbol
if (!wordUnder.contains(QRegularExpression("^[A-Za-z0-9_]+$")))
{
qDebug() << wordUnder << "is not a symbol!";
return;
}
// set the highlight symbol
highlightSymbol = wordUnder;
// store the cursor position to check later
mainHighlightPosition = cursorFront;
// highlight all relevant symbols
highlightMatchingSymbols(wordUnder);
qDebug() << "Highlight done\n\n";
}
// clear previously highlights
void BackgroundHighlighter::clearHighlights()
{
QTextCursor cursor = textCursor();
// wipe the ENTIRE document with the default background, this should be REALLY fast
// WARNING: this may have unintended consequences if you have other backgrounds you want to keep
cursor.select(QTextCursor::Document);
cursor.setCharFormat(defaultFmt);
// reset variables
mainHighlightPosition = -1;
highlightSymbol.clear();
}
// highlight all matching symbols
void BackgroundHighlighter::highlightMatchingSymbols(const QString &symbol)
{
// highlight background of congruent symbols
QString docText = toPlainText();
// use a regex with \\b to look for standalone symbols
QRegularExpression regexp("\\b" + symbol + "\\b");
// loop through all matches in the text
int matchPosition = docText.indexOf(regexp);
while (matchPosition != -1)
{
// if the position
setWordFormat(matchPosition, matchPosition == mainHighlightPosition ? mainFmt : subsidiaryFmt);
// find next match
matchPosition = docText.indexOf(regexp, matchPosition + 1);
}
}
Наконец, вот main.cpp
(5) (~10 строк, ~45 строк с завершителем)
#include <QApplication>
#include <backgroundhighlighter.h>
QAbstractItemModel *modelFromFile(const QString& fileName, QCompleter *completer)
{
QFile file(fileName);
if (!file.open(QFile::ReadOnly))
return new QStringListModel(completer);
#ifndef QT_NO_CURSOR
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
#endif
QStringList words;
while (!file.atEnd()) {
QByteArray line = file.readLine();
if (!line.isEmpty())
words << line.trimmed();
}
#ifndef QT_NO_CURSOR
QApplication::restoreOverrideCursor();
#endif
return new QStringListModel(words, completer);
}
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
BackgroundHighlighter bh(":/res/symbols.txt");
QCompleter *completer = new QCompleter();
completer->setModel(modelFromFile(":/res/wordlist.txt", completer));
// use this and comment the above if you don't have or don't want to use wordlist.txt
// QStringListModel *model = new QStringListModel(QStringList() << "aaaaaaa" << "aaaaab" << "aaaabb" << "aaacccc",
completer);
// completer->setModel(model);
completer->setModelSorting(QCompleter::CaseInsensitivelySortedModel);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setWrapAround(false);
bh.setCompleter(completer);
bh.show();
return a.exec();
}
В res.qrc
добавить /
префикс и добавление файлов (res/symbols.txt
, res/wordlist.txt
) от res/
подкаталог.
Я старался изо всех сил, чтобы
- предоставить достаточное объяснение в комментариях.
- проверить мое решение в контексте всех тестовых случаев.
- оптимизировать код (не совсем) для лучшей производительности во время выполнения.
Какие классы Qt используются?
Приведенный выше код использует модуль ядра Qt, модуль виджетов Qt и модуль Qt Gui.
Среди них основные, которые я использовал, были
QTextEdit
для подклассаBackgroundHighlighter
учебный класс. Это виджет. СигналcursorPositionChanged
происходит отQTextEdit
,QCharTextFormat
для форматирования блоков текста. Особенно сsetBackground()
унаследованный отQTextFormat
,QTextCursor
для управления курсором (без визуальных изменений), выбора слов и выделения их.
Некоторые другие второстепенные были
QRegularExpression
для сопоставления символов.QTextDocument
для инициализации подсветки синтаксиса.QFile
для загрузки файлов.- а также
QString
(Очевидно).
Другие классы, возможно, использовались Highlighter
и Custom Completer
, К ним относятся QSyntaxHighlighter, QCompleter и класс событий пары.
Были ли какие-либо предыдущие попытки, которые вы пытались найти, чтобы я не упал в тот же люк?
Я потратил некоторое время, пытаясь найти, как я мог бы реализовать это с QTextBlock
а также QTextBlockFormat
, Видимо, QTextBlock
распознает только один блок как линию. Я пытался использовать несколько блоков в строке для обработки символов и даже пытался использовать QTextTable
решить вопрос. Я с треском провалился.
Насколько "оптимизирован" этот код?
Обратите внимание, что я не пошел серьезно и не написал тестовые / крайние случаи, не профилировал или не рассчитал время (я мог бы рассчитать это).
Когда я сказал "оптимизировать", я имел в виду тот факт, что раньше у меня были медленные, худшие реализации (используя QList
и что "нет).
Я проверил с symbols.txt
файл напоминающий
symbol1 symbol2 symbol3 symbol4 symbol5
symbol1 symbol2 symbol3 symbol4 symbol5
symbol1 symbol2 symbol3 symbol4 symbol5
// ... 500 lines
и я счастлив сказать, что считаю время разумным (примерно 1 секунда для меня?).
Тем не менее, вы можете следить за количеством строк по мере их роста. С тем же текстовым файлом в 1000 строк, программа начнет занимать ок. 3 секунды для выделения.
Обратите внимание, что... я не оптимизировал это полностью. Возможно, может быть лучшая реализация, которая форматирует только тогда, когда символ прокручивается в поле зрения пользователя. Это всего лишь предложение. Как это реализовать, я не знаю. (Но я мог бы попробовать.)
Есть ли какие-либо "побочные эффекты", о которых мне следует опасаться?
Да, около 133, под BackgroundHighlighter::clearHighlights()
метод, я предупреждаю, как код может убрать любые изначально выделенные фоновые выделения, так как он устанавливает фоновый символ ВСЕГО документа в формат по умолчанию. Это может быть непреднамеренным следствием результата.
Как работает завершитель?
Слова загружаются из модели в main.cpp
, Как только пользователь начинает вводить слово со словом не менее 3 символов, появляется автозаполнение. Если завершитель не появляется для вас, попробуйте сначала реализовать пример в отдельном / отдельном приложении.
Как я могу изменить цвет фона форматов?
Перейти к строкам с 27 по 29 backgroundhighlighter.cpp
, Там видно, что я централизовал форматирование. mainFmt
относится к блоку форматирования непосредственно под курсором. subsidiaryFmt
относится к блокам форматирования на конгруэнтных символах. defaultFmt
относится к стандартному формату всего документа, который будет использоваться для сброса формата.
Где я могу получить symbols.txt
а также wordlist.txt
?
Я прикрепил их к своему хранилищу GitHub Stackru. Вы можете скачать и скопировать их оттуда.
Если что-то не так, пожалуйста, прокомментируйте ниже.
Для стилизованного фона выбранных слов, как на картинке в 1-м посте, отредактируйте backgroundhighlighter.cpp в строке 30
QRadialGradient gradient(50, 50, 50, 50, 50);
gradient.setColorAt(0, QColor::fromRgbF(0, 1, 0, 1));
gradient.setColorAt(1, QColor::fromRgbF(0, 0, 0, 0));
QBrush brush(gradient);
subsidiaryFmt.setBackground(brush);
Но после первого слова градиенты теряются. Я попытаюсь это исправить. Есть идеи?