Шаг 50.
Библиотека Qt.
Добавление расцветки синтаксиса в QTextEdit

    На этом шаге рассмотрим добавление расцветки синтаксиса QTextEdit.

    Расцветка и форматирование способствуют более удобному восприятию структуры текста программы. Добавить расцветку синтаксиса в QTextEdit очень просто, для этого нужно унаследовать класс QSyntaxHighlighter и реализовать в унаследованном классе метод highlightBlock().

    Следующий простой пример выделяет цифры в тексте красным цветом:

/*virtual*/ void MyHighlighter::highlightBlock(const QString& str)
{
    for (int i = 0; i < str.length(); ++i) {
        if (str.at(i).isNumber()) {
           setFormat(i, 1, Qt::red);
        }
    }
}

    В метод highlightBlock() передается одна строка текста. В первом параметре метода setFormat() передаем стартовое значение, второй параметр задает количество символов (в нашем случае 1), а в последнем параметре передаем цвет для расцветки (в нашем случае Qt::red). Вместо цвета в последнем параметре можно также передавать и шрифт (QFont).

    Для того чтобы применить объект класса расцветки к объекту QTextEdit, нужно при создании передать ему указатель на объект QTextDocument. Например:

MyHighlighter* pHighlighter = new MyHighlighter(ptxt->document());

    Как видно из примера, для применения расцветки синтаксиса нужен указатель на объект класса QTextDocument, а это значит, что применение расцветки не ограничивается только классом QTextEdit, и ее можно применять ко всем классам, имеющим в своем распоряжении объект класса QTextDocument. В число таких классов входят, например, QTextBrowser, QTextFrame, QTextTable, класс элемента текста QGraphicsTextItem графического представления и другие классы.

    В следующем примере реализуем виджет, который делает расцветку программ на языке C++ в стиле Borland (рис. 1).


Рис.1. Пример расцветки текста

    В листинге приводится текст заголовочного файла:

#ifndef SYNTAXHIGHLIGHTER_H
#define SYNTAXHIGHLIGHTER_H

#include <QSyntaxHighlighter>

class QTextDocument;
//класс SyntaxHighlighter наследуется от класса QSyntaxHighlighter
class SyntaxHighlighter: public QSyntaxHighlighter {
    Q_OBJECT
private:
    QStringList m_lstKeywords;
protected:
    //определены перечисления NormalState,
    //InsideCStyleComment и InsideCString,
    //они нам понадобятся далее для определения текущего состояния фрагмента
    enum { NormalState = -1, InsideCStyleComment, InsideCString };
    //перегружаем метод highlightBlock(), который нам
    //необходим для реализации собственной расцветки синтаксиса
    virtual void highlightBlock(const QString&);
    //метод getKeyword() будет давать ответ на вопрос,
    //является ли строка на указанной позиции ключевым
    //словом языка C++ или определением Qt
    QString getKeyword(int i, const QString& str) const;
public:
    //конструктор
    SyntaxHighlighter(QTextDocument* parent = 0);
};
#endif // SYNTAXHIGHLIGHTER_H

    Приведем текст файла main.cpp:

#include <QApplication>
#include <QTextEdit>
#include <QFont>
#include <QPalette>
#include <QFile>
#include "SyntaxHighlighter.h"
int main (int argc, char** argv)
{
    QApplication app(argc, argv);
    app.setApplicationDisplayName("Расцветка текста");
    //создаем виджет txt класса QTextEdit
    QTextEdit txt;
    //создаем объект шрифта fnt
    QFont fnt("Lucida Console", 9, QFont::Normal);
    //устанавливаем его в нашем редакторе вызовом метода
    //setDefaultFont() из объекта документа редактора
    txt.document()->setDefaultFont(fnt);
    //создаем объект расцветки синтаксиса созданного нами
    //класса SyntaxHighlighter и передаем ему в качестве
    //предка указатель на объект документа нашего редактора
    //этот объект позаботится об его уничтожении при своем разрушении
    new SyntaxHighlighter(txt.document());
    //создаем объект палитры pal, он нам нужен, чтобы
    QPalette pal = txt.palette();
    //установить цвет  фона (темно-синий) по умолчанию
    pal.setColor(QPalette::Base, Qt::darkBlue);
    //установить цвет шрифта (желтый) по умолчанию
    pal.setColor(QPalette::Text, Qt::yellow);
    txt.setPalette(pal);
    //отображаем наш редактор вызовом метода show()
    txt.show();
    //задаем его размеры методом resize()
    txt.resize(740, 480);
    //создаем объект файла с именем SyntaxHighlighter.cpp
    QFile file("SyntaxHighlighter.cpp");
    //открываем файл для чтения
    file.open(QFile::ReadOnly);
    //его текст считывается методом readAll() и
    //устанавливается в редакторе вызовом метода setPlainText()
    txt.setPlainText(QLatin1String(file.readAll()));
    return app.exec();
}

    Приведем текст файла SyntaxHighlighter.cpp:

#include "SyntaxHighlighter.h"
//в конструкторе мы инициализируем объект списка
//m_lstKeywords ключевыми словами
SyntaxHighlighter::SyntaxHighlighter(QTextDocument* parent/*= 0*/)
    : QSyntaxHighlighter(parent)
{
    m_lstKeywords
       << "foreach"   << "bool"    << "int"    << "void"    << "double"
       << "float"     << "char"    << "delete" << "class"   << "const"
       << "virtual"   << "mutable" << "this"   << "struct"  << "union"
       << "throw"     << "for"     << "if"     << "else"    << "false"
       << "namespace" << "switch"  << "case"   << "public"  << "private"
       << "protected" << "new"     << "return" << "using"   << "true"
       << "->"        << ">>"      << "<<"     << ">"       << "<"
       << "("         << ")"       << "{"      << "}"       << "["
       << "]"         << "+"       << "-"      << "*"       << "/"
       << "="         << "!"       << "."      << ","       << ";"
       << ":"         << "&"       << "emit"   << "connect" << "SIGNAL"
       << "|"         << "SLOT"    << "slots"  << "signals";
}
/*В метод highlightBlock() передается только одна строка
текста, но не все концепции синтаксиса ограничиваются одной строкой.
Поэтому мы ввели для этих случаев три состояния:
  • NormalState — нормальное состояние, при котором должна
    использоваться расцветка, задаваемая нашей палитрой;
  • InsideCString — состояние, в котором текущая позиция находится
    внутри строки, в этом случае цвет текста должен быть бирюзовым Qt::cyan;
  • InsideCStyleComment — состояние, когда текущая позиция находится в
    комментарии вида /*...*/, в этом случае цвет текста должен быть
    темно-серым Qt::darkGray.*/
/*virtual*/ void SyntaxHighlighter::highlightBlock(const QString& str) { //текущее состояние устанавливаем в переменной nState, //а получаем его вызовом метода previousBlockState() int nState = previousBlockState(); int nStart = 0; /*самым первым в цикле мы контролируем моменты завершения состояний InsideCStyleComment иInsideCString. Для завершения первого мы проверяем на текущей позиции строку "*/" и, если находим ее, присваиваем переменной nState состояние NormalState и докрашиваем строку в темно-серый цвет, после чего увеличиваем переменную i на единицу, т. к. следующий символ мы уже обработали.*/ for (int i = 0; i < str.length(); ++i) { if (nState == InsideCStyleComment) { if (str.mid(i, 2) == "*/") { nState = NormalState; setFormat(nStart, i - nStart + 2, Qt::darkGray); i++; } } /*в отслеживании наступления состояния конца строки InsideCString мы сначала проверяем наличие символа кавычек (" либо '), а потом следим за тем, чтобы этому символу не предшествовал символ \, т. к. иначе, встретив строку вида \", расцветка синтаксиса неправильно интерпретирует ситуацию и раскрасит косую черту как элемент строки, а кавычку поймет как конец строки. Если последнее контрольное условие выполняется, то строка завершилась, и мы изменяем статус на нормальный и докрашиваем ее последний символ в бирюзовый цвет. */ else if (nState == InsideCString) { if (str.mid(i, 1) == "\"" || str.mid(i, 1) == "\'") { if (str.mid(i - 1, 2) != "\\\"" && str.mid(i - 1, 2) != "\\\'" ) { nState = NormalState; setFormat(nStart, i - nStart + 1, Qt::cyan); } } } else { /*в третьей секции основного if мы контролируем наступления состояний, сравнивая текущую позицию в строке с различными символами, которые должны менять форматирование. Первые три сравнения не проводят смену состояния, т. к. не могут выходить за пределы одной строки. Это однострочный комментарий вида //, директивы препроцессора, начинающиеся с символа #, цифры (определяются с помощью метода QString::isNumber())*/ if (str.mid(i, 2) == "//") { setFormat(i, str.length() - i, Qt::darkGray); break; } else if (str.mid(i, 1) == "#") { setFormat(i, str.length() - i, Qt::green); break; } else if (str.at(i).isNumber()) { setFormat(i, 1, Qt::cyan); } /*для смены состояния на InsideCStyleComment или InsideCString достаточно нахождения на текущей позиции строки "/*" либо символа " соответственно*/ else if (str.mid(i, 2) == "/*") { nStart = i; nState = InsideCStyleComment; } else if (str.mid(i, 1) == "\"" || str.mid(i, 1) == "\'") { nStart = i; nState = InsideCString; } else { /*последнее сравнение не проводит смену состояния, т. к. не может выходить за пределы одной строки. Это ключевые слова языка С++ и определения Qt. Ключевые слова определяются при помощи метода getKeyword(). Этот метод возвращает само ключевое слово, которое он нашел на заданной позиции. Это слово мы раскрашиваем в белый цвет и увеличиваем переменную i на его длину, так обрабатываем все символы слова*/ QString strKeyword = getKeyword(i, str); if (!strKeyword.isEmpty()) { setFormat(i, strKeyword.length(), Qt::white); i += strKeyword.length() - 1; } } } } /*в случае если цикл завершился, и мы находимся в текущем состоянии InsideCStyleComment либо InsideCString, мы закрашиваем наш блок с начала позиции смены состояния и до самого конца*/ if (nState == InsideCStyleComment) { setFormat(nStart, str.length() - nStart, Qt::darkGray); } if (nState == InsideCString) { setFormat(nStart, str.length() - nStart, Qt::cyan); } в завершение мы устанавливаем текущее состояние методом setCurrentState()*/ setCurrentBlockState(nState); } /*назначение метода getKeyword() заключается в нахождении ключевых слов языка C++ и определений Qt*/ QString SyntaxHighlighter::getKeyword(int nPos, const QString& str) const { QString strTemp = ""; /*работаем с объектом списка ключевых слов m_lstKeyword и всякий раз при вызове метода пытаемся найти в цикле foreach совпадение с элементами списка согласно текущей позиции и размера ключевого слова. При удаче ключевое слово присваивается промежуточной строковой переменной strTemp, цикл прерывается (break)*/ foreach (QString strKeyword, m_lstKeywords) { if (str.mid(nPos, strKeyword.length()) == strKeyword) { strTemp = strKeyword; break; } } /*значение этой переменной возвращается в качестве результата */ return strTemp; }

    Файлы приложения можно взять здесь.

    На следующем шаге рассмотрим виджет абстрактного счетчика — класс QAbstractSpinBox.




Предыдущий шаг Содержание Следующий шаг