Пример "Code Editor"Файлы:
Пример "Code Editor" показывает, как создать простой редактор с нумерацией строк и подсветкой текущей строки. Как можно видеть на рисунке, редактор выводит номер строки в области слева от области редактирования. Редактор подсвечивает строку содержащую курсор. Мы реализовали редактор в CodeEditor, который является виджетом унаследованным от QPlainTextEdit. У нас имеется отдельный виджет в CodeEditor (LineNumberArea), на котором отрисовываем номер строки. QPlainTextEdit унаследовано от QAbstractScrollArea, а редактирование происходит внутри границ его viewport()'а. Место для номера строки мы получили установив левое поле области просмотра такого размера, который нужен для отрисовки номера строки. Когда доходит до редактирования кода , мы предпочитаем QPlainTextEdit перед QTextEdit поскольку он лучше оптимизирован для обработки обычного текста. За подробностями обращайтесь к описанию класса QPlainTextEdit. QPlainTextEdit даёт нам возможность добавить выделения в дополнение к выделению, которое может делать пользователь с помощью мыши или клавиатуры. Эту функциональность мы используем для подсветки текущей строки. Позднее будет больше подробностей. Теперь можно перейти к определениям и реализациям CodeEditor и LineNumberArea. Давайте начнём с класса LineNumberArea. Класс LineNumberAreaМы нарисуем номера строк на этом виджете и поместим их над CodeEditor'ом, над областью левого поля viewport()'а. При рисовании области нам нужны защищённые функции в QPlainTextEdit. Чтобы не усложнять положение вещей, мы рисуем область в классе CodeEditor. Область также запрашивает редактор о вычислении своей подсказки размера. Обратите внимание на то, что мы можем просто нарисовать номер строки непосредственно в редакторе кода и исключить класс LineNumberArea. Однако, класс QWidget помогает нам прокручивать - scroll() - свое содержимое Кроме того, отдельные виджеты - правильный выбор, если мы хотим расширить редактор точками останова или другими возможностями редактора кода. Тогда виджет поможет в обработке событий мыши. class LineNumberArea : public QWidget { public: LineNumberArea(CodeEditor *editor) : QWidget(editor) { codeEditor = editor; } QSize sizeHint() const { return QSize(codeEditor->lineNumberAreaWidth(), 0); } protected: void paintEvent(QPaintEvent *event) { codeEditor->lineNumberAreaPaintEvent(event); } private: CodeEditor *codeEditor; }; Определение класса CodeEditorВот определение класса редактора кода: class CodeEditor : public QPlainTextEdit { Q_OBJECT public: CodeEditor(QWidget *parent = 0); void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth(); protected: void resizeEvent(QResizeEvent *event); private slots: void updateLineNumberAreaWidth(int newBlockCount); void highlightCurrentLine(); void updateLineNumberArea(const QRect &, int); private: QWidget *lineNumberArea; }; В редакторе мы изменяем размер и рисуем номера строк на LineNumberArea. Нам нужно делать это когда в редакторе изменились номера строк, и когда прокручивается область просмотра viewport() редактора. Разумеется, делается это также при изменении размера редактора. Выполняем это в updateLineNumberWidth() и updateLineNumberArea(). Всякий раз когда изменяется позиция курсора, мы подсвечиваем текущую строку в highlightCurrentLine(). Реализация класса CodeEditorТеперь рассмотрим реализацию редактора кода, начав с конструктора. CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent) { lineNumberArea = new LineNumberArea(this); connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); updateLineNumberAreaWidth(0); highlightCurrentLine(); } Соединяем в конструкторе наши слоты с сигналами в QPlainTextEdit. Это необходимо для вычисления ширины области номера строки и подсветки первой строки при создании редактора. int CodeEditor::lineNumberAreaWidth() { int digits = 1; int max = qMax(1, blockCount()); while (max >= 10) { max /= 10; ++digits; } int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits; return space; } Функция lineNumberAreaWidth() вычисляет ширину виджета LineNumberArea. Получаем количество цифр в последней строке редактора и умножаем на максимальную ширину цифры. void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */) { setViewportMargins(lineNumberAreaWidth(), 0, 0, 0); } При обновлении ширины области номера строки мы просто вызываем QAbstractScrollArea::setViewportMargins(). void CodeEditor::updateLineNumberArea(const QRect &rect, int dy) { if (dy) lineNumberArea->scroll(0, dy); else lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); if (rect.contains(viewport()->rect())) updateLineNumberAreaWidth(0); } Этот слоты вызывается при прокрутке области просмотра редактора. Передаваемый в качестве аргумента QRect является частью области редактирования, которая будет обновляться (перерисовываться). dy хранит количество пикселей области просмотра, которые были прокручены вертикально. void CodeEditor::resizeEvent(QResizeEvent *e) { QPlainTextEdit::resizeEvent(e); QRect cr = contentsRect(); lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height())); } При изменении размера редактора, также нужно изменить размер области номера строки. void CodeEditor::highlightCurrentLine() { QList<QTextEdit::ExtraSelection> extraSelections; if (!isReadOnly()) { QTextEdit::ExtraSelection selection; QColor lineColor = QColor(Qt::yellow).lighter(160); selection.format.setBackground(lineColor); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = textCursor(); selection.cursor.clearSelection(); extraSelections.append(selection); } setExtraSelections(extraSelections); } При изменении позиции курсора подсвечиваем текущую строку, т.е., строку содержащую курсор. QPlainTextEdit даёт возможность иметь одновременно более одного выделения. Мы можем установить формат символов (QTextCharFormat) этих выделений. Очищаем выделение курсора перед установкой нового QPlainTextEdit::ExtraSelection, иначе несколько строк будут подсвечены когда пользователь выберет мышью несколько строк. Он же устанавливает выделение с текстовым курсором. При использовании свойства FullWidthSelection, будет выбран текущий блок курсора (строки). Если вы хотите выбрать только часть текстового блока, курсор нужно передвинуть с помощью QTextCursor::movePosition() от позиции установленной с помощью setPosition(). void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event) { QPainter painter(lineNumberArea); painter.fillRect(event->rect(), Qt::lightGray); lineNumberAreaPaintEvent() вызывается из LineNumberArea всякий раз при получении события рисования. Начинаем рисование фона виджета. QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); int bottom = top + (int) blockBoundingRect(block).height(); Теперь пройдёмся по всем видимым строкам и отрисуем для каждой строки номер в дополнительной области. Заметьте, что при редактировании обычного текста каждая строка будет состоять из одного QTextBlock; тем не менее, если включен перенос строк, то строка может занимать несколько строк в области просмотра редактора текста. Получим верхнюю и нижнюю y-координаты первого текстового блока, и подгоним эти значения к высоте текущего текстового блока в каждой итерации цикла. while (block.isValid() && top <= event->rect().bottom()) { if (block.isVisible() && bottom >= event->rect().top()) { QString number = QString::number(blockNumber + 1); painter.setPen(Qt::black); painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number); } block = block.next(); top = bottom; bottom = top + (int) blockBoundingRect(block).height(); ++blockNumber; } } Заметьте, что мы проверяем - виден ли блок - в дополнение к проверке на вхождение в область просмотра - блок может, например, скрыт окном, размещенным над текстовым редактором. Пожелания по расширению редактора кодаЛюбой уважаемый редактор кода содержит подсветку синтаксиса; Пример "Syntax Highlighter" показывает, как его создать. В дополнение к номерам строк вы можете добавить дополнительную область, например, для точек останова. QSyntaxHighlighter с помощью setCurrentBlockUserData() даёт возможность добавить пользовательские данные к каждому текстовому блоку. Этим можно воспользоваться для реализации соответствия пар скобок. В highlightCurrentLine(), данные текущего блока currentBlock() могут быть получены с помощью QTextBlock::userData(). Парные скобки можно подсвечивать с помощью дополнительного выделения. The "Matching Parentheses with QSyntaxHighlighter" article in Qt Quarterly 31 implements this. You find it here: http://doc.qt.nokia.com/qq/. The line number area is now painted every time the cursor blinks (because we connect updateRequest() to updateLineNumberArea()). We can avoid this by introducing a new member variable to CodeEditor that keeps track of when the update request comes from a cursor blink (in which case we do not repaint). The code below requires the m_countCache variable, which is a QPair<int, int> initialized with -1 for both first and second. void CodeEditor::updateLineNumberArea(const QRect &rect, int dy) { if (dy) { lineNumberArea->scroll(0, dy); } else if (m_countCache.first != blockCount() || m_countCache.second != textCursor().block().lineCount()) { lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); m_countCache.first = blockCount(); m_countCache.second = textCursor().block().lineCount(); } if (rect.contains(viewport()->rect())) updateLineNumberAreaWidth(0); } |
Попытка перевода Qt документации. Если есть желание присоединиться, или если есть замечания или пожелания, то заходите на форум: Перевод Qt документации на русский язык... Люди внесшие вклад в перевод: Команда переводчиков |