Qt 富文本处理(23): Syntax Highlighter Example【来自官档的翻译】

Syntax Highlighter Example 官方案例

语法高亮案例显示了如何执行简单的语法突出显示。

语法高亮应用程序显示了C ++文件使用自定义的语法高亮。
该示例包含两个类:

  • Highlighter类定义并应用突出显示规则。

  • MainWindow小部件是应用程序的主窗口。

  我们将首先查看Highlighter类,以了解如何根据自己的喜好自定义QSyntaxHighlighter类,然后再看一下MainWindow类的相关部分,以了解如何在应用程序中使用自定义荧光笔类。

Highlighter类的定义

要提供自己的语法突出显示,必须扩展QSyntaxHighlighter类,重新实现highlightBlock()函数,并定义自己的突出显示规则。

class Highlighter : public QSyntaxHighlighter
{
    Q_OBJECT
public:
    Highlighter(QTextDocument *parent = 0);

protected:
    void highlightBlock(const QString &text) override;

private:

    /* 使用私有结构存储突出显示规则:规则由QRegularExpression和
     * QTextCharFormat的实例组成。
     * 并使用QVector存储各种规则。*/
    struct HighlightingRule
    {
        QRegularExpression pattern;
        QTextCharFormat format;
    };

    QVector<HighlightingRule> highlightingRules;

    // 跨行的表达式需要特殊处理
    QRegularExpression commentStartExpression;
    QRegularExpression commentEndExpression;

    /* QTextCharFormat类提供QTextDocument中字符的格式设置信息,
     * 以指定文本的视觉属性,以及有关其在超文本文档中的作用的信息。
     * QTextCharFormat::setFontWeight()定义字体粗细
     * QTextCharFormat::setForeground()函数定义颜色。*/
    QTextCharFormat keywordFormat;
    QTextCharFormat classFormat;
    QTextCharFormat singleLineCommentFormat;
    QTextCharFormat multiLineCommentFormat;
    QTextCharFormat quotationFormat;
    QTextCharFormat functionFormat;
};

Highlighter类的实现

  子类化QSyntaxHighlighter类时,必须将父参数传递给基类构造函数。 父级是将在其上应用语法突出显示的文本文档。 在此示例中,我们还选择在构造函数中定义突出显示规则:

#include "Highlighter.h"

Highlighter::Highlighter(QTextDocument *parent)
    : QSyntaxHighlighter(parent)
{
    HighlightingRule rule;

/****************************************************************************
*  首先,我们定义关键字规则,该规则可以识别最常见的C++关键字。
* 我们为keywordFormat提供了深蓝色粗体。
* 对于每个关键字,将关键字和指定的格式分配给HighlightingRule对象,然后将该对象附加到规则列表中。
*
* 使用QRegularExpression类的正则,对于正则需要有点了解就行。
* "\\bchar\\b":\\b就是\b的转移序列,意识为单词的边界,中间的char就是关键词
*/
    keywordFormat.setForeground(Qt::darkBlue);
    keywordFormat.setFontWeight(QFont::Bold);
    const QString keywordPatterns[] = {
        QStringLiteral("\\bchar\\b"),       QStringLiteral("\\bclass\\b"),      QStringLiteral("\\bconst\\b"),
        QStringLiteral("\\bdouble\\b"),     QStringLiteral("\\benum\\b"),       QStringLiteral("\\bexplicit\\b"),
        QStringLiteral("\\bfriend\\b"),     QStringLiteral("\\binline\\b"),     QStringLiteral("\\bint\\b"),
        QStringLiteral("\\blong\\b"),       QStringLiteral("\\bnamespace\\b"),  QStringLiteral("\\boperator\\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("\\btemplate\\b"),   QStringLiteral("\\btypedef\\b"),    QStringLiteral("\\btypename\\b"),
        QStringLiteral("\\bunion\\b"),      QStringLiteral("\\bunsigned\\b"),   QStringLiteral("\\bvirtual\\b"),
        QStringLiteral("\\bvoid\\b"),       QStringLiteral("\\bvolatile\\b"),   QStringLiteral("\\bbool\\b")
    };
    for (const QString &pattern : keywordPatterns) {
        rule.pattern = QRegularExpression(pattern);
        rule.format = keywordFormat;
        highlightingRules.append(rule);
    }

/***************************************************************************
 * 然后,我们创建一种将应用于Qt类名称的格式。类名将以深洋红色和粗体显示。
 * 我们指定一个字符串模式,它实际上是一个捕获所有Qt类名的正则表达式。
 * 然后,我们将正则表达式和指定的格式分配给HighlightingRule对象,并将该对象附加到规则列表中。*/
    classFormat.setFontWeight(QFont::Bold);
    classFormat.setForeground(Qt::darkMagenta);
    rule.pattern = QRegularExpression(QStringLiteral("\\bQ[A-Za-z]+\\b"));
    rule.format = classFormat;
    highlightingRules.append(rule);

    /* 定义引号高亮规则:模式具有正则表达式的形式 例如: "......"
     * 并以相关格式存储在HighlightingRule对象中。 */
    quotationFormat.setForeground(Qt::darkGreen);
    rule.pattern = QRegularExpression(QStringLiteral("\".*\""));
    rule.format = quotationFormat;
    highlightingRules.append(rule);

    /* 定义函数的高亮规则:模式具有正则表达式的形式,例如:My_func(
     * 并以相关格式存储在HighlightingRule对象中。
     * regex 只检测了左括号的存在,即认为是函数名*/
    functionFormat.setFontItalic(true);
    functionFormat.setForeground(Qt::blue);
    rule.pattern = QRegularExpression(QStringLiteral("\\b[A-Za-z0-9_]+(?=\\()"));
    rule.format = functionFormat;
    highlightingRules.append(rule);

    // 单行注释的高亮(//)
    singleLineCommentFormat.setForeground(Qt::red);
    rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*"));
    rule.format = singleLineCommentFormat;
    highlightingRules.append(rule);

    // 多行注释的高亮(/*...*/)
    multiLineCommentFormat.setForeground(Qt::red);
    // 多行注释的开始:/*
    commentStartExpression = QRegularExpression(QStringLiteral("/\\*"));
    // 多行注释的结束:/*
    commentEndExpression   = QRegularExpression(QStringLiteral("\\*/"));

    /* 由于QSyntaxHighlighter类的设计,多行注释需要特别注意。
     * 创建QSyntaxHighlighter对象后,富文本引擎会在需要时自动调用其highlightBlock()函数,突出显示给定的文本块。
     * 当注释跨越多个文本块时,将出现问题。
     * 解决方法放在 Highlighter :: highlightBlock()函数的实现时,
     * 此时,我们仅指定多行注释的颜色。*/
}


/* highlightBlock()函数会在富文本引擎需要时自动调用,例如,当文本块发生变化时。*/
void Highlighter::highlightBlock(const QString &text)
{
    /* 首先,应用存储在highlightingRules向量中的语法高亮显示规则。*/

    // 检索每个规则(即每个HighlightingRule对象)
    for (const HighlightingRule &rule : qAsConst(highlightingRules)) {

        //使用QRegularExpression::globalMatch()函数在给定的文本块中搜索模式。
        QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);

        while (matchIterator.hasNext()) {

            // 当发现模式出现时,获取该模式
            QRegularExpressionMatch match = matchIterator.next();

            //使用 QRegularExpressionMatch::capturedStart()函数来确定将要格式化的字符串的起始位置。
            //使用 QRegularExpressionMatch::capturedLength()返回匹配字符串的长度,如果没有匹配,则返回0。
            //使用 QSyntaxHighlighter类提供了setFormat()函数执行格式化

            setFormat(match.capturedStart(), match.capturedLength(), rule.format);

           /* 为了执行实际的格式化,QSyntaxHighlighter类提供了setFormat()函数。这个函数对作为参数传递给highlightBlock()函数的文本块进行操作。
            * 指定的格式将从给定的起始位置应用于给定长度的文本。在显示时,以给定格式设置的格式化属性将与直接存储在文档中的格式化信息合并。
            * 注意,文档本身不会被这个函数设置的格式修改。 重复此过程,直到在当前文本块中找到该模式的最后一次出现为止。*/
        }
    }

/******************************************************************************************************
 * 要处理可以跨越多个文本块的构造(如c++多行注释),有必要知道前一个文本块的结束状态(例如,注释)。在highlightBlock()中,
 * 使用QSyntaxHighlighter::previousBlockState()函数查询前一个文本块的结束状态。
 * 解析块之后,可以使用QSyntaxHighlighter::setCurrentBlockState()保存最后的状态。
 *
 * 这个previousBlockState()函数返回一个int值。 如果未设置任何状态,则返回值为-1。
 * 使用setCurrentBlockState()函数指定任何其他值来标识任何给定状态。
 * 设置状态后,QTextBlock会保留该值,直到再次设置该值或删除相应的文本段落为止。
 *
 * 在本例中,我们选择使用0表示“不在注释中”状态,使用1表示“在注释中”状态。当应用存储的语法高亮显示规则时,我们将当前块状态初始化为0。*/

    setCurrentBlockState(0);

/******************************************************************************************************
 * 查找起始表达式的位置
 *
 * 如果先前的块状态为“处于注释中”(previousBlockState()== 1),则在文本块的开头开始搜索结束表达式。
 * 如果previousBlockState()返回0,则查找首次出现起始表达式的位置*/
    int startIndex = 0;
    if (previousBlockState() != 1)
        startIndex = text.indexOf(commentStartExpression);

//接着查找结束表达式的位置,因为跨行,所以需要循环查找。
    while (startIndex >= 0) {
        QRegularExpressionMatch match = commentEndExpression.match(text, startIndex);
        int endIndex = match.capturedStart();
        int commentLength = 0;
//如果在当前文本块中找不到结束表达式,则将当前块状态设置为1,并计算当前注释文本的长度
        if (endIndex == -1) {
            setCurrentBlockState(1);
            commentLength = text.length() - startIndex;
        }
//如果在当前文本块中找到结束表达式,则计算注释文本的实际长度
        else {

            commentLength = endIndex - startIndex + match.capturedLength();
        }
//应用多行注释格式。
        setFormat(startIndex, commentLength, multiLineCommentFormat);
//然后,我们搜索下一个出现的开始表达式并重复该过程。
        startIndex = text.indexOf(commentStartExpression, startIndex + commentLength);
/* 这样就完成了Highlighter类的实现。*/
    }
}

MainWindow类的定义

使用QSyntaxHighlighter子类很简单; 只需为您的应用程序提供类的实例,然后将您想要突出显示的文档传递给它。

 class MainWindow : public QMainWindow
 {
     Q_OBJECT

 public:
     MainWindow(QWidget *parent = 0);

 public slots:
     void about();
     void newFile();
     void openFile(const QString &path = QString());

 private:
     void setupEditor();
     void setupFileMenu();
     void setupHelpMenu();

     QTextEdit *editor;
     Highlighter *highlighter;
 };

在此示例中,我们声明了一个Highlighter实例的指针,稍后将在私有setupEditor()函数中对其进行初始化。

MainWindow类的实现

#include "MainWindow.h"

/****************************************************************
 * 主窗口的构造函数很简单。
 * 我们首先设置菜单,然后初始化编辑器,并将其设置为应用程序的中央小部件。
 * 最后,我们设置主窗口的标题。*/
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    setupFileMenu();
    setupHelpMenu();
    setupEditor();

    setCentralWidget(editor);
    setWindowTitle(tr("Syntax Highlighter"));
}


void MainWindow::about()
{
    QMessageBox::about(this, tr("About Syntax Highlighter"),
                tr("<p>The <b>Syntax Highlighter</b> example shows how " \
                   "to perform simple syntax highlighting by subclassing " \
                   "the QSyntaxHighlighter class and describing " \
                   "highlighting rules using regular expressions.</p>"));
}

void MainWindow::newFile()
{
    editor->clear();
}

void MainWindow::openFile(const QString &path)
{
    QString fileName = path;

    if (fileName.isNull())
        fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "", "C++ Files (*.cpp *.h)");

    if (!fileName.isEmpty()) {
        QFile file(fileName);
        if (file.open(QFile::ReadOnly | QFile::Text))
            editor->setPlainText(file.readAll());
    }
}

/****************************************************************
 * 在私有setupEditor()便捷函数中初始化并安装Highlighter对象:*/
void MainWindow::setupEditor()
{

/*    首先,我们创建要在编辑器中使用的字体,然后我们创建编辑器本身,它是QTextEdit类的实例。
 *    在使用MainWindow类定义文件初始化编辑器之前,先创建一个Highlighter实例,将编
 * 辑器的文档作为参数传递。语法高亮将应用于文档。 
 *    然后我们就完成了。
 *
 * QSyntaxHighlighter对象一次只能安装在一个文档上,但是您可以使用:
 * setDocument()函数轻松地将荧光笔重新安装在另一文档上。QSyntaxHighlighter类还提供了
 * document()函数,该函数返回当前设置的文档。*/
    QFont font;
    font.setFamily("Courier");
    font.setFixedPitch(true);
    font.setPointSize(12);

    editor = new QTextEdit;
    editor->setFont(font);

    highlighter = new Highlighter(editor->document());

    QFile file("mainwindow.h");
    if (file.open(QFile::ReadOnly | QFile::Text))
        editor->setPlainText(file.readAll());
}

void MainWindow::setupFileMenu()
{
    QMenu *fileMenu = new QMenu(tr("&File"), this);
    menuBar()->addMenu(fileMenu);

    fileMenu->addAction(tr("&New"), this,
                        &MainWindow::newFile, QKeySequence::New);
    fileMenu->addAction(tr("&Open..."),
                        this, [this](){ openFile(); }, QKeySequence::Open);
    fileMenu->addAction(tr("E&xit"), qApp,
                        &QApplication::quit, QKeySequence::Quit);
}

void MainWindow::setupHelpMenu()
{
    QMenu *helpMenu = new QMenu(tr("&Help"), this);
    menuBar()->addMenu(helpMenu);

    helpMenu->addAction(tr("&About"), this, &MainWindow::about);
    helpMenu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值