Как отобразить графические объекты позади или на переднем плане текста внутри QTextEdit в Qt?

Я хотел бы отобразить прямоугольник позади слова, которое я выбрал, как это делает Qt Creator:QtCreator делает это, когда я выбираю слово

Я экспериментирую с примером QSyntaxHighlighter. Я могу изменить стили на основе шаблонов ключевых слов. Я хотел бы иметь графику или виджеты для пользовательских списков автозаполнения.

2 ответа

Решение

Обратите внимание, что этот ответ еще не охвачен

Я хотел бы иметь графику или виджеты для пользовательских списков автозаполнения.

но я смотрю на это

Редактировать:

Для автозаполнения следуйте Custom Completer Example или Completer Example,

Приведенный ниже код следует за первым, который я нагло, без стеснения скопировал и интегрировал в BackgroundHighlighter класс и main.cpp,


Этот ответ будет содержать пять файлов в проекте вместе с файлом ресурсов Qt.

  1. highlighter.h (Highlighter Class для синтаксиса)
  2. highlighter.cpp
  3. backgroundHighlighter.h (BackgroundHighlighter Class)
  4. backgroundHighlighter.cpp
  5. main.cpp
  6. res.qrc (необязательно, не нужно, вы можете жестко закодировать ваш текст)
  7. res (каталог) (необязательно)
  8. |- symbols.txt (необязательно, вы можете установить свой собственный текст по умолчанию)
  9. |- 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/ подкаталог.

Я старался изо всех сил, чтобы

  1. предоставить достаточное объяснение в комментариях.
  2. проверить мое решение в контексте всех тестовых случаев.
  3. оптимизировать код (не совсем) для лучшей производительности во время выполнения.

Какие классы 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);

введите описание изображения здесь

Но после первого слова градиенты теряются. Я попытаюсь это исправить. Есть идеи?

Другие вопросы по тегам