简体   繁体   English

如何在Qt中的QTextEdit中在文本的后面或前景显示图形对象?

[英]How to display graphics objects behind or foreground of text inside QTextEdit in Qt?

I would like to display a rectangle behind a word I selected like Qt Creator does here:我想在我选择的单词后面显示一个矩形,就像 Qt Creator 在这里所做的那样: 当我选择一个单词时,QtCreator 会这样做。

I am experimenting with the example of QSyntaxHighlighter.我正在试验 QSyntaxHighlighter 的例子。 I am able to change styles based on keyword patterns.我能够根据关键字模式更改样式。 I would like to have graphics or widgets for custom autocompletion lists.我想要自定义自动完成列表的图形或小部件。

For autocompletion follow the Custom Completer Example or the Completer Example .对于自动完成,请遵循Custom Completer ExampleCompleter Example

The code below follows the first one, which I blatantly, unashamedly copied and integrated into the BackgroundHighlighter class and main.cpp .下面的代码遵循第一个,我公然、无耻地复制并集成到BackgroundHighlighter类和main.cpp


This answer will contain five files within a project along with a Qt Resource File.这个答案将包含一个项目中的五个文件以及一个 Qt 资源文件。

  1. highlighter.h (Highlighter Class for Syntax) highlighter.h (语法的高亮类)
  2. highlighter.cpp
  3. backgroundHighlighter.h (BackgroundHighlighter Class) backgroundHighlighter.h (BackgroundHighlighter 类)
  4. backgroundHighlighter.cpp
  5. main.cpp
  6. res.qrc (optional, not needed, you can hardcode your text) res.qrc (可选,不需要,您可以对文本进行硬编码)
  7. res (directory) (optional) res (目录)(可选)
  8. |- symbols.txt (optional, you can set your own default text) |- symbols.txt (可选,您可以设置自己的默认文本)
  9. |- wordlist.txt (optional, copied from example but you could use your own line-delimited word list and set this in main.cpp with a QStringListModel ) |- wordlist.txt (可选,从示例中复制,但您可以使用自己的行分隔单词列表并在main.cpp使用QStringListModel

Note that the implementation of the Highlighter class for (1) and (2) can be found in the Qt Syntax Highlighter Example .请注意,可以在Qt 语法荧光笔示例 中找到 (1) 和 (2) 的Highlighter类的实现。 I will leave its implementation as an exercise for the reader.我将把它的实现留给读者作为练习。

In calling the BackgroundHighlighter class, one can pass it a file name to load text from a file.在调用BackgroundHighlighter类时,可以向它传递一个文件名以从文件加载文本。 (This wasn't in the OP's specification, but was convenient to implement due to the large amount of text I wanted to test.) (这不在 OP 的规范中,但由于我要测试的文本量很大,因此很容易实现。)

Also note that I integrated the Custom Completer Example into the class.另请注意,我将Custom Completer Example集成到类中。

Here's backgroundHighlighter.h (3) (~45 lines, ~60 lines with completer):这是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;           //  refers to format block directly under the cursor   
    QTextCharFormat subsidiaryFmt;     //  refers to the formatting blocks on matching words  
    QTextCharFormat defaultFmt;        //  refers to the default format of the **entire** document which will be used in resetting the format     

    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

And here's backgroundHighlighter.cpp (4) (~160 lines, ~250 lines with completer):这里是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 stackoverflow user. Please upvote the answer "
                              "at https://stackoverflow.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);
    }
}

Finally, here's main.cpp (5) (~10 lines, ~45 lines with completer)最后,这里是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();
}

In res.qrc add a / prefix and add files ( res/symbols.txt , res/wordlist.txt ) from the res/ subdirectory.res.qrc添加/前缀并从res/子目录添加文件( res/symbols.txtres/wordlist.txt )。

I have tested with a symbols.txt file resembling我已经用类似的symbols.txt文件进行了测试

symbol1 symbol2 symbol3 symbol4 symbol5
symbol1 symbol2 symbol3 symbol4 symbol5
symbol1 symbol2 symbol3 symbol4 symbol5
// ... ditto 500 lines

It takes about 1 second, which probably isn't ideal (100ms is probably more ideal).大约需要 1 秒,这可能并不理想(100 毫秒可能更理想)。

However, you might want to watch over for the line count as it grows.但是,您可能希望在行数增长时注意行数。 With the same text file at 1000 lines, the program will start to take approx.使用 1000 行的相同文本文件,程序将开始花费大约。 3 seconds for highlighting. 3 秒突出显示。

Note that... I haven't optimised it entirely .请注意...我还没有完全优化它。 There could possibly be a better implementation which formats only when the symbol scrolls into the user's view .可能有更好的实现,当符号滚动到用户视图时才进行格式化 This is just a suggestion.这只是一个建议。 How to implement it I don't know.我不知道如何实现它。


Notes笔记

  • For reference, I've attached symbols.txt and wordlist.txt on github .作为参考,我在github上附加了symbols.txt 和wordlist.txt。
  • If you want to change the background colour of formatting, go to lines 27 to 29 of backgroundhighlighter.cpp .如果要更改格式的背景颜色,请转到backgroundhighlighter.cpp第 27 至 29 行。 There, you can see that I centralised the formatting.在那里,您可以看到我集中了格式。
  • BackgroundHighlighter::clearHighlights() might clear away any background highlights originally added as it sets the ENTIRE document's character background to the default format. BackgroundHighlighter::clearHighlights()可能会清除最初添加的任何背景高光,因为它将整个文档的字符背景设置为默认格式。 This may be an unintended consequence of the result.这可能是结果的意外后果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM