CodeEditor Class Implementation
We will now go through the code editors implementation, starting off with the constructor.
CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent)
{
lineNumberArea = new LineNumberArea(this);
connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
connect(this, SIGNAL(updateRequest(const QRect &, int)), this, SLOT(updateLineNumberArea(const QRect &, int)));
connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));
updateLineNumberAreaWidth(0);
highlightCurrentLine();
}
In the constructor we connect our slots to signals in QPlainTextEdit. It is necessary to calculate the line number area width and highlight the first line when the editor is created.
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;
}
The lineNumberAreaWidth() function calculates the width of the LineNumberArea widget. We take the number of digits in the last line of the editor and multiply that with the maximum width of a digit.
void CodeEditor::updateLineNumberAreaWidth(int )
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
When we update the width of the line number area, we simply call 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);
}
This slot is invoked when the editors viewport has been scrolled. The QRect given as argument is the part of the editing area that is do be updated (redrawn). dy holds the number of pixels the view has been scrolled vertically.
void CodeEditor::resizeEvent(QResizeEvent *e)
{
QPlainTextEdit::resizeEvent(e);
QRect cr = contentsRect();
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
When the size of the editor changes, we also need to resize the line number area.
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);
}
When the cursor position changes, we highlight the current line, i.e., the line containing the cursor.
QPlainTextEdit gives the possibility to have more than one selection at the same time. we can set the character format (QTextCharFormat) of these selections. We clear the cursors selection before setting the new new QPlainTextEdit::ExtraSelection, else several lines would get highlighted when the user selects multiple lines with the mouse.
One sets the selection with a text cursor. When using the FullWidthSelection property, the current cursor text block (line) will be selected. If you want to select just a portion of the text block, the cursor should be moved with QTextCursor::movePosition() from a position set with setPosition().
void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
QPainter painter(lineNumberArea);
painter.fillRect(event->rect(), Qt::lightGray);
The lineNumberAreaPaintEvent() is called from LineNumberArea whenever it receives a paint event. We start off by painting the widget's background.
QTextBlock block = firstVisibleBlock();
int blockNumber = block.blockNumber();
int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
int bottom = top + (int) blockBoundingRect(block).height();
We will now loop through all visible lines and paint the line numbers in the extra area for each line. Notice that in a plain text edit each line will consist of one QTextBlock; though, if line wrapping is enabled, a line may span several rows in the text edit's viewport.
We get the top and bottom y-coordinate of the first text block, and adjust these values by the height of the current text block in each iteration in the loop.
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;
}
}
Notice that we check if the block is visible in addition to check if it is in the areas viewport - a block can, for example, be hidden by a window placed over the text edit.
Suggestions for Extending the Code Editor
No self-respecting code editor is without a syntax highligther; the Syntax Highlighter Example shows how to create one.
In addition to line numbers, you can add more to the extra area, for instance, break points.
QSyntaxHighlighter gives the possibility to add user data to each text block with setCurrentBlockUserData(). This can be used to implement parenthesis matching. In the highlightCurrentLine(), the data of the currentBlock() can be fetched with QTextBlock::userData(). Matching parentheses can be highlighted with an extra selection.