【QT】带行号以及缩略图显示的十六进制QPlainTextEdit

QT自带有一个Example codeeditor可以显示行号。修改了一下,用于显示16进制数据,并且增加了缩略图显示。效果如下:

类名:CHexEditor

接口: 

  1. void setData(quint8 *data, quint32 len)  // 设置待显示数据指针以及长度
  2. void setLineNumBgColor(QColor color) // 行号背景色
  3. void setLineNumTextColor(QColor color) // 行号文本色
  4. void setLineNumHighlightEnabled(bool flag) // 行号是否高亮
  5.  void setLineNumHighlightBold(bool flag) // 行号高亮时背景色
  6. void setLineNumHighlightBold(bool flag)  // 行号高亮时文本是否加粗
  7.  void setThumbnailAreaTextColor(QColor color) // 缩略图文本色
  8. void setThumbnail(bool enable) // 是否启用缩略图

主文件:mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "chexeditor.h"
#include <QRandomGenerator>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setContentsMargins(2, 2, 2, 2);

    CHexEditor *m_hexEdit = new CHexEditor(this);

    m_hexEdit->setWordWrapMode(QTextOption::NoWrap);
    m_hexEdit->setLineNumHighlightEnabled(true);
    m_hexEdit->setFont(QFont("JetBrains Mono", 12));
    m_hexEdit->setThumbnail(true);
    m_hexEdit->setReadOnly(true);

    // 设置字体色
    setStyleSheet("CHexEditor {color: #e0e0e0; background-color: #303030; }");

    setCentralWidget(m_hexEdit);

    quint8 testArr[70] = {0};
    for (int i=0; i<sizeof(testArr); i++)
        testArr[i] = QRandomGenerator::global()->bounded(0, 255);

    m_hexEdit->setData(&testArr[0], sizeof(testArr));
}

CHexEditor.cpp

#include "chexeditor.h"
#include <QPainter>
#include <QTextBlock>
#include <QByteArray>
#include <QScrollBar>

CHexEditor::CHexEditor(QWidget *parent) : QPlainTextEdit(parent)
{
    // 创建行号区对象
    m_lineNumberArea = new LineNumberArea(this);
    m_lineNumberArea->setCursor(Qt::ArrowCursor);

    // 创建缩略图对象
    m_thumbnailArea = new ThumbnailArea(this);
    m_thumbnailArea->setCursor(Qt::OpenHandCursor);

    viewport()->setCursor(Qt::PointingHandCursor); // 更改文本区鼠标指针
    setCursorWidth(2);                             // 光标宽度

    updateLineNumberAreaWidth(0);

    connect(this, &CHexEditor::blockCountChanged, this, &CHexEditor::updateLineNumberAreaWidth);
    connect(this, &CHexEditor::updateRequest,     this, &CHexEditor::updateLineNumberArea);
    connect(this, &CHexEditor::cursorPositionChanged, this, &CHexEditor::highlightCurrentLine);
}

CHexEditor::~CHexEditor()
{
    if (m_data)
    {
        free(m_data);
        m_dataSize = 0;
    }
}

// 重新计算行号区域宽度,当行数目变化时,blockCountChanged事件触发本函数计算。
void CHexEditor::updateLineNumberAreaWidth(int newBlockCount)
{
    // PlainEdit 默认视口Margin为0,0,0,0,表示滚动区边缘到左、顶、右、底的距离
    // 左侧设置为行号区域宽度,则左侧行号区等同于从视口滚动区剥离,
    // 主体文字将不会显示在左侧被剥离的区域。

    // 每行显示输出 16 * 3 Hex数字
    QString strMaxLine(48, QChar('F'));
    QRect lineRect = fontMetrics().boundingRect(strMaxLine);

    int vScrollBarWidth = verticalScrollBar()->isVisible() ? verticalScrollBar()->rect().width() : 0;
    int rightMargin = m_hasThumbnail ? (contentsRect().width() - lineRect.width() - lineNumberAreaWidth() - vScrollBarWidth - 2) : 0;

    setViewportMargins(lineNumberAreaWidth(), 0, rightMargin, 0);

    // 右侧视口margin改变后会导致textEdit横向滚动条无法出现,设置一下滚动条最大宽度
    if (m_hasThumbnail)
        viewport()->setMaximumWidth(lineRect.width() + rightMargin);
}

int CHexEditor::lineNumberAreaWidth()
{
    return m_lineAreaPaddingLeft + m_lineAreaPaddingRight +
                fontMetrics().horizontalAdvance(QLatin1Char('9')) * 8;
}

// updateReqest信号触发本函数
// 文本卷动时,rect包含整个视口区域,
// 当文本垂直卷动时,dy参数携带视口卷动的像素数。
void CHexEditor::updateLineNumberArea(const QRect &rect, int dy)
{
    if (dy) // scrolled vertically
    {
        m_lineNumberArea->scroll(0, dy);  // 行号区同步滚动dy像素
        if (m_hasThumbnail)
            m_thumbnailArea->scroll(0, dy);
    }
    else
    {
        m_lineNumberArea->update(0, rect.y(), m_lineNumberArea->width(), rect.height());
        if (m_hasThumbnail)
            m_thumbnailArea->update();//0, rect.y(), 300, rect.height());
    }

    if (rect.contains(viewport()->rect()))
        updateLineNumberAreaWidth(0);
}

void CHexEditor::highlightCurrentLine()
{
    QList<QTextEdit::ExtraSelection> extraSelections;

    if (!isReadOnly()) {
        QTextEdit::ExtraSelection selection;

        selection.format.setBackground(m_currLineHighlightColor);
        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
        selection.cursor = textCursor();
        selection.cursor.clearSelection();
        extraSelections.append(selection);
    }

    setExtraSelections(extraSelections);
}

void CHexEditor::paintEvent(QPaintEvent *event)
{
    QPlainTextEdit::paintEvent(event);

    if (!m_hasThumbnail)
        return;

    QPainter dc(viewport());

    // 开启反锯齿
    dc.setRenderHint(QPainter::Antialiasing, true);

    // 右侧缩略图虚线位置y坐标
    int splitLineY = viewport()->rect().right();

    QPen verticalLinePen = QPen(QColor(Qt::gray));
    verticalLinePen.setStyle(Qt::DotLine);
    dc.setPen(verticalLinePen);
    // 绘制左侧行号与正文分隔线
    //dc.drawLine(verticalLineX, contentsRect().top(), verticalLineX, contentsRect().bottom());
    // 绘制右侧提示线
    dc.drawLine(splitLineY, contentsRect().top(), splitLineY, contentsRect().bottom());
}

// PlainEdit大小变化时,同步更新行号区域大小
void CHexEditor::resizeEvent(QResizeEvent *e)
{
    QPlainTextEdit::resizeEvent(e);

    QString strMaxLine(48, QChar('F'));
    QRect lineRect = fontMetrics().boundingRect(strMaxLine);
    QRect cr = contentsRect();

    // 设定行号区大小
    m_lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));

    // viewPort 同时设置左右两侧区域时,似乎有bug(仅设置左侧行号区域无问题)
    // 因此resize这里增加重新设置viewPort Margin,但重设viewPort Margin,又会引起resize事件
    // 会引起循环,加入一些保护条件
    static bool lastVScrollBarVisiableFlag = verticalScrollBar()->isVisible();

    if (verticalScrollBar()->isVisible() == false)
    {
        updateLineNumberAreaWidth(0);
        lastVScrollBarVisiableFlag = false;

        // 设定缩略图区大小
        if (m_hasThumbnail)
            m_thumbnailArea->setGeometry(
                QRect(cr.left() + lineNumberAreaWidth() + viewport()->width(),
                      cr.top(), (cr.width() - lineNumberAreaWidth() - viewport()->rect().width()),
                      cr.height()));
    }
    else
    {
        if (verticalScrollBar()->isVisible() != lastVScrollBarVisiableFlag)
        {
            updateLineNumberAreaWidth(0);
            lastVScrollBarVisiableFlag = verticalScrollBar()->isVisible();

            // 设定缩略图区大小
            if (m_hasThumbnail)
                m_thumbnailArea->setGeometry(
                    QRect(cr.left() + lineNumberAreaWidth() + viewport()->width(),
                          cr.top(),
                          (cr.width() - lineNumberAreaWidth() - viewport()->rect().width()),
                          cr.height()));
        }
    }
}

void CHexEditor::lineNumberAreaMouseEvent(QMouseEvent *event)
{
    QWidget::mousePressEvent(event);
}

void CHexEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
{
    QPainter painter(m_lineNumberArea);
    painter.setRenderHint(QPainter::Antialiasing, true);

    // 绘制行号默认背景
    painter.fillRect(event->rect(), m_lineNumBgColor);

    QTextBlock block = firstVisibleBlock();
    int blockNumber = block.blockNumber();
    int top    = qRound(blockBoundingGeometry(block).translated(contentOffset()).top());
    int height = qRound(blockBoundingRect(block).height());
    int bottom = top + height;

    int currLineNum = textCursor().blockNumber();
    while (block.isValid() && top <= event->rect().bottom()) {
        if (block.isVisible() && bottom >= event->rect().top()) {
            int lineNum = blockNumber + 1;

            // 当前行号背景高亮与否
            if (m_lineNumHighlightEnabled && blockNumber == currLineNum)
            {
                QRect highLightAreaRect(0, top, m_lineNumberArea->width(), fontMetrics().height() + 1);
                painter.fillRect(highLightAreaRect, m_lineNumHighlightBgColor);
            }

            // 当前行的行号字体加粗与否
            if (m_lineNumHighlightBold && blockNumber == currLineNum)
            {
                QFont font = painter.font();
                font.setBold(true);
                painter.setFont(font);
            }

            painter.setPen(m_lineNumTextColor);
            // 右对齐方式绘制行号
            painter.drawText(0, top, m_lineNumberArea->width() - m_lineAreaPaddingRight, fontMetrics().height(),
                             Qt::AlignRight, QString("%1").arg(blockNumber << 4, 8, 16, QChar('0')));
        }

        block = block.next();
        top = bottom;
        height = qRound(blockBoundingRect(block).height());
        bottom = top + height;
        ++blockNumber;
    }
}

void CHexEditor::thumbnailAreaPaintEvent(QPaintEvent *event)
{
    if (!m_hasThumbnail)
        return;

    if (!m_data)
        return;

    QPainter painter(m_thumbnailArea);
    painter.setRenderHint(QPainter::Antialiasing, true);

    // 绘制缩略图区默认背景
    painter.fillRect(event->rect(), palette().brush(QPalette::Base).color());
    painter.setPen(m_thumbnailAreaTextColor);

    QTextBlock block = firstVisibleBlock();
    int blockNumber = block.blockNumber();
    int top    = qRound(blockBoundingGeometry(block).translated(contentOffset()).top());
    int height = qRound(blockBoundingRect(block).height());
    int bottom = top + height;

    while (block.isValid() && top <= event->rect().bottom()) {
        if (block.isVisible() && bottom >= event->rect().top()) {
            // 右对齐方式绘制char字符
            int loopFlag = 16;

            if ((blockNumber + 1) * 16 > m_dataSize)
                loopFlag = m_dataSize % 16;

            QString strChar;
            for (int i=0; i<loopFlag; i++)
            {
                if ((blockNumber * 16 + i) >= m_dataSize)
                    break;

                if (m_data[blockNumber * 16 + i] < 32 || m_data[blockNumber * 16 + i] > 127)
                    strChar = QString(".");
                else
                    strChar = QString::asprintf("%c", m_data[blockNumber * 16 + i]);

                painter.drawText(8 + fontMetrics().horizontalAdvance('9') * i,
                                 top,
                                 fontMetrics().horizontalAdvance('9'),
                                 fontMetrics().height(),
                                 Qt::AlignRight, strChar);
            }
        }

        block = block.next();
        top = bottom;
        height = qRound(blockBoundingRect(block).height());
        bottom = top + height;
        ++blockNumber;
    }
}

void CHexEditor::setData(quint8 *data, quint32 len)
{
    if (!data || !len)
        return;

    if (m_data)
        free(m_data);

    m_data = (quint8 *)malloc(len);
    if (!m_data)
        return;
    m_dataSize = len;

    memcpy(m_data, data, len);

    QString strData;
    for (int i=0; i<len; i++)
    {
        if ((i & 0xf) == 0xf)
            strData += QString("%1\n").arg(data[i], 2, 16, QChar('0'));
        else
            strData += QString("%1 ").arg(data[i], 2, 16, QChar('0'));
    }

    appendPlainText(strData);

    moveCursor(QTextCursor::Start);
}

chexeditor.h

#ifndef CHEXEDITOR_H
#define CHEXEDITOR_H

#include <QPlainTextEdit>
#include <QObject>
#include <QWidget>

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

class LineNumberArea;
class ThumbnailArea;

class CHexEditor : public QPlainTextEdit
{
    Q_OBJECT
public:
    CHexEditor(QWidget *parent = nullptr);
    ~CHexEditor();

    int lineNumberAreaWidth();
    // 处理行号区域鼠标点击事件
    void lineNumberAreaMouseEvent(QMouseEvent *event);
    // 处理行号区重绘事件
    void lineNumberAreaPaintEvent(QPaintEvent *event);
    // 缩略图区重绘事件
    void thumbnailAreaPaintEvent(QPaintEvent *event);

    // 缩略图设置
    void setThumbnail(bool enable) { m_hasThumbnail = enable; }

    // 行号高亮设置
    void setLineNumHighlightBold(bool flag)        { m_lineNumHighlightBold = flag; };
    void setLineNumHighlightEnabled(bool flag)     { m_lineNumHighlightEnabled = flag; };
    void setLineNumHightlightBgColor(QColor color) { m_lineNumHighlightBgColor = color; };

    // 行号颜色设置
    void setLineNumBgColor(QColor color)   { m_lineNumBgColor   = color; };
    void setLineNumTextColor(QColor color) { m_lineNumTextColor = color; };

    // 设置Hex文本数据
    void setData(quint8 *data, quint32 len);

    //
    void setThumbnailAreaTextColor(QColor color) { m_thumbnailAreaTextColor = color; };

protected:
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;

private slots:
    void updateLineNumberAreaWidth(int newBlockCount);
    void highlightCurrentLine();
    void updateLineNumberArea(const QRect &rect, int dy);

private:
    QWidget *m_lineNumberArea = nullptr;  // 行号区对象
    QWidget *m_thumbnailArea  = nullptr;   // 缩略图区对象

    quint8 *m_data     = nullptr;
    quint32 m_dataSize = 0;

    bool m_hasThumbnail = true;       //是否显示右侧缩略图
    // 缩略图区文字颜色
    QColor m_thumbnailAreaTextColor = QColor(0xc0, 0xc0, 0xc0);

    int m_lineAreaPaddingLeft  = 5;   //行号文字左侧留5像素画标记
    int m_lineAreaPaddingRight = 5;   //行号文字右侧留5像素画标记

    // 高亮行背景色
    QColor m_currLineHighlightColor = QColor(0x30, 0x50, 0x80).lighter();

    // 行号区背景色
    QColor m_lineNumBgColor = QColor(0x40, 0x40, 0x40); //QColor(0x60, 0x50, 0x60);
    // 行号文本颜色
    QColor m_lineNumTextColor  = QColor(Qt::yellow).lighter();
    // 行号是否高亮
    bool m_lineNumHighlightEnabled = false;
    // 行号高亮颜色
    QColor m_lineNumHighlightBgColor = m_currLineHighlightColor; //Qt::blue;
    // 当前行行号是否加粗显示
    bool m_lineNumHighlightBold = false;
};




class LineNumberArea : public QWidget
{
public:
    LineNumberArea(CHexEditor *editor) : QWidget(editor), m_hexEditor(editor)
    {
    }

    QSize sizeHint() const override
    {
        return QSize(m_hexEditor->lineNumberAreaWidth(), 0);
    }

protected:
    void paintEvent(QPaintEvent *event) override
    {
        m_hexEditor->lineNumberAreaPaintEvent(event);
    }

    void mousePressEvent(QMouseEvent *event) override
    {
        m_hexEditor->lineNumberAreaMouseEvent(event);
    }

private:
    CHexEditor *m_hexEditor;
};

class ThumbnailArea : public QWidget
{
public:
    ThumbnailArea(CHexEditor *editor) : QWidget(editor), m_hexEditor(editor)
    {

    }

    QSize sizeHint() const override
    {
        return QSize(0, 0); //QSize(m_hexEditor->lineNumberAreaWidth(), 0);
    }
protected:
    void paintEvent(QPaintEvent *event) override
    {
        m_hexEditor->thumbnailAreaPaintEvent(event);
    }

private:
    CHexEditor *m_hexEditor;
};
#endif // CHEXEDITOR_H

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值