The program for this example displays a plain TextEdit widget which contains source code. Portions of the text are highlighted in various colors using the GUI syntax highlighting class.
#include <QtCore>
#include <QtGui>
#include "syntax.h"
class MainWindow : public QMainWindow
{
public:
MainWindow();
private:
Syntax *m_syntaxParser;
QString loadText();
};
The MainWindow class declares a private pointer to a Syntax object and a method which is used to load the text displayed in the TextEdit widget. The Syntax class will be created in this example.
MainWindow::MainWindow()
{
setMinimumSize(700, 650);
QWidget *centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
QPlainTextEdit *textEdit = new QPlainTextEdit();
textEdit->setPlainText(loadText());
QFont font = textEdit->font();
font.setPointSize(11);
textEdit->setFont(font);
QPushButton *pb_close = new QPushButton();
pb_close->setText("Close");
QHBoxLayout *layout_text = new QHBoxLayout();
layout_text->setContentsMargins(20, 15, 20, 0);
layout_text->addWidget(textEdit);
QHBoxLayout *layout_pb = new QHBoxLayout();
layout_pb->addStretch();
layout_pb->addWidget(pb_close);
layout_pb->addStretch();
QVBoxLayout *layout = new QVBoxLayout();
layout->addLayout(layout_text);
layout->addSpacing(15);
layout->addLayout(layout_pb);
centralWidget->setLayout(layout);
connect(pb_close, &QPushButton::clicked,
this, &QWidget::close);
m_syntaxParser = new Syntax(textEdit->document());
m_syntaxParser->processSyntax();
}
QString MainWindow::loadText()
{
QString retval = "sample text";
QFile file("../sample.txt");
bool ok = file.open(QIODevice::ReadOnly);
if (ok) {
retval = QString::fromUtf8(file.readAll());
}
return retval;
}
The user interface for this example creates a new QPlainTextEdit and a QPushButton. On line 9 the textEdit is initialized with the contents of the “sample.txt” file when loadText() is called. The code for lines 18 though 32 configure the layout of the controls on the main window.
On line 37 a new Syntax object is created. On the following line is a call to processSyntax(), which is a method of the Syntax class.
The purpose of the Syntax class is to define a set of regular expressions which will be used to determine if a given word should be highlighted.
Syntax Highlighting
The QSyntaxHighlighter class is part of the CopperSpice GUI library. It was designed as an abstract class which is used as a base class in a given application. An abstract class contains at least one pure virtual method. In order to implement syntax highlighting we will create a new class which inherits from QSyntaxHighlighter. The other requirement is to override the virtual highlightBlock() method in the Syntax class.
The header file syntax.h contains the declaration for class Syntax.
// portion of syntax.h
class Syntax : public QSyntaxHighlighter
{
CS_OBJECT(Syntax)
public:
Syntax(QTextDocument *document);
bool processSyntax();
protected:
void highlightBlock(const QString &text) override;
private:
static QByteArray readJson(QString fileName);
struct HighlightingRule {
QRegularExpression pattern;
QTextCharFormat format;
};
QVector<HighlightingRule> highlightingRules;
};
The Syntax header declares a vector to store a structure of rules. Each structure object contains a regular expression and a format indicating how to color the words which match the given regular expression.
The source syntax.cpp contains the implementation for class Syntax.
// portion of syntax.cpp
bool Syntax::processSyntax()
{
QByteArray data = readJson("../syn_cpp.json");
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject object = doc.object();
QJsonArray list;
HighlightingRule rule;
// keywords
list = object.value("keywords").toArray();
rule.format.setFontItalic(false);
rule.format.setForeground(QColor(0,0,255));
for (const auto &item : list) {
rule.pattern = QRegularExpression(item.toString());
highlightingRules.append(rule);
}
// ** highlighting rules for other categories
// ** special rules for quoted text, single line comments
rehighlight();
return true;
}
void Syntax::highlightBlock(const QString &text)
{
QRegularExpressionMatch match;
for (auto &rule : highlightingRules) {
match = rule.pattern.match(text);
while (match.hasMatch()) {
int index = match.capturedStart(0) - text.begin();
int length = match.capturedLength();
setFormat(index, length, rule.format);
// get new match
match = rule.pattern.
match(text, match.capturedEnd(0));
}
}
}
The Syntax cpp file implements two methods. The processSyntax() method reads and parses a json file. It contains a list of regular expression strings which are grouped by different keys. Looking at our code, on line 14 the strings for Keywords are stored in the variable list.
The strings are used by the highlightBlock() method to decipher if a given word matches a particular regular expression. If a match is found the formatting for the given key is used. For example, if the word is a keyword like “constexpr” or “return” then it will be shown in blue as defined on line 17.
On line 28 is a required call to the base class. The rehighlight() method will run the highlighting process on the entire document.
The call to setFormat() on line 44 passes the formatting rules to the base class. QSyntaxHighlighter is responsible for updating the document based on the new formatting definitions.
Build Files
This example introduces the idea of using multiple source files. The syntax highlighting source was separated into a header file (syntax.h) which contains the class declaration and a source file (syntax.cpp) which contains the implementation.
The relevant lines of the build file are shown below. The complete build is available in the zip file.
list(APPEND PROJECT_INCLUDES
${CMAKE_CURRENT_SOURCE_DIR}/syntax.h
)
list(APPEND PROJECT_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/syntax.cpp
)
add_executable(${PROJECT_NAME}
${PROJECT_INCLUDES}
${PROJECT_SOURCES}
)
Main Function
Since the source code for main() has not changed there is no need to show it again. Refer to example 3 or download the full source for this example.
Running the Example
To build and run this example use the same CMake build file and commands as we showed for the first example. The only suggested modification is on line 2 of the CMakeLists.txt file, change “example_1” to “example_34”.