关键代码
CodeEditor.h 文件
#ifndef CODEEDITOR_H
#define CODEEDITOR_H
#include <QPlainTextEdit>
#include <QPaintEvent>
#include <QContextMenuEvent>
#include <QMouseEvent>
#include <QMouseEvent>
#include <QPainter>
#include <QTextBlock>
#include <QFontMetrics>
#include <QTextCursor>
#include <QTextBlockFormat>
#include <QTextDocument>
#include <QTextCodec>
#include <QMenu>
#include <QScrollBar>
QString GetQString(QByteArray byteArray);//防止乱码
enum TagType
{
NUMBER=0,
MARKROUND,//圆
MARKBLOCK //方块
};
class TagLineNumberArea;
class TagDataItem
{
public:
TagDataItem(TagType tagTypeTmp, int widthTmp = 20):
tagType(tagTypeTmp), width(widthTmp)
{
}
bool BLineNumbersMark(int number)
{
//return std::find(vecLineNumbersMark.begin(), vecLineNumbersMark.end(),number) != vecLineNumbersMark.end();
return mapMarkLineColor.find(number) != mapMarkLineColor.end();
}
void markerAdd(int line, QColor color=Qt::black)
{
mapMarkLineColor[line] = color;
}
QColor getLineColor(int line)
{
return mapMarkLineColor[line];
}
TagType tagType;
int positionX;
int width;
private:
std::map<int, QColor> mapMarkLineColor;
};
class CodeEditor:public QPlainTextEdit
{
Q_OBJECT
public:
CodeEditor(QWidget *paren=0);
~CodeEditor();
virtual void PaintTag(QPainter& painter, int line, int y);
void AddTag(TagDataItem* item);//添加标记或行号
void UpdateColor(int line, int startIndex, int endIndex, QColor fontColor, QColor backColor);
void UpdateColor(int startLine, int endLine, int startColumn, int endColumn, QColor fontColor, QColor backColor);//更新行字体或背景色
void UpdateLineNumberWidth(int lineNumberWidthNew);//更新行号宽度
void UpdateTagWidth(int lineNumberWidthOld, int lineNumberWidth);//更新行号后面宽度
void GoToLine(int line);//跳转至行
int GetLineNumber(QTextCursor &cursor);//获取行号
void lineNumberAreaPaintEvent(QPaintEvent* event);//绘制
int lineNumberAreaWidth();//计算行号宽度
protected:
virtual void resizeEvent(QResizeEvent *event)override;
private slots:
void updateLineNumberAreaWidth(int newBlockCount);
void hightlightCurrentLine();
void updateLineNumberArea(const QRect &rect, int dy);
void updateLineNumber();
public:
TagLineNumberArea *tagLineNumberArea;
int currentLineNumber;
int currentLinePositionY;
std::vector<TagDataItem*> vecTagDataItem;
QColor backgroundColor;
QColor fontColor;
QColor fontCurrentColor;
int lineNumberPositionX;
int lineNumberWidth;
int tagCurrentWidth;
int spacing;
bool bLineNumber;
bool bHightCurrentLineNumber;
};
class TagLineNumberArea: public QWidget
{
public:
TagLineNumberArea(CodeEditor *editor):QWidget(editor), codeEditor(editor)
{
}
QSize sizeHint()const override
{
return QSize(codeEditor->lineNumberAreaWidth(), 0);
}
protected:
void paintEvent(QPaintEvent* event)override
{
codeEditor->lineNumberAreaPaintEvent(event);
}
public:
CodeEditor* codeEditor;
};
#endif // CODEEDITOR_H
CodeEditor.cpp 文件
#include "CodeEditor.h"
CodeEditor::CodeEditor(QWidget *parent):QPlainTextEdit(parent)
{
setMouseTracking(true);//获取鼠标移动
setWordWrapMode(QTextOption::NoWrap);//自动换行, 会导致获取的行号有问题
backgroundColor = QColor(240,240,240);
fontColor = QColor(160,160,160);
fontCurrentColor = QColor(0,0,0);
lineNumberPositionX = 0;
tagCurrentWidth = 0;
lineNumberWidth = 0;
spacing = 5;
bLineNumber = false;
bHightCurrentLineNumber = true;
tagLineNumberArea = new TagLineNumberArea(this);
connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth);
connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea);
//connect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::hightlightCurrentLine);
UpdateLineNumberWidth(0);
}
CodeEditor::~CodeEditor()
{
}
void CodeEditor::PaintTag(QPainter &painter, int line, int y)
{
for (TagDataItem* item:vecTagDataItem)
{
switch (item->tagType)
{
case NUMBER:
break;
case MARKROUND:
if(item->BLineNumbersMark(line))
{
painter.setPen(QColor(item->getLineColor(line)));
painter.setBrush(QColor(item->getLineColor(line)));
int w = item->width < fontMetrics().height() ? item->width : fontMetrics().height();
painter.drawEllipse(item->positionX + item->width/2 - w/2, y + fontMetrics().height()/2 - w/2,
w, w);
}
break;
case MARKBLOCK:
if(item->BLineNumbersMark(line))
{
painter.setPen(QColor(item->getLineColor(line)));
painter.setBrush(QColor(item->getLineColor(line)));
painter.drawRect(item->positionX, y, item->width, fontMetrics().height());
}
break;
default:
break;
}
}
}
void CodeEditor::AddTag(TagDataItem *item)
{
item->positionX = tagCurrentWidth + spacing;
vecTagDataItem.push_back(item);
tagCurrentWidth = tagCurrentWidth + spacing + item->width;
if(!bLineNumber && item->tagType == NUMBER)
{
lineNumberPositionX = item->positionX;
lineNumberWidth = item->width;
bLineNumber = true;
}
}
void CodeEditor::UpdateColor(int line, int startIndex, int endIndex, QColor fontColor, QColor backColor)
{
QTextCursor cursor = this->textCursor();
//移动行
cursor.movePosition(QTextCursor::Start);
for(int i=1; i<line; i++)
{
cursor.movePosition(QTextCursor::Down);
}
//移动列
cursor.movePosition(QTextCursor::StartOfBlock);
for(int i=1; i<startIndex; i++)
{
cursor.movePosition(QTextCursor::NextCharacter);
}
if(endIndex == 0)
{
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
}
else
{
for(int i=1; i<endIndex; i++)
{
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}
}
QTextCharFormat fmt;
fmt.setForeground(QBrush(fontColor));//字体色
fmt.setBackground(QBrush(backColor));//背景色
cursor.setCharFormat(fmt);
}
void CodeEditor::UpdateColor(int startLine, int endLine, int startColumn, int endColumn, QColor fontColor, QColor backColor)
{
QTextCursor cursor = this->textCursor();
//移动行
cursor.movePosition(QTextCursor::Start);
for(int i=1; i<startLine; i++)
{
cursor.movePosition(QTextCursor::Down);
}
//移动列
cursor.movePosition(QTextCursor::StartOfBlock);
for(int i=1; i<startColumn; i++)
{
cursor.movePosition(QTextCursor::NextCharacter);
}
//移动行
cursor.movePosition(QTextCursor::Start);
for(int i=startLine; i<endLine; i++)
{
cursor.movePosition(QTextCursor::Down, QTextCursor::KeepAnchor);
}
//移动列
cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
for(int i=0; i<endColumn; i++)
{
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
}
QTextCharFormat fmt;
fmt.setForeground(QBrush(fontColor));//字体色
fmt.setBackground(QBrush(backColor));//背景色
cursor.setCharFormat(fmt);
}
void CodeEditor::UpdateLineNumberWidth(int lineNumberWidthNew)
{
int width = tagCurrentWidth - lineNumberWidth + lineNumberWidthNew;
tagCurrentWidth = width;
UpdateTagWidth(lineNumberWidth, lineNumberWidthNew);
lineNumberWidth = lineNumberWidthNew;
}
void CodeEditor::UpdateTagWidth(int lineNumberWidthOld, int lineNumberWidth)
{
bool numberAfterTag = false;
for(TagDataItem* item: vecTagDataItem)
{
if(item->tagType == NUMBER)
{
numberAfterTag = true;
}
if(numberAfterTag)
{
item->positionX = item->positionX - lineNumberWidthOld + lineNumberWidth;
}
}
}
void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
void CodeEditor::hightlightCurrentLine()
{
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);
}
void CodeEditor::updateLineNumberArea(const QRect &rect, int dy)
{
if (dy)
tagLineNumberArea->scroll(0, dy);
else
tagLineNumberArea->update(0, rect.y(), tagLineNumberArea->width(), rect.height());
if (rect.contains(viewport()->rect()))
updateLineNumberAreaWidth(0);
}
void CodeEditor::updateLineNumber()
{
tagLineNumberArea->update();
}
void CodeEditor::GoToLine(int line)
{
QTextCursor textCursor = this->textCursor();
int position = document()->findBlockByNumber(line - 1).position();
textCursor.setPosition(position, QTextCursor::MoveAnchor);
setTextCursor(textCursor);
this->verticalScrollBar()->setValue(line-1);
}
int CodeEditor::GetLineNumber(QTextCursor &cursor)
{
QTextLayout *layout = cursor.block().layout();
int pos = cursor.position() - cursor.block().position();
int line = layout->lineForTextPosition(pos).lineNumber() + cursor.block().firstLineNumber();
return line;
}
int CodeEditor::lineNumberAreaWidth()
{
if(!bLineNumber)
return tagCurrentWidth;
int digits = 1;
int max = qMax(1, blockCount());
while(max >= 10)
{
max /=10;
++digits;
}
int lineNumberWidth = 3 + fontMetrics().horizontalAdvance(QLatin1Char('9'))*digits;
UpdateLineNumberWidth(lineNumberWidth);
return tagCurrentWidth;
}
void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
//当前行
QTextCursor cursor = textCursor();
int line = GetLineNumber(cursor);
currentLineNumber = line;
currentLinePositionY = -1;
//背景
QPainter painter(tagLineNumberArea);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.fillRect(event->rect(), backgroundColor);
//
QTextBlock block = firstVisibleBlock();
int blockNumber = block.blockNumber();
int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top());
int bottom = top + qRound(blockBoundingRect(block).height());
while(block.isValid() && top <= event->rect().bottom())
{
if(block.isValid() && bottom >= event->rect().top())
{
QString number = QString::number(blockNumber +1);
if(blockNumber == line)
currentLinePositionY = top;//记录当前位置
//记载行号
if(bLineNumber)
{
if(blockNumber == line && bHightCurrentLineNumber)
{
painter.setPen(fontCurrentColor);//当前行号高亮
QFont ft = painter.font();
ft.setBold(true);
painter.setFont(ft);
}
else
{
painter.setPen(fontColor);
}
painter.drawText(lineNumberPositionX, top, lineNumberWidth, fontMetrics().height(),
Qt::AlignRight, number);
}
//绘制tag
PaintTag(painter, blockNumber, top);
}
block = block.next();
top = bottom;
bottom = top + qRound(blockBoundingGeometry(block).height());
++blockNumber;
}
}
void CodeEditor::resizeEvent(QResizeEvent *e)
{
QPlainTextEdit::resizeEvent(e);
QRect cr = contentsRect();
tagLineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
QString GetQString(QByteArray byteArray)
{
QTextCodec::ConverterState state;
QTextCodec *codec = QTextCodec::codecForName("UTF-8");
QString qstr = codec->toUnicode(byteArray.constData(), byteArray.size(), &state);
if(state.invalidChars > 0)
qstr = QTextCodec::codecForName("GBK")->toUnicode(byteArray);
else {
qstr = byteArray;
}
return qstr;
}
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "CodeEditor.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
CodeEditor *edit = new CodeEditor;
TagDataItem* item = new TagDataItem(MARKBLOCK, 10);
item->markerAdd(2, Qt::blue);
item->markerAdd(5, Qt::yellow);
item->markerAdd(526, Qt::red);
item->markerAdd(527, Qt::black);
edit->AddTag(item);
TagDataItem *itemNumber = new TagDataItem(NUMBER, 20);
edit->AddTag(itemNumber);
TagDataItem *item2 = new TagDataItem(MARKBLOCK, 10);
item2->markerAdd(5, Qt::blue);
item2->markerAdd(6, Qt::yellow);
item2->markerAdd(7, Qt::red);
item2->markerAdd(8, Qt::red);
item2->markerAdd(526, Qt::red);
item2->markerAdd(527, Qt::red);
edit->AddTag(item2);
this->setCentralWidget(edit);
QFont ft;
ft.setFamily("微软雅黑");
ft.setPointSize(10);
edit->setFont(ft);
QFile file("C:/Data/test.cpp");
qDebug() << file.exists();
if(file.open(QIODevice::ReadOnly))
{
edit->appendPlainText(file.readAll());
file.close();
}
edit->GoToLine(520);
edit->UpdateColor(1,3,2, 5,Qt::red, Qt::yellow);
edit->UpdateColor(2,3,2, 5,Qt::red, Qt::yellow);
}
MainWindow::~MainWindow()
{
}