C++ 如何在Qt中的QTextEdit中显示文本后面或前面的图形对象?
我想在我选择的单词后面显示一个矩形,就像Qt Creator在这里所做的那样:C++ 如何在Qt中的QTextEdit中显示文本后面或前面的图形对象?,c++,qt,autocomplete,syntax-highlighting,qtextedit,C++,Qt,Autocomplete,Syntax Highlighting,Qtextedit,我想在我选择的单词后面显示一个矩形,就像Qt Creator在这里所做的那样: 我正在试验QSyntaxHighlighter的示例。我能够根据关键字模式更改样式。我希望有用于自定义自动完成列表的图形或小部件。对于自动完成,请遵循或 下面的代码是第一个代码的后续代码,我毫不掩饰地将其复制并集成到BackgroundHighlighter类和main.cpp中 这个答案将包含一个项目中的五个文件以及一个Qt资源文件 highlighter.h(用于语法的highlighter类) highli
我正在试验QSyntaxHighlighter的示例。我能够根据关键字模式更改样式。我希望有用于自定义自动完成列表的图形或小部件。对于自动完成,请遵循或 下面的代码是第一个代码的后续代码,我毫不掩饰地将其复制并集成到
BackgroundHighlighter
类和main.cpp
中
这个答案将包含一个项目中的五个文件以及一个Qt资源文件
highlighter.h
(用于语法的highlighter类)highlighter.cpp
backgroundHighlighter.h
(backgroundHighlighter类)backgroundHighlighter.cpp
main.cpp
res.qrc
(可选,不需要,您可以硬编码文本)res
(目录)(可选)|-symbols.txt
(可选,您可以设置自己的默认文本)|-wordlist.txt
(可选,从中复制,但您可以使用自己的行分隔单词列表,并在main.cpp
中使用BackgroundHighlighter
类时,可以向其传递文件名以从文件加载文本。(这不在OP的规范中,但由于我想测试大量文本,因此实现起来很方便。)
还要注意的是,我已经将
以下是backgroundHighlighter.h
(3)(约45行,约60行带补全符):
最后,这里是main.cpp
(5)(~10行,~45行带完成符)
大约需要1秒,这可能并不理想(100ms可能更理想)
但是,您可能希望在行数增长时注意它。对于1000行的同一文本文件,程序将开始用大约3秒的时间突出显示
请注意。。。我没有完全优化它。可能有一个更好的实现,它只在符号滚动到用户视图时格式化。这只是一个建议。我不知道如何实现它
注释
- 为了便于参考,我在上附加了symbols.txt和wordlist.txt
- 如果要更改格式的背景颜色,请转到
的第27至29行。在那里,你可以看到我集中了格式backgroundhighlighter.cpp
可能会清除最初添加的任何背景高光,因为它会将整个文档的字符背景设置为默认格式。这可能是结果的意外后果BackgroundHighlighter::clearHighlights()
main.cpp
中,尝试将setModel
实现替换为completer->setModel(QStringListModel(QStringList())不,我的意思是,现在当我添加3个字母时,会触发补全符。它工作得很好。你能给我解释一下symbols.txt的作用吗?世界主义者不是我自动补全唯一需要的东西吗?symbols.txt的用途是什么?
#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
#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);
}
}
#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();
}
symbol1 symbol2 symbol3 symbol4 symbol5
symbol1 symbol2 symbol3 symbol4 symbol5
symbol1 symbol2 symbol3 symbol4 symbol5
// ... ditto 500 lines