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



.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;








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.










share|improve this question
























  • Hi! What have you tried? How's your experimenting with QSyntaxHighlighter?

    – TrebledJ
    Nov 15 '18 at 12:50











  • Well, i have this example and so far i encountered no problem:doc.qt.io/qt-5/…

    – kostas petsis
    Nov 15 '18 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?

    – kostas petsis
    Nov 15 '18 at 19:49











  • Have you checked out QTextBlock, QTextBlockFormat, QTextFrameFormat?

    – TrebledJ
    Nov 16 '18 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

    – kostas petsis
    Nov 16 '18 at 14:37

















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.










share|improve this question
























  • Hi! What have you tried? How's your experimenting with QSyntaxHighlighter?

    – TrebledJ
    Nov 15 '18 at 12:50











  • Well, i have this example and so far i encountered no problem:doc.qt.io/qt-5/…

    – kostas petsis
    Nov 15 '18 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?

    – kostas petsis
    Nov 15 '18 at 19:49











  • Have you checked out QTextBlock, QTextBlockFormat, QTextFrameFormat?

    – TrebledJ
    Nov 16 '18 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

    – kostas petsis
    Nov 16 '18 at 14:37













1












1








1


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.










share|improve this question
















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.







c++ qt autocomplete syntax-highlighting qtextedit






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 18 '18 at 15:25









TrebledJ

3,66421328




3,66421328










asked Nov 15 '18 at 11:14









kostas petsiskostas petsis

136




136












  • Hi! What have you tried? How's your experimenting with QSyntaxHighlighter?

    – TrebledJ
    Nov 15 '18 at 12:50











  • Well, i have this example and so far i encountered no problem:doc.qt.io/qt-5/…

    – kostas petsis
    Nov 15 '18 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?

    – kostas petsis
    Nov 15 '18 at 19:49











  • Have you checked out QTextBlock, QTextBlockFormat, QTextFrameFormat?

    – TrebledJ
    Nov 16 '18 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

    – kostas petsis
    Nov 16 '18 at 14:37

















  • Hi! What have you tried? How's your experimenting with QSyntaxHighlighter?

    – TrebledJ
    Nov 15 '18 at 12:50











  • Well, i have this example and so far i encountered no problem:doc.qt.io/qt-5/…

    – kostas petsis
    Nov 15 '18 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?

    – kostas petsis
    Nov 15 '18 at 19:49











  • Have you checked out QTextBlock, QTextBlockFormat, QTextFrameFormat?

    – TrebledJ
    Nov 16 '18 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

    – kostas petsis
    Nov 16 '18 at 14:37
















Hi! What have you tried? How's your experimenting with QSyntaxHighlighter?

– TrebledJ
Nov 15 '18 at 12:50





Hi! What have you tried? How's your experimenting with QSyntaxHighlighter?

– TrebledJ
Nov 15 '18 at 12:50













Well, i have this example and so far i encountered no problem:doc.qt.io/qt-5/…

– kostas petsis
Nov 15 '18 at 19:48





Well, i have this example and so far i encountered no problem:doc.qt.io/qt-5/…

– kostas petsis
Nov 15 '18 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?

– kostas petsis
Nov 15 '18 at 19:49





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?

– kostas petsis
Nov 15 '18 at 19:49













Have you checked out QTextBlock, QTextBlockFormat, QTextFrameFormat?

– TrebledJ
Nov 16 '18 at 1:49





Have you checked out QTextBlock, QTextBlockFormat, QTextFrameFormat?

– TrebledJ
Nov 16 '18 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

– kostas petsis
Nov 16 '18 at 14:37





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

– kostas petsis
Nov 16 '18 at 14:37












2 Answers
2






active

oldest

votes


















1














Note that this answer hasn't yet covered




I would like to have graphics or widgets for custom autocompletion lists.




but I'm looking into it.



Edit:



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;
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


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 his 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)
:"<>?,./;'\-="); // end of word
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
QString completionPrefix = textUnderCursor();

if (!isShortcut && (hasModifier

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 donenn";


// 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 tried my best to



  1. provide sufficient explanation within the comments.


  2. test my solution in the context of all test cases.


  3. optimise the code (not really) for better runtime performance.

What Qt classes are used?



The code above makes use of the Qt Core Module, Qt Widgets Module, and the Qt Gui Module.



Among these, the main ones I used were



  • QTextEdit for subclassing the BackgroundHighlighter class. This is a widget. The signal cursorPositionChanged comes from QTextEdit.


  • QCharTextFormat for formatting blocks of text. Especially with the setBackground() inherited from QTextFormat.



  • QTextCursor for manipulating the cursor (no visual changes), selecting words, and higlighting them.

Some other minor ones were



  • QRegularExpression for symbol matching.


  • QTextDocument for initialising the syntax highlighter.



  • QFile for loading files.

  • and QString (obviously).

Other classes may have been used by the Highlighter and the Custom Completer. These include QSyntaxHighlighter, QCompleter, and a couple event class.



Were there any previous attempts you tried that I should look out for so that I don't fall into the same manhole?



I spent a while trying to find how I might be able to implement it with QTextBlock and QTextBlockFormat. Apparently, QTextBlock only recognises one block as a line. I tried using several blocks per line to handle symbols and even tried using QTextTable to solve the issue. I failed miserably.



How "optimised" is this code?



Note that I didn't go all serious and write test/edge cases, profile it, or time it (I might time it).



When I said "optimise", I was alluding to the fact that I had slower, worse implementations before (using QList and whatnot).



I have tested with a symbols.txt file resembling



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


and I'm happy to say that I find the time reasonable (approx. 1 second for me?).



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. (But I might try 🤔.)



Are there any "side effects" I should be wary of?



Yes, at around line 133, under the BackgroundHighlighter::clearHighlights() method, I warn how the code 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.



How does the completer work?



The words are loaded from a model in main.cpp. Once the user starts typing, with a word of at least 3 characters, the autocompleter shows up. If the completer isn't showing up for you, try to first implement the example on a standalone/separate app.



How can I change the background colour of the formats?



Go to lines 27 to 29 of backgroundhighlighter.cpp. There, you can see that I centralised the formatting. mainFmt refers to the formatting block directly under the cursor. subsidiaryFmt refers to the formatting blocks on congruent symbols. defaultFmt refers to the default format of the entire document which will be used in resetting the format.



Where can I get symbols.txt and wordlist.txt?



I've attached these on my github stackoverflow repository. You can download and copy them from there.




If anything is amiss, please comment below.






share|improve this answer

























  • 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 mainn return " How is the completion list is triggered? with these files, i wasn't able to trigger it.The highlition works

    – kostas petsis
    Nov 17 '18 at 17:36












  • 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?

    – TrebledJ
    Nov 17 '18 at 17:44












  • Edit: I didnt put right the paths of these 2 files.Now it gets triggered by itself.

    – kostas petsis
    Nov 17 '18 at 17:52











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

    – TrebledJ
    Nov 17 '18 at 17:57











  • 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?

    – kostas petsis
    Nov 17 '18 at 18:04


















-1














For styled background of selected words like in the picture in the 1st post edit the backgroundhighlighter.cpp at line 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);


enter image description here



But after the first word the gradients gets lost..I'll try to fix this.Any ideas?






share|improve this answer























  • This should be in a separate question so that others can help along + debug. :-)

    – TrebledJ
    Nov 17 '18 at 18:47







  • 1





    Seems to me like the gradient is anchored at a certain center point.

    – TrebledJ
    Nov 17 '18 at 18:55











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53318233%2fhow-to-display-graphics-objects-behind-or-foreground-of-text-inside-qtextedit-in%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























2 Answers
2






active

oldest

votes








2 Answers
2






active

oldest

votes









active

oldest

votes






active

oldest

votes









1














Note that this answer hasn't yet covered




I would like to have graphics or widgets for custom autocompletion lists.




but I'm looking into it.



Edit:



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;
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


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 his 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)
:"<>?,./;'\-="); // end of word
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
QString completionPrefix = textUnderCursor();

if (!isShortcut && (hasModifier

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 donenn";


// 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 tried my best to



  1. provide sufficient explanation within the comments.


  2. test my solution in the context of all test cases.


  3. optimise the code (not really) for better runtime performance.

What Qt classes are used?



The code above makes use of the Qt Core Module, Qt Widgets Module, and the Qt Gui Module.



Among these, the main ones I used were



  • QTextEdit for subclassing the BackgroundHighlighter class. This is a widget. The signal cursorPositionChanged comes from QTextEdit.


  • QCharTextFormat for formatting blocks of text. Especially with the setBackground() inherited from QTextFormat.



  • QTextCursor for manipulating the cursor (no visual changes), selecting words, and higlighting them.

Some other minor ones were



  • QRegularExpression for symbol matching.


  • QTextDocument for initialising the syntax highlighter.



  • QFile for loading files.

  • and QString (obviously).

Other classes may have been used by the Highlighter and the Custom Completer. These include QSyntaxHighlighter, QCompleter, and a couple event class.



Were there any previous attempts you tried that I should look out for so that I don't fall into the same manhole?



I spent a while trying to find how I might be able to implement it with QTextBlock and QTextBlockFormat. Apparently, QTextBlock only recognises one block as a line. I tried using several blocks per line to handle symbols and even tried using QTextTable to solve the issue. I failed miserably.



How "optimised" is this code?



Note that I didn't go all serious and write test/edge cases, profile it, or time it (I might time it).



When I said "optimise", I was alluding to the fact that I had slower, worse implementations before (using QList and whatnot).



I have tested with a symbols.txt file resembling



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


and I'm happy to say that I find the time reasonable (approx. 1 second for me?).



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. (But I might try 🤔.)



Are there any "side effects" I should be wary of?



Yes, at around line 133, under the BackgroundHighlighter::clearHighlights() method, I warn how the code 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.



How does the completer work?



The words are loaded from a model in main.cpp. Once the user starts typing, with a word of at least 3 characters, the autocompleter shows up. If the completer isn't showing up for you, try to first implement the example on a standalone/separate app.



How can I change the background colour of the formats?



Go to lines 27 to 29 of backgroundhighlighter.cpp. There, you can see that I centralised the formatting. mainFmt refers to the formatting block directly under the cursor. subsidiaryFmt refers to the formatting blocks on congruent symbols. defaultFmt refers to the default format of the entire document which will be used in resetting the format.



Where can I get symbols.txt and wordlist.txt?



I've attached these on my github stackoverflow repository. You can download and copy them from there.




If anything is amiss, please comment below.






share|improve this answer

























  • 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 mainn return " How is the completion list is triggered? with these files, i wasn't able to trigger it.The highlition works

    – kostas petsis
    Nov 17 '18 at 17:36












  • 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?

    – TrebledJ
    Nov 17 '18 at 17:44












  • Edit: I didnt put right the paths of these 2 files.Now it gets triggered by itself.

    – kostas petsis
    Nov 17 '18 at 17:52











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

    – TrebledJ
    Nov 17 '18 at 17:57











  • 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?

    – kostas petsis
    Nov 17 '18 at 18:04















1














Note that this answer hasn't yet covered




I would like to have graphics or widgets for custom autocompletion lists.




but I'm looking into it.



Edit:



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;
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


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 his 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)
:"<>?,./;'\-="); // end of word
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
QString completionPrefix = textUnderCursor();

if (!isShortcut && (hasModifier

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 donenn";


// 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 tried my best to



  1. provide sufficient explanation within the comments.


  2. test my solution in the context of all test cases.


  3. optimise the code (not really) for better runtime performance.

What Qt classes are used?



The code above makes use of the Qt Core Module, Qt Widgets Module, and the Qt Gui Module.



Among these, the main ones I used were



  • QTextEdit for subclassing the BackgroundHighlighter class. This is a widget. The signal cursorPositionChanged comes from QTextEdit.


  • QCharTextFormat for formatting blocks of text. Especially with the setBackground() inherited from QTextFormat.



  • QTextCursor for manipulating the cursor (no visual changes), selecting words, and higlighting them.

Some other minor ones were



  • QRegularExpression for symbol matching.


  • QTextDocument for initialising the syntax highlighter.



  • QFile for loading files.

  • and QString (obviously).

Other classes may have been used by the Highlighter and the Custom Completer. These include QSyntaxHighlighter, QCompleter, and a couple event class.



Were there any previous attempts you tried that I should look out for so that I don't fall into the same manhole?



I spent a while trying to find how I might be able to implement it with QTextBlock and QTextBlockFormat. Apparently, QTextBlock only recognises one block as a line. I tried using several blocks per line to handle symbols and even tried using QTextTable to solve the issue. I failed miserably.



How "optimised" is this code?



Note that I didn't go all serious and write test/edge cases, profile it, or time it (I might time it).



When I said "optimise", I was alluding to the fact that I had slower, worse implementations before (using QList and whatnot).



I have tested with a symbols.txt file resembling



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


and I'm happy to say that I find the time reasonable (approx. 1 second for me?).



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. (But I might try 🤔.)



Are there any "side effects" I should be wary of?



Yes, at around line 133, under the BackgroundHighlighter::clearHighlights() method, I warn how the code 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.



How does the completer work?



The words are loaded from a model in main.cpp. Once the user starts typing, with a word of at least 3 characters, the autocompleter shows up. If the completer isn't showing up for you, try to first implement the example on a standalone/separate app.



How can I change the background colour of the formats?



Go to lines 27 to 29 of backgroundhighlighter.cpp. There, you can see that I centralised the formatting. mainFmt refers to the formatting block directly under the cursor. subsidiaryFmt refers to the formatting blocks on congruent symbols. defaultFmt refers to the default format of the entire document which will be used in resetting the format.



Where can I get symbols.txt and wordlist.txt?



I've attached these on my github stackoverflow repository. You can download and copy them from there.




If anything is amiss, please comment below.






share|improve this answer

























  • 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 mainn return " How is the completion list is triggered? with these files, i wasn't able to trigger it.The highlition works

    – kostas petsis
    Nov 17 '18 at 17:36












  • 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?

    – TrebledJ
    Nov 17 '18 at 17:44












  • Edit: I didnt put right the paths of these 2 files.Now it gets triggered by itself.

    – kostas petsis
    Nov 17 '18 at 17:52











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

    – TrebledJ
    Nov 17 '18 at 17:57











  • 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?

    – kostas petsis
    Nov 17 '18 at 18:04













1












1








1







Note that this answer hasn't yet covered




I would like to have graphics or widgets for custom autocompletion lists.




but I'm looking into it.



Edit:



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;
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


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 his 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)
:"<>?,./;'\-="); // end of word
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
QString completionPrefix = textUnderCursor();

if (!isShortcut && (hasModifier

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 donenn";


// 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 tried my best to



  1. provide sufficient explanation within the comments.


  2. test my solution in the context of all test cases.


  3. optimise the code (not really) for better runtime performance.

What Qt classes are used?



The code above makes use of the Qt Core Module, Qt Widgets Module, and the Qt Gui Module.



Among these, the main ones I used were



  • QTextEdit for subclassing the BackgroundHighlighter class. This is a widget. The signal cursorPositionChanged comes from QTextEdit.


  • QCharTextFormat for formatting blocks of text. Especially with the setBackground() inherited from QTextFormat.



  • QTextCursor for manipulating the cursor (no visual changes), selecting words, and higlighting them.

Some other minor ones were



  • QRegularExpression for symbol matching.


  • QTextDocument for initialising the syntax highlighter.



  • QFile for loading files.

  • and QString (obviously).

Other classes may have been used by the Highlighter and the Custom Completer. These include QSyntaxHighlighter, QCompleter, and a couple event class.



Were there any previous attempts you tried that I should look out for so that I don't fall into the same manhole?



I spent a while trying to find how I might be able to implement it with QTextBlock and QTextBlockFormat. Apparently, QTextBlock only recognises one block as a line. I tried using several blocks per line to handle symbols and even tried using QTextTable to solve the issue. I failed miserably.



How "optimised" is this code?



Note that I didn't go all serious and write test/edge cases, profile it, or time it (I might time it).



When I said "optimise", I was alluding to the fact that I had slower, worse implementations before (using QList and whatnot).



I have tested with a symbols.txt file resembling



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


and I'm happy to say that I find the time reasonable (approx. 1 second for me?).



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. (But I might try 🤔.)



Are there any "side effects" I should be wary of?



Yes, at around line 133, under the BackgroundHighlighter::clearHighlights() method, I warn how the code 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.



How does the completer work?



The words are loaded from a model in main.cpp. Once the user starts typing, with a word of at least 3 characters, the autocompleter shows up. If the completer isn't showing up for you, try to first implement the example on a standalone/separate app.



How can I change the background colour of the formats?



Go to lines 27 to 29 of backgroundhighlighter.cpp. There, you can see that I centralised the formatting. mainFmt refers to the formatting block directly under the cursor. subsidiaryFmt refers to the formatting blocks on congruent symbols. defaultFmt refers to the default format of the entire document which will be used in resetting the format.



Where can I get symbols.txt and wordlist.txt?



I've attached these on my github stackoverflow repository. You can download and copy them from there.




If anything is amiss, please comment below.






share|improve this answer















Note that this answer hasn't yet covered




I would like to have graphics or widgets for custom autocompletion lists.




but I'm looking into it.



Edit:



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;
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


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 his 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)
:"<>?,./;'\-="); // end of word
bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
QString completionPrefix = textUnderCursor();

if (!isShortcut && (hasModifier

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 donenn";


// 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 tried my best to



  1. provide sufficient explanation within the comments.


  2. test my solution in the context of all test cases.


  3. optimise the code (not really) for better runtime performance.

What Qt classes are used?



The code above makes use of the Qt Core Module, Qt Widgets Module, and the Qt Gui Module.



Among these, the main ones I used were



  • QTextEdit for subclassing the BackgroundHighlighter class. This is a widget. The signal cursorPositionChanged comes from QTextEdit.


  • QCharTextFormat for formatting blocks of text. Especially with the setBackground() inherited from QTextFormat.



  • QTextCursor for manipulating the cursor (no visual changes), selecting words, and higlighting them.

Some other minor ones were



  • QRegularExpression for symbol matching.


  • QTextDocument for initialising the syntax highlighter.



  • QFile for loading files.

  • and QString (obviously).

Other classes may have been used by the Highlighter and the Custom Completer. These include QSyntaxHighlighter, QCompleter, and a couple event class.



Were there any previous attempts you tried that I should look out for so that I don't fall into the same manhole?



I spent a while trying to find how I might be able to implement it with QTextBlock and QTextBlockFormat. Apparently, QTextBlock only recognises one block as a line. I tried using several blocks per line to handle symbols and even tried using QTextTable to solve the issue. I failed miserably.



How "optimised" is this code?



Note that I didn't go all serious and write test/edge cases, profile it, or time it (I might time it).



When I said "optimise", I was alluding to the fact that I had slower, worse implementations before (using QList and whatnot).



I have tested with a symbols.txt file resembling



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


and I'm happy to say that I find the time reasonable (approx. 1 second for me?).



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. (But I might try 🤔.)



Are there any "side effects" I should be wary of?



Yes, at around line 133, under the BackgroundHighlighter::clearHighlights() method, I warn how the code 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.



How does the completer work?



The words are loaded from a model in main.cpp. Once the user starts typing, with a word of at least 3 characters, the autocompleter shows up. If the completer isn't showing up for you, try to first implement the example on a standalone/separate app.



How can I change the background colour of the formats?



Go to lines 27 to 29 of backgroundhighlighter.cpp. There, you can see that I centralised the formatting. mainFmt refers to the formatting block directly under the cursor. subsidiaryFmt refers to the formatting blocks on congruent symbols. defaultFmt refers to the default format of the entire document which will be used in resetting the format.



Where can I get symbols.txt and wordlist.txt?



I've attached these on my github stackoverflow repository. You can download and copy them from there.




If anything is amiss, please comment below.







share|improve this answer














share|improve this answer



share|improve this answer








edited Nov 17 '18 at 18:51

























answered Nov 17 '18 at 13:03









TrebledJTrebledJ

3,66421328




3,66421328












  • 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 mainn return " How is the completion list is triggered? with these files, i wasn't able to trigger it.The highlition works

    – kostas petsis
    Nov 17 '18 at 17:36












  • 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?

    – TrebledJ
    Nov 17 '18 at 17:44












  • Edit: I didnt put right the paths of these 2 files.Now it gets triggered by itself.

    – kostas petsis
    Nov 17 '18 at 17:52











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

    – TrebledJ
    Nov 17 '18 at 17:57











  • 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?

    – kostas petsis
    Nov 17 '18 at 18:04

















  • 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 mainn return " How is the completion list is triggered? with these files, i wasn't able to trigger it.The highlition works

    – kostas petsis
    Nov 17 '18 at 17:36












  • 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?

    – TrebledJ
    Nov 17 '18 at 17:44












  • Edit: I didnt put right the paths of these 2 files.Now it gets triggered by itself.

    – kostas petsis
    Nov 17 '18 at 17:52











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

    – TrebledJ
    Nov 17 '18 at 17:57











  • 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?

    – kostas petsis
    Nov 17 '18 at 18:04
















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 mainn return " How is the completion list is triggered? with these files, i wasn't able to trigger it.The highlition works

– kostas petsis
Nov 17 '18 at 17:36






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 mainn return " How is the completion list is triggered? with these files, i wasn't able to trigger it.The highlition works

– kostas petsis
Nov 17 '18 at 17:36














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?

– TrebledJ
Nov 17 '18 at 17:44






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?

– TrebledJ
Nov 17 '18 at 17:44














Edit: I didnt put right the paths of these 2 files.Now it gets triggered by itself.

– kostas petsis
Nov 17 '18 at 17:52





Edit: I didnt put right the paths of these 2 files.Now it gets triggered by itself.

– kostas petsis
Nov 17 '18 at 17:52













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.

– TrebledJ
Nov 17 '18 at 17:57





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.

– TrebledJ
Nov 17 '18 at 17:57













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?

– kostas petsis
Nov 17 '18 at 18:04





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?

– kostas petsis
Nov 17 '18 at 18:04













-1














For styled background of selected words like in the picture in the 1st post edit the backgroundhighlighter.cpp at line 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);


enter image description here



But after the first word the gradients gets lost..I'll try to fix this.Any ideas?






share|improve this answer























  • This should be in a separate question so that others can help along + debug. :-)

    – TrebledJ
    Nov 17 '18 at 18:47







  • 1





    Seems to me like the gradient is anchored at a certain center point.

    – TrebledJ
    Nov 17 '18 at 18:55















-1














For styled background of selected words like in the picture in the 1st post edit the backgroundhighlighter.cpp at line 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);


enter image description here



But after the first word the gradients gets lost..I'll try to fix this.Any ideas?






share|improve this answer























  • This should be in a separate question so that others can help along + debug. :-)

    – TrebledJ
    Nov 17 '18 at 18:47







  • 1





    Seems to me like the gradient is anchored at a certain center point.

    – TrebledJ
    Nov 17 '18 at 18:55













-1












-1








-1







For styled background of selected words like in the picture in the 1st post edit the backgroundhighlighter.cpp at line 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);


enter image description here



But after the first word the gradients gets lost..I'll try to fix this.Any ideas?






share|improve this answer













For styled background of selected words like in the picture in the 1st post edit the backgroundhighlighter.cpp at line 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);


enter image description here



But after the first word the gradients gets lost..I'll try to fix this.Any ideas?







share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 17 '18 at 18:39









kostas petsiskostas petsis

136




136












  • This should be in a separate question so that others can help along + debug. :-)

    – TrebledJ
    Nov 17 '18 at 18:47







  • 1





    Seems to me like the gradient is anchored at a certain center point.

    – TrebledJ
    Nov 17 '18 at 18:55

















  • This should be in a separate question so that others can help along + debug. :-)

    – TrebledJ
    Nov 17 '18 at 18:47







  • 1





    Seems to me like the gradient is anchored at a certain center point.

    – TrebledJ
    Nov 17 '18 at 18:55
















This should be in a separate question so that others can help along + debug. :-)

– TrebledJ
Nov 17 '18 at 18:47






This should be in a separate question so that others can help along + debug. :-)

– TrebledJ
Nov 17 '18 at 18:47





1




1





Seems to me like the gradient is anchored at a certain center point.

– TrebledJ
Nov 17 '18 at 18:55





Seems to me like the gradient is anchored at a certain center point.

– TrebledJ
Nov 17 '18 at 18:55

















draft saved

draft discarded
















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53318233%2fhow-to-display-graphics-objects-behind-or-foreground-of-text-inside-qtextedit-in%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Kleinkühnau

Makov (Slowakei)

Deutsches Schauspielhaus