1

I would like to display a rectangle behind a word I selected like Qt Creator does here: QtCreator does this when I select a word.

I am experimenting with the example of QSyntaxHighlighter. I am able to change styles based on keyword patterns. I would like to have graphics or widgets for custom autocompletion lists.

6
  • Hi! What have you tried? How's your experimenting with QSyntaxHighlighter? Commented Nov 15, 2018 at 12:50
  • Well, i have this example and so far i encountered no problem:doc.qt.io/qt-5/… Commented Nov 15, 2018 at 19:48
  • The image above is how Qtcreator looks and also how i WANT my app to look, so how can i render rectangles or other shapes(can i?) inside QTextEdit? Commented Nov 15, 2018 at 19:49
  • Have you checked out QTextBlock, QTextBlockFormat, QTextFrameFormat? Commented Nov 16, 2018 at 1:49
  • No.I will check them out and see if they fit what i want to do.I'll keep the thread open for someone that has done this or was able to display graphics inside the QTextEdit Commented Nov 16, 2018 at 14:37

1 Answer 1

1

For autocompletion follow the Custom Completer Example or the Completer Example.

The code below follows the first one, which I blatantly, unashamedly copied and integrated into the BackgroundHighlighter class and main.cpp.


This answer will contain five files within a project along with a Qt Resource File.

  1. highlighter.h (Highlighter Class for Syntax)
  2. highlighter.cpp
  3. backgroundHighlighter.h (BackgroundHighlighter Class)
  4. backgroundHighlighter.cpp
  5. main.cpp
  6. res.qrc (optional, not needed, you can hardcode your text)
  7. res (directory) (optional)
  8. |- symbols.txt (optional, you can set your own default text)
  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)

Note that the implementation of the Highlighter class for (1) and (2) can be found in the Qt Syntax Highlighter Example. 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. (This wasn't in the OP's specification, but was convenient to implement due to the large amount of text I wanted to test.)

Also note that I integrated the Custom Completer Example into the class.

Here's backgroundHighlighter.h (3) (~45 lines, ~60 lines with completer):

#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):

#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)

#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.

I have tested with a symbols.txt file resembling

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).

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. 3 seconds for highlighting.

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.
  • If you want to change the background colour of formatting, go to lines 27 to 29 of backgroundhighlighter.cpp. 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. This may be an unintended consequence of the result.
Sign up to request clarification or add additional context in comments.

13 Comments

Could you provide me a symbols.txt and wordlist.txt cause i dont know what to put there.I tried: symbols.txt: "c cl cla clas class in inc incl inclu includ include class include" wordlist.txt: "class include typedef printf int main{\n} return " How is the completion list is triggered? with these files, i wasn't able to trigger it.The highlition works
Here you go: Link to Github Repo. Err... completion is supposed to be triggered after entering at least 3 characters. If it doesn't work, maybe first try a separate implementation from the example?
Edit: I didnt put right the paths of these 2 files.Now it gets triggered by itself.
Hmm... so without typing, it pops up automatically? In main.cpp try to replace the setModel implementation with completer->setModel(QStringListModel(QStringList() << "aaaaaaa" << "aaaaab" << "aaaabb" << "aaacccc", completer)); and in backgroundhighlight.cpp fill in the default text with something feasible.
No i mean that now the completer gets triggered when i add 3 letters.It works well.Could you explain me the what does the symbols.txt does?Isn't the worldist the only thing i need for autocompleting?Whats the purpose of symbols.txt?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.