基于Qt的CodeEditor

首先看最终效果:



主要要实现的地方是行号的显示,还有选中行的高亮。


项目结构


整个程序只有三个文件,最主要的只有一个CodeEditor类,它是继承自QPlainTextEdit,这个类相比于普通的TextEdit更适合于做富 文本编辑器。


头文件

//codeeditor.h
#ifndef CODEEDITOR_H
#define CODEEDITOR_H

#include <QPlainTextEdit>
#include <QObject>

QT_BEGIN_NAMESPACE
class QPaintEvent;
class QResizeEvent;
class QSize;
class QWidget;
QT_END_NAMESPACE

class LineNumberArea;

//![codeeditordefinition]

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:
   

   
       LineNumberArea *lineNumberArea;
};//![codeeditordefinition]//![extraarea]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:


Code Editor *codeEditor;};//![extraarea]#endif


头文件中包含了两个类的定义:继承QPlainTextEdit的CodeEditor和继承QWidget的LineNumberArea。

写在一起的原因:在显示行号的时候需要用到QPlainTextEdit的protected方法,为了方便就直接写死在CodeEditor中。

在编辑器中,当代码的行数发生改变,或者编辑器的大小发生改变,行号都需要重新绘制,为此而创建了两个slot:updateLineNumberWidth() 和 updateLineNumberArea()。


cpp文件

//codeeditor.cpp
#include <QtWidgets>

#include "codeeditor.h"

//![constructor]

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();
}

//![constructor]

//![extraAreaWidth]

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;
}

//![extraAreaWidth]

//![slotUpdateExtraAreaWidth]

void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{ 
    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}

//![slotUpdateExtraAreaWidth]

//![slotUpdateRequest]

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);
}

//![slotUpdateRequest]

//![resizeEvent]

void CodeEditor::resizeEvent(QResizeEvent *e)
{
    QPlainTextEdit::resizeEvent(e);

    QRect cr = contentsRect();
    lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}

//![resizeEvent]

//![cursorPositionChanged]

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);
}

//![cursorPositionChanged]

//![extraAreaPaintEvent_0]

void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
    QPainter painter(lineNumberArea);
    painter.fillRect(event->rect(), Qt::lightGray);

//![extraAreaPaintEvent_0]

//![extraAreaPaintEvent_1]
    QTextBlock block = firstVisibleBlock();
    int blockNumber = block.blockNumber();
    int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
    int bottom = top + (int) blockBoundingRect(block).height();
//![extraAreaPaintEvent_1]

//![extraAreaPaintEvent_2]
    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;
    }
}
//![extraAreaPaintEvent_2]

在cpp中,首先是类的构造函数,初始化私有成员,链接相应的signal和slot。计算linenumbeearea的宽度并高亮第一行也是必须的。

lineNumberAreaWidth():用于计算 LineNumberArea的宽度,取得最大行数,将其位数与数字9的宽度相乘,再加上3的间隔就可以了。

updateLineNumberAreaWidth(int):更新LineNumberArea的宽度,主要用到了setViewportMargins(),设置内边距。当行数不断增加,只要将左边的内边距加大就可以了。

updateLineNumberArea(const QRect &rect, int dy):当有换行出现的时候,对LineNumberArea进行更新

resizeEvent(QResizeEvent *e):当窗口大小变化的时候,LineNumberArea的大小也要进行变化。

highlightCurrentLine():当光标位置发生改变的时候,高亮的位置也发生改变。

lineNumberAreaPaintEvent(QPaintEvent *event):主要负责显示行号,首先是绘制黑色的背景,然后循环绘制行号。在plain text edit中,每一行都只包含一个 QTextBlock。

因此,出现折行的话也不用担心行号不正确。注意要进行两次判断,一次是calid,依次是visible。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值