Playing around with QHighlighter
published at 09.10.2024 16:52 by Jens Weller
Save to Instapaper Pocket
Thinking about conference t-shirt design for Meeting C++ 2024, I started to play around with the text highlighting abilities in Qt. I've started with the QHighlighter example.
The example is a great introduction, and pretty much what I need for prototyping. I wonder if there is a better way then using regular expressions, but for now I've stuck to the highlighter class from the example. My use case does not require high performance, but using a boyer moore horspool searcher might work very well for keywords.
But I couldn't resist to extend the highlighter class to cover more keywords:
keywordFormat.setForeground(Qt::darkBlue); keywordFormat.setFontWeight(QFont::Bold); const QString keywordPatterns[] = { QStringLiteral("\\bauto\\b"),QStringLiteral("\\bbool\\b"), QStringLiteral("\\bchar\\b"), QStringLiteral("\\bclass\\b"), QStringLiteral("\\bconst\\b"),QStringLiteral("\\bconstexpr\\b"),QStringLiteral("\\bconcept\\b"),QStringLiteral("\\bco_await\\b"),QStringLiteral("\\bco_return\\b"),QStringLiteral("\\bconsteval\\b"), QStringLiteral("\\bdelete\\b"), QStringLiteral("\\bdouble\\b"),QStringLiteral("\\bdo\\b"), QStringLiteral("\\benum\\b"), QStringLiteral("\\bexplicit\\b"),QStringLiteral("\\belse\\b"), QStringLiteral("\\bfriend\\b"),QStringLiteral("\\bfloat\\b"),QStringLiteral("\\bfor\\b"), QStringLiteral("\\binline\\b"), QStringLiteral("\\bint\\b"),QStringLiteral("\\bif\\b"), QStringLiteral("\\blong\\b"), QStringLiteral("\\bnamespace\\b"), QStringLiteral("\\bnew\\b"), QStringLiteral("\\bnullptr\\b"), QStringLiteral("\\boperator\\b"),QStringLiteral("\\boverride\\b"), QStringLiteral("\\bprivate\\b"), QStringLiteral("\\bprotected\\b"), QStringLiteral("\\bpublic\\b"), QStringLiteral("\\bshort\\b"), QStringLiteral("\\bsignals\\b"), QStringLiteral("\\bsigned\\b"), QStringLiteral("\\bslots\\b"), QStringLiteral("\\bstatic\\b"), QStringLiteral("\\bstruct\\b"),QStringLiteral("\\bshort\\b"), QStringLiteral("\\btemplate\\b"), QStringLiteral("\\bthis\\b"),QStringLiteral("\\btypedef\\b"), QStringLiteral("\\btypename\\b"), QStringLiteral("\\bunion\\b"),QStringLiteral("\\busing\\b"), QStringLiteral("\\bunsigned\\b"), QStringLiteral("\\bvirtual\\b"), QStringLiteral("\\bvoid\\b"), QStringLiteral("\\bvolatile\\b"),QStringLiteral("\\bwhile\\b") }; for (const QString &pattern : keywordPatterns) { rule.pattern = QRegularExpression(pattern); rule.format = keywordFormat; highlightingRules.append(rule); } classFormat.setFontWeight(QFont::Bold); classFormat.setForeground(Qt::darkMagenta); rule.pattern = QRegularExpression(QStringLiteral("\\bQ[A-Za-z]+\\b")); rule.format = classFormat; highlightingRules.append(rule); preprocessorFormat.setFontWeight(QFont::Bold); preprocessorFormat.setForeground(Qt::darkBlue); rule.pattern = QRegularExpression(QStringLiteral("\\#[A-Za-z]+")); rule.format = preprocessorFormat; highlightingRules.append(rule);
I assume the keyword list is still incomplete. I've added the preprocessor regex so that #include & co works. Which shows that for some part a rule based parser/regex is needed for highlighting.
I'm trying to use this in a different context, seeing the highlighter work in a QTextEdit is nice, but I'm not sure if I should print that on a t-shirt. So what if one could actually use this in a QPainter? One needs to know a little bit about Qts APIs to connect this. After all even if QTextEdit had a method allowing you to pass in a QPainter as its context, it would draw the QTextEdit likely. But the QTextDocument class has a drawContent method with QPainter as its argument, and actually this is what is passed from the QTextEdit to the highlighter:
QFont font; font.setFamily("Courier"); font.setFixedPitch(true); font.setPointSize(10); //editor = new QTextEdit; editor = ui->editor; editor->setFont(font); highlighter = new Highlighter(editor->document()); editor->setPlainText("#include \n\n//some tests\nclass Pair{int x,y;};\n bool operator==(const Pair& p1,const Pair p2){return p1.x == p2.x && p1.y == p2.y;}\nconstexpr std::string s{\"test bool const\"};"); ui->paintpanel->setDoc(editor->document());
So if we had a different Widget, all that is needed is to pass in the pointer to the text document used by the QTextEdit. Then one can paint the text with a QPainter in a Paint event outside of the QTextEdit:
void PaintPanel::paintEvent(QPaintEvent *event) { QPainter painter(this); QBrush b(Qt::lightGray); painter.setBrush(b); painter.setPen(Qt::darkGreen); painter.drawRect(50,10,400,200); if(doc) { doc->setTextWidth(300); doc->drawContents(&painter); } }
PaintPanel is a QWidget derived class demonstrating this. I was happy to see it working at first, but then started wondering on how to actually use this. As the background color in both widgets is white, I was not sure if drawContent would also paint a background color. For that reason I paint a rectangle first, to test if it does. Which it doesn't. For a prototype this is far enough. Its usability is limited, as drawContent allows to pass in a rectangle, but that only makes it paint the text that maps with that rectangle. You can't set a point/offset where the painting should start. Likely you'd have to do that either with the QPainter instance, or paint into a QPixmap/QImage which then later gets painted by another QPainter on a different surface.
But being able to simply add needed keywords and set their properties in the text is interesting.
In the end this is what the prototype looks like:
For the moment I'm happy with this.
Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!