На этом шаге рассмотрим добавление расцветки синтаксиса 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() передается только одна строка
текста, но не все концепции синтаксиса ограничиваются одной строкой.
Поэтому мы ввели для этих случаев три состояния:/*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; }
- NormalState — нормальное состояние, при котором должна
использоваться расцветка, задаваемая нашей палитрой;- InsideCString — состояние, в котором текущая позиция находится
внутри строки, в этом случае цвет текста должен быть бирюзовым Qt::cyan;- InsideCStyleComment — состояние, когда текущая позиция находится в
комментарии вида /*...*/, в этом случае цвет текста должен быть
темно-серым Qt::darkGray.*/
Файлы приложения можно взять здесь.
На следующем шаге рассмотрим виджет абстрактного счетчика — класс QAbstractSpinBox.