From: Soeren Apel Date: Sat, 30 Nov 2019 20:16:12 +0000 (+0100) Subject: Integrate QHexView and make it better X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=560f837745f9779417b6984d3e6c838ae8451838;p=pulseview.git Integrate QHexView and make it better --- diff --git a/pv/views/decoder_output/QHexView.cpp b/pv/views/decoder_output/QHexView.cpp index 7d43cf0c..b379026c 100644 --- a/pv/views/decoder_output/QHexView.cpp +++ b/pv/views/decoder_output/QHexView.cpp @@ -40,11 +40,11 @@ using std::size_t; -const unsigned int HEXCHARS_IN_LINE = 47; -const unsigned int GAP_ADR_HEX = 10; -const unsigned int GAP_HEX_ASCII = 16; -const unsigned int BYTES_PER_LINE = 16; - +const unsigned int BYTES_PER_LINE = 16; +const unsigned int HEXCHARS_IN_LINE = BYTES_PER_LINE * 3 - 1; +const unsigned int GAP_ADR_HEX = 10; +const unsigned int GAP_HEX_ASCII = 10; +const unsigned int GAP_ASCII_SLIDER = 5; DataStorageArray::DataStorageArray(const QByteArray &arr) { @@ -64,20 +64,19 @@ size_t DataStorageArray::size() QHexView::QHexView(QWidget *parent): - QAbstractScrollArea(parent), - pdata_(nullptr) + QAbstractScrollArea(parent), + pdata_(nullptr) { setFont(QFont("Courier", 10)); - charWidth_ = fontMetrics().width(QLatin1Char('9')); + charWidth_ = fontMetrics().boundingRect('X').width(); charHeight_ = fontMetrics().height(); - posAddr_ = 0; - posHex_ = 10 * charWidth_ + GAP_ADR_HEX; + // Determine X coordinates of the three sub-areas + posAddr_ = 0; + posHex_ = 10 * charWidth_ + GAP_ADR_HEX; posAscii_ = posHex_ + HEXCHARS_IN_LINE * charWidth_ + GAP_HEX_ASCII; - setMinimumWidth(posAscii_ + (BYTES_PER_LINE * charWidth_)); - setFocusPolicy(Qt::StrongFocus); } @@ -114,13 +113,16 @@ void QHexView::clear() verticalScrollBar()->setValue(0); } -QSize QHexView::fullSize() const +QSize QHexView::getFullSize() const { if (!pdata_) return QSize(0, 0); - size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_); + size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_) + + GAP_ASCII_SLIDER + verticalScrollBar()->width(); + size_t height = pdata_->size() / BYTES_PER_LINE; + if (pdata_->size() % BYTES_PER_LINE) height++; @@ -136,8 +138,12 @@ void QHexView::paintEvent(QPaintEvent *event) QPainter painter(viewport()); + // Calculate and update the widget and paint area sizes QSize areaSize = viewport()->size(); - QSize widgetSize = fullSize(); + QSize widgetSize = getFullSize(); + setMinimumWidth(widgetSize.width()); + setMaximumWidth(widgetSize.width()); + verticalScrollBar()->setPageStep(areaSize.height() / charHeight_); verticalScrollBar()->setRange(0, (widgetSize.height() - areaSize.height()) / charHeight_ + 1); @@ -150,78 +156,98 @@ void QHexView::paintEvent(QPaintEvent *event) lastLineIdx++; } - painter.fillRect(event->rect(), this->palette().color(QPalette::Base)); - - QColor addressAreaColor = QColor(0xd4, 0xd4, 0xd4, 0xff); + // Fill widget and address area backgrounds + painter.fillRect(event->rect(), palette().color(QPalette::Base)); painter.fillRect(QRect(posAddr_, event->rect().top(), - posHex_ - GAP_ADR_HEX + 2, height()), addressAreaColor); + posHex_ - (GAP_ADR_HEX / 2), height()), palette().color(QPalette::Window)); - int linePos = posAscii_ - (GAP_HEX_ASCII / 2); - painter.setPen(Qt::gray); + // Paint divider line between hex and ASCII areas + int line_x = posAscii_ - (GAP_HEX_ASCII / 2); + painter.setPen(palette().color(QPalette::Midlight)); + painter.drawLine(line_x, event->rect().top(), line_x, height()); - painter.drawLine(linePos, event->rect().top(), linePos, height()); + // Paint address area + painter.setPen(palette().color(QPalette::ButtonText)); - painter.setPen(Qt::black); + int yStart = charHeight_; + for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) { - int yPosStart = charHeight_; + QString address = QString("%1").arg(lineIdx * 16, 10, 16, QChar('0')).toUpper(); + painter.drawText(posAddr_, y, address); + y += charHeight_; + } - QBrush def = painter.brush(); - QBrush selected = QBrush(QColor(0x6d, 0x9e, 0xff, 0xff)); + // Paint hex values + QBrush regular = painter.brush(); + QBrush selected = QBrush(palette().color(QPalette::Highlight)); QByteArray data = pdata_->getData(firstLineIdx * BYTES_PER_LINE, (lastLineIdx - firstLineIdx) * BYTES_PER_LINE); - for (size_t lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx++) { - QString address = QString("%1").arg(lineIdx * 16, 10, 16, QChar('0')); - painter.drawText(posAddr_, yPos, address); + yStart = charHeight_; + for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) { + + painter.setBackgroundMode(Qt::OpaqueMode); - int xPos = posHex_; + int x = posHex_; for (size_t i = 0; i < BYTES_PER_LINE && ((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) < (size_t)data.size(); i++) { size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2; - if ((pos >= selectBegin_) && (pos < selectEnd_)) { - painter.setBackground(selected); - painter.setBackgroundMode(Qt::OpaqueMode); - } - QString val = QString::number((data.at((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) & 0xF0) >> 4, 16); - painter.drawText(xPos, yPos, val); - - if (((pos+1) >= selectBegin_) && ((pos+1) < selectEnd_)) { + if ((pos >= selectBegin_) && (pos < selectEnd_)) { painter.setBackground(selected); - painter.setBackgroundMode(Qt::OpaqueMode); + painter.setPen(palette().color(QPalette::HighlightedText)); } else { - painter.setBackground(def); - painter.setBackgroundMode(Qt::OpaqueMode); + painter.setBackground(regular); + painter.setPen(palette().color(QPalette::Text)); } - val = QString::number((data.at((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) & 0xF), 16); - painter.drawText(xPos + charWidth_, yPos, val); + // First nibble + QString val = QString::number((data.at((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) & 0xF0) >> 4, 16).toUpper(); + painter.drawText(x, y, val); - painter.setBackground(def); - painter.setBackgroundMode(Qt::OpaqueMode); + // Second nibble + val = QString::number((data.at((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) & 0xF), 16).toUpper(); + painter.drawText(x + charWidth_, y, val); - xPos += 3 * charWidth_; + x += 3 * charWidth_; } - int xPosAscii = posAscii_; + y += charHeight_; + } + + // Paint ASCII characters + yStart = charHeight_; + for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) { + + int x = posAscii_; for (size_t i = 0; ((lineIdx - firstLineIdx) * BYTES_PER_LINE + i) < (size_t)data.size() && (i < BYTES_PER_LINE); i++) { char ch = data[(unsigned int)((lineIdx - firstLineIdx) * BYTES_PER_LINE + i)]; - if ((ch < 0x20) || (ch > 0x7e)) + if ((ch < 0x20) || (ch > 0x7E)) ch = '.'; - painter.drawText(xPosAscii, yPos, QString(ch)); - xPosAscii += charWidth_; + size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2; + if ((pos >= selectBegin_) && (pos < selectEnd_)) { + painter.setBackground(selected); + painter.setPen(palette().color(QPalette::HighlightedText)); + } else { + painter.setBackground(regular); + painter.setPen(palette().color(QPalette::Text)); + } + + painter.drawText(x, y, QString(ch)); + x += charWidth_; } - yPos += charHeight_; + y += charHeight_; } + // Paint cursor if (hasFocus()) { int x = (cursorPos_ % (2 * BYTES_PER_LINE)); int y = cursorPos_ / (2 * BYTES_PER_LINE); y -= firstLineIdx; int cursorX = (((x / 2) * 3) + (x % 2)) * charWidth_ + posHex_; int cursorY = y * charHeight_ + 4; - painter.fillRect(cursorX, cursorY, 2, charHeight_, this->palette().color(QPalette::WindowText)); + painter.fillRect(cursorX, cursorY, 2, charHeight_, palette().color(QPalette::WindowText)); } } @@ -229,9 +255,7 @@ void QHexView::keyPressEvent(QKeyEvent *event) { bool setVisible = false; - /*****************************************************************************/ - /* Cursor movements */ - /*****************************************************************************/ + // Cursor movements if (event->matches(QKeySequence::MoveToNextChar)) { setCursorPos(cursorPos_ + 1); resetSelection(cursorPos_); @@ -286,9 +310,7 @@ void QHexView::keyPressEvent(QKeyEvent *event) setVisible = true; } - /*****************************************************************************/ - /* Select commands */ - /*****************************************************************************/ + // Select commands if (event->matches(QKeySequence::SelectAll)) { resetSelection(0); if (pdata_) @@ -401,7 +423,7 @@ void QHexView::keyPressEvent(QKeyEvent *event) void QHexView::mouseMoveEvent(QMouseEvent *event) { - int actPos = cursorPos(event->pos()); + int actPos = cursorPosFromMousePos(event->pos()); setCursorPos(actPos); setSelection(actPos); @@ -410,7 +432,7 @@ void QHexView::mouseMoveEvent(QMouseEvent *event) void QHexView::mousePressEvent(QMouseEvent *event) { - int cPos = cursorPos(event->pos()); + int cPos = cursorPosFromMousePos(event->pos()); if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) && (event->button() == Qt::LeftButton)) setSelection(cPos); @@ -422,19 +444,20 @@ void QHexView::mousePressEvent(QMouseEvent *event) viewport()->update(); } -size_t QHexView::cursorPos(const QPoint &position) +size_t QHexView::cursorPosFromMousePos(const QPoint &position) { int pos = -1; if (((size_t)position.x() >= posHex_) && ((size_t)position.x() < (posHex_ + HEXCHARS_IN_LINE * charWidth_))) { - int x = (position.x() - posHex_) / charWidth_; + // Note: We add 1.5 character widths so that selection across + // byte gaps is smoother + int x = (position.x() + (1.5 * charWidth_ / 2) - posHex_) / charWidth_; - if ((x % 3) == 0) - x = (x / 3) * 2; - else - x = ((x / 3) * 2) + 1; + // Note: We allow only full bytes to be selected, not nibbles, + // so we round to the nearest byte gap + x = (2 * x + 1) / 3; int firstLineIdx = verticalScrollBar()->value(); int y = (position.y() / charHeight_) * 2 * BYTES_PER_LINE; diff --git a/pv/views/decoder_output/QHexView.hpp b/pv/views/decoder_output/QHexView.hpp index fd4b86f1..234abda6 100644 --- a/pv/views/decoder_output/QHexView.hpp +++ b/pv/views/decoder_output/QHexView.hpp @@ -60,6 +60,7 @@ private: class QHexView: public QAbstractScrollArea { +public: QHexView(QWidget *parent = 0); ~QHexView(); @@ -75,13 +76,13 @@ protected: void mousePressEvent(QMouseEvent *event); private: - QSize fullSize() const; + QSize getFullSize() const; void resetSelection(); void resetSelection(int pos); void setSelection(int pos); void ensureVisible(); void setCursorPos(int pos); - size_t cursorPos(const QPoint &position); + size_t cursorPosFromMousePos(const QPoint &position); private: DataStorage *pdata_; diff --git a/pv/views/decoder_output/view.cpp b/pv/views/decoder_output/view.cpp index fdf4bc7d..5c3b031d 100644 --- a/pv/views/decoder_output/view.cpp +++ b/pv/views/decoder_output/view.cpp @@ -25,6 +25,7 @@ #include #include "view.hpp" +#include "QHexView.hpp" #include "pv/session.hpp" #include "pv/util.hpp" @@ -44,7 +45,9 @@ View::View(Session &session, bool is_main_view, QMainWindow *parent) : // Note: Place defaults in View::reset_view_state(), not here signal_selector_(new QComboBox()), - format_selector_(new QComboBox()) + format_selector_(new QComboBox()), + stacked_widget_(new QStackedWidget()), + hex_view_(new QHexView()) { QVBoxLayout *root_layout = new QVBoxLayout(this); root_layout->setContentsMargins(0, 0, 0, 0); @@ -64,6 +67,10 @@ View::View(Session &session, bool is_main_view, QMainWindow *parent) : // Add format types format_selector_->addItem(tr("Hexdump"), qVariantFromValue(QString("text/hexdump"))); + // Add widget stack + root_layout->addWidget(stacked_widget_); + stacked_widget_->addWidget(hex_view_); + reset_view_state(); } diff --git a/pv/views/decoder_output/view.hpp b/pv/views/decoder_output/view.hpp index cd90b5ca..ff636e03 100644 --- a/pv/views/decoder_output/view.hpp +++ b/pv/views/decoder_output/view.hpp @@ -21,9 +21,12 @@ #define PULSEVIEW_PV_VIEWS_DECODEROUTPUT_VIEW_HPP #include +#include #include +#include "QHexView.hpp" + namespace pv { class Session; @@ -63,6 +66,8 @@ private Q_SLOTS: private: QComboBox *signal_selector_, *format_selector_; + QStackedWidget *stacked_widget_; + QHexView *hex_view_; }; } // namespace decoder_output