OSDN Git Service

バイナリビューアを実装、他ビューアも自分描画に変更
[gefu/Gefu.git] / hexview.cpp
diff --git a/hexview.cpp b/hexview.cpp
new file mode 100644 (file)
index 0000000..57f7b6f
--- /dev/null
@@ -0,0 +1,285 @@
+#include "preferences.h"
+#include "hexview.h"
+
+#include <QApplication>
+#include <QClipboard>
+#include <QDebug>
+#include <QPainter>
+#include <QPaintEvent>
+#include <QScrollArea>
+
+const int HEXCHARS_IN_LINE = 3 * 16;
+const int BYTES_PER_LINE = 16;
+const int GAP_ADR_HEX = 1;
+const int GAP_HEX_ASCII = 3;
+
+HexView::HexView(QScrollArea *parent) :
+    QWidget(parent)
+{
+    m_scrollArea = parent;
+    this->setObjectName("hexView");
+    m_scrollArea->setWidget(this);
+
+    resetSelection(0);
+}
+
+void HexView::setData(const QByteArray &data)
+{
+    m_data = data;
+    adjust();
+}
+
+int HexView::addressChars() const
+{
+    return QString("%1").arg(m_data.size(), 0, 16).length();
+}
+
+void HexView::adjust()
+{
+    int lines = (m_data.size() / BYTES_PER_LINE) + 1;
+    setMinimumHeight((lines + 1) * m_charHeight);
+
+    int addressArea = (addressChars() + GAP_ADR_HEX) * m_charWidth;
+    int hexArea = HEXCHARS_IN_LINE * m_charWidth;
+    int asciiArea = (GAP_HEX_ASCII + BYTES_PER_LINE) * m_charWidth;
+    setMinimumWidth(addressArea + hexArea + asciiArea);
+}
+
+int HexView::cursorPos(const QPoint &pos)
+{
+    int result = -1;
+    if (xPosHex() <= pos.x() && pos.x() < xPosAscii()) {
+        int x = (pos.x() - xPosHex()) / m_charWidth;
+        if (x % 3 == 0) {
+            x /= 3;
+        }
+        else {
+            x = (x / 3) + 1;
+        }
+        int y = pos.y() / m_charHeight;
+
+        result = x + y * BYTES_PER_LINE;
+
+        qDebug() << x << y << result;
+    }
+
+    return result;
+}
+
+void HexView::resetSelection(int index)
+{
+    m_selectionBegin = index;
+    m_selectionEnd = index;
+    m_selectionInit = index;
+
+    emit copyAvailable(false);
+}
+
+void HexView::setSelection(int index)
+{
+    if (index > m_selectionInit) {
+        m_selectionBegin = m_selectionInit;
+        m_selectionEnd = index;
+    }
+    else {
+        m_selectionBegin = index;
+        m_selectionEnd = m_selectionInit;
+    }
+
+    emit copyAvailable(m_selectionBegin != m_selectionEnd);
+}
+
+int HexView::xPosHex() const
+{
+    return m_charWidth * (addressChars() + GAP_ADR_HEX);
+}
+
+int HexView::xPosAscii() const
+{
+    return xPosHex() + m_charWidth * (HEXCHARS_IN_LINE + GAP_HEX_ASCII);
+}
+
+void HexView::onCopy()
+{
+    QString selected;
+    for (int idx = m_selectionBegin; idx < m_selectionEnd; ++idx) {
+        quint8 c = static_cast<quint8>(m_data[idx]);
+        selected.append(QString("%1 ").arg(c, 2, 16, QChar('0')));
+    }
+
+    QClipboard *clipboard = qApp->clipboard();
+    clipboard->setText(selected.trimmed());
+}
+
+void HexView::onScaleDown()
+{
+    Preferences prefs(this);
+    QFont font = prefs.getHexViewFont();
+    font.setPointSize(font.pointSize() - 1);
+    prefs.setHexViewFont(font);
+
+    setVisible(true);
+    adjust();
+    update();
+}
+
+void HexView::onScaleUp()
+{
+    Preferences prefs(this);
+    QFont font = prefs.getHexViewFont();
+    font.setPointSize(font.pointSize() + 1);
+    prefs.setHexViewFont(font);
+
+    setVisible(true);
+    adjust();
+    update();
+}
+
+void HexView::onSelectAll()
+{
+    resetSelection(0);
+    setSelection(m_data.size());
+
+    update();
+}
+
+void HexView::setVisible(bool visible)
+{
+    if (visible) {
+        Preferences prefs(this);
+        QPalette pal = this->palette();
+        pal.setColor(this->backgroundRole(), prefs.getHexViewBgColor());
+        pal.setColor(this->foregroundRole(), prefs.getHexViewFgColor());
+        this->setPalette(pal);
+        this->setAutoFillBackground(true);
+        this->setFont(prefs.getHexViewFont());
+
+        m_charHeight = fontMetrics().height();
+        m_charWidth = fontMetrics().width('9');
+    }
+
+    QWidget::setVisible(visible);
+}
+
+void HexView::mousePressEvent(QMouseEvent *e)
+{
+    if (e->button() == Qt::LeftButton) {
+        int cPos = cursorPos(e->pos());
+        resetSelection(cPos);
+
+        update();
+    }
+}
+
+void HexView::mouseDoubleClickEvent(QMouseEvent *e)
+{
+    if (e->button() == Qt::LeftButton) {
+        int cPos = cursorPos(e->pos());
+        int lineHead = (cPos / BYTES_PER_LINE) * BYTES_PER_LINE;
+        resetSelection(lineHead);
+        setSelection(lineHead + BYTES_PER_LINE);
+
+        update();
+    }
+}
+
+void HexView::mouseMoveEvent(QMouseEvent *e)
+{
+    m_scrollArea->ensureVisible(e->x(), e->y());
+
+    int cPos = cursorPos(e->pos());
+    if (cPos != -1) {
+        setSelection(cPos);
+        update();
+    }
+}
+
+void HexView::keyPressEvent(QKeyEvent *)
+{
+}
+
+void HexView::paintEvent(QPaintEvent *e)
+{
+    QPainter painter(this);
+
+    // 描画対象となるデータの範囲
+    int firstLineIdx = ((e->rect().top() / m_charHeight) - m_charHeight) * BYTES_PER_LINE;
+    if (firstLineIdx < 0)
+        firstLineIdx = 0;
+    int lastLineIdx = ((e->rect().bottom() / m_charHeight) + m_charHeight) * BYTES_PER_LINE;
+    if (lastLineIdx > m_data.size())
+        lastLineIdx = m_data.size();
+    // 描画開始位置
+    int yPosStart = (firstLineIdx / BYTES_PER_LINE) * m_charHeight + m_charHeight;
+
+    // アドレスエリア
+    QRect addressRect(e->rect());
+    addressRect.setLeft(0);
+    addressRect.setWidth(m_charWidth * addressChars());
+    painter.fillRect(addressRect, Qt::gray);
+    painter.setPen(Qt::black);
+    for (int lineIdx = firstLineIdx, yPos = yPosStart;
+         lineIdx < lastLineIdx;
+         lineIdx += BYTES_PER_LINE, yPos += m_charHeight)
+    {
+        QString address = QString("%1")
+                .arg(lineIdx, addressChars(), 16, QChar('0'));
+        painter.drawText(addressRect.left(), yPos, address);
+    }
+
+    // バイナリエリア
+    for (int lineIdx = firstLineIdx, yPos = yPosStart;
+         lineIdx < lastLineIdx;
+         lineIdx += BYTES_PER_LINE, yPos += m_charHeight)
+    {
+        int xPos = xPosHex();
+        for (int colIdx = 0;
+             lineIdx + colIdx < m_data.size() && colIdx < BYTES_PER_LINE;
+             colIdx++, xPos += m_charWidth * 3)
+        {
+            int Idx = lineIdx + colIdx;
+            if (m_selectionBegin <= Idx && Idx < m_selectionEnd) {
+                painter.setBackground(this->palette().highlight());
+                painter.setBackgroundMode(Qt::OpaqueMode);
+                painter.setPen(this->palette().highlightedText().color());
+            }
+            else {
+                painter.setPen(this->palette().color(this->foregroundRole()));
+                painter.setBackgroundMode(Qt::TransparentMode);
+            }
+
+            quint8 ch = static_cast<quint8>(m_data[Idx]);
+            QString s = QString("%1").arg(ch, 2, 16, QChar('0'));
+            if (colIdx < 8) {
+                s.append(" ");
+                painter.drawText(xPos, yPos, s);
+            }
+            else {
+                s.prepend(" ");
+                painter.drawText(xPos, yPos, s);
+            }
+        }
+    }
+
+    // アスキーエリア
+    painter.setPen(this->palette().color(this->foregroundRole()));
+    painter.setBackgroundMode(Qt::TransparentMode);
+    for (int lineIdx = firstLineIdx, yPos = yPosStart;
+         lineIdx < lastLineIdx;
+         lineIdx += BYTES_PER_LINE, yPos += m_charHeight)
+    {
+        int xPos = xPosAscii();
+
+        for (int colIdx = 0;
+             lineIdx + colIdx < m_data.size() && colIdx < BYTES_PER_LINE;
+             colIdx++, xPos += m_charWidth)
+        {
+            quint8 ch = static_cast<quint8>(m_data[lineIdx + colIdx]);
+            if (!::isprint(ch) && ch != ' ') {
+                ch = '.';
+            }
+            QString s = QString(ch).toLatin1();
+            painter.drawText(xPos, yPos, s);
+        }
+    }
+}