]> sigrok.org Git - pulseview.git/blobdiff - pv/views/decoder_binary/QHexView.cpp
Session: Fix issue #67 by improving error handling
[pulseview.git] / pv / views / decoder_binary / QHexView.cpp
index ccd50f87bccc1291195d2b515a4ee70418e422f0..0badadc77eeb9923c8bd7d192986dd70f3bf0880 100644 (file)
@@ -27,6 +27,8 @@
  * SOFTWARE.
  */
 
+#include <limits>
+
 #include <QApplication>
 #include <QClipboard>
 #include <QDebug>
 #include <QKeyEvent>
 #include <QScrollBar>
 #include <QSize>
+#include <QString>
 #include <QPainter>
 #include <QPaintEvent>
 
 #include "QHexView.hpp"
 
+using std::make_pair;
+
 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;
@@ -52,18 +57,15 @@ QHexView::QHexView(QWidget *parent):
        data_(nullptr),
        selectBegin_(0),
        selectEnd_(0),
-       cursorPos_(0)
+       cursorPos_(0),
+       visible_range_(0, 0),
+       highlighted_sample_(std::numeric_limits<uint64_t>::max())
 {
        setFont(QFont("Courier", 10));
 
        charWidth_ = fontMetrics().boundingRect('X').width();
        charHeight_ = fontMetrics().height();
 
-       // 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;
-
        setFocusPolicy(Qt::StrongFocus);
 
        if (palette().color(QPalette::ButtonText).toHsv().value() > 127) {
@@ -71,11 +73,13 @@ QHexView::QHexView(QWidget *parent):
                chunk_colors_.emplace_back(100, 149, 237); // QColorConstants::Svg::cornflowerblue
                chunk_colors_.emplace_back(60, 179, 113);  // QColorConstants::Svg::mediumseagreen
                chunk_colors_.emplace_back(210, 180, 140); // QColorConstants::Svg::tan
+               visible_range_color_ = QColor("#fff5ee");  // QColorConstants::Svg::seashell
        } else {
                // Color is dark
-               chunk_colors_.emplace_back(0, 0, 139);   // QColorConstants::Svg::darkblue
-               chunk_colors_.emplace_back(34, 139, 34); // QColorConstants::Svg::forestgreen
-               chunk_colors_.emplace_back(160, 82, 45); // QColorConstants::Svg::sienna
+               chunk_colors_.emplace_back(0, 0, 139);    // QColorConstants::Svg::darkblue
+               chunk_colors_.emplace_back(34, 139, 34);  // QColorConstants::Svg::forestgreen
+               chunk_colors_.emplace_back(160, 82, 45);  // QColorConstants::Svg::sienna
+               visible_range_color_ = QColor("#fff5ee"); // QColorConstants::Svg::seashell
        }
 }
 
@@ -99,6 +103,27 @@ void QHexView::set_data(const DecodeBinaryClass* data)
        }
        data_size_ = size;
 
+       address_digits_ = (uint8_t)QString::number(data_size_, 16).length();
+
+       // Calculate X coordinates of the three sub-areas
+       posAddr_  = 0;
+       posHex_   = address_digits_ * charWidth_ + GAP_ADR_HEX;
+       posAscii_ = posHex_ + HEXCHARS_IN_LINE * charWidth_ + GAP_HEX_ASCII;
+
+       viewport()->update();
+}
+
+void QHexView::set_visible_sample_range(uint64_t start, uint64_t end)
+{
+       visible_range_ = make_pair(start, end);
+
+       viewport()->update();
+}
+
+void QHexView::set_highlighted_data_sample(uint64_t sample)
+{
+       highlighted_sample_ = sample;
+
        viewport()->update();
 }
 
@@ -113,6 +138,8 @@ void QHexView::clear()
        data_ = nullptr;
        data_size_ = 0;
 
+       highlighted_sample_ = std::numeric_limits<uint64_t>::max();
+
        viewport()->update();
 }
 
@@ -159,7 +186,7 @@ size_t QHexView::create_hex_line(size_t start, size_t end, QString* dest,
        end = std::min((uint64_t)end, offset + BYTES_PER_LINE);
 
        if (with_offset)
-               dest->append(QString("%1 ").arg(row * BYTES_PER_LINE, 10, 16, QChar('0')).toUpper());
+               dest->append(QString("%1 ").arg(row * BYTES_PER_LINE, address_digits_, 16, QChar('0')).toUpper());
 
        initialize_byte_iterator(offset);
        for (size_t i = offset; i < offset + BYTES_PER_LINE; i++) {
@@ -213,17 +240,35 @@ void QHexView::initialize_byte_iterator(size_t offset)
 
        if (current_chunk_id_ < data_->chunks.size())
                current_chunk_ = data_->chunks[current_chunk_id_];
+
+       current_chunk_sample_ = current_chunk_.sample;
+
+       // Obtain sample of next chunk if there is one
+       if ((current_chunk_id_ + 1) < data_->chunks.size())
+               next_chunk_sample_ = data_->chunks[current_chunk_id_ + 1].sample;
+       else
+               next_chunk_sample_ = std::numeric_limits<uint64_t>::max();
 }
 
-uint8_t QHexView::get_next_byte(bool* is_next_chunk)
+uint8_t QHexView::get_next_byte(bool* is_new_chunk)
 {
-       if (is_next_chunk != nullptr)
-               *is_next_chunk = (current_chunk_offset_ == 0);
+       if (is_new_chunk != nullptr)
+               *is_new_chunk = (current_chunk_offset_ == 0);
 
        uint8_t v = 0;
        if (current_chunk_offset_ < current_chunk_.data.size())
                v = current_chunk_.data[current_chunk_offset_];
 
+       current_chunk_sample_ = current_chunk_.sample;
+
+       if (is_new_chunk) {
+               // Obtain sample of next chunk if there is one
+               if ((current_chunk_id_ + 1) < data_->chunks.size())
+                       next_chunk_sample_ = data_->chunks[current_chunk_id_ + 1].sample;
+               else
+                       next_chunk_sample_ = std::numeric_limits<uint64_t>::max();
+       }
+
        current_offset_++;
        current_chunk_offset_++;
 
@@ -266,6 +311,12 @@ void QHexView::paintEvent(QPaintEvent *event)
 {
        QPainter painter(viewport());
 
+       QFont normal_font = painter.font();
+       QFont bold_font = painter.font();
+       bold_font.setWeight(QFont::Bold);
+
+       bool bold_font_was_used = false;
+
        // Calculate and update the widget and paint area sizes
        QSize widgetSize = getFullSize();
        setMinimumWidth(widgetSize.width());
@@ -283,7 +334,7 @@ void QHexView::paintEvent(QPaintEvent *event)
        // Fill widget background
        painter.fillRect(event->rect(), palette().color(QPalette::Base));
 
-       if (!data_ || (data_size_ == 0)) {
+       if (!data_ || (data_size_ == 0) || (data_->chunks.empty())) {
                painter.setPen(palette().color(QPalette::Text));
                QString s = tr("No data available");
                int x = (areaSize.width() - fontMetrics().boundingRect(s).width()) / 2;
@@ -319,7 +370,7 @@ void QHexView::paintEvent(QPaintEvent *event)
        int yStart = 2 * charHeight_;
        for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
 
-               QString address = QString("%1").arg(lineIdx * 16, 10, 16, QChar('0')).toUpper();
+               QString address = QString("%1").arg(lineIdx * 16, address_digits_, 16, QChar('0')).toUpper();
                painter.drawText(posAddr_, y, address);
                y += charHeight_;
        }
@@ -331,13 +382,15 @@ void QHexView::paintEvent(QPaintEvent *event)
                        charHeight_ - 3, QString::number(offset, 16).toUpper());
 
        // Paint hex values
-       QBrush regular = palette().buttonText();
-       QBrush selected = palette().highlight();
+       QBrush regular_brush = palette().buttonText();
+       QBrush selected_brush = palette().highlight();
+       QBrush visible_range_brush = QBrush(visible_range_color_);
 
        bool multiple_chunks = (data_->chunks.size() > 1);
        unsigned int chunk_color = 0;
 
        initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE);
+
        yStart = 2 * charHeight_;
        for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
 
@@ -346,26 +399,42 @@ void QHexView::paintEvent(QPaintEvent *event)
                        size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
 
                        // Fetch byte
-                       bool is_next_chunk;
-                       uint8_t byte_value = get_next_byte(&is_next_chunk);
+                       bool is_new_chunk;
+                       uint8_t byte_value = get_next_byte(&is_new_chunk);
 
-                       if (is_next_chunk) {
+                       if (is_new_chunk) {
                                chunk_color++;
                                if (chunk_color == chunk_colors_.size())
                                        chunk_color = 0;
+
+                               // New chunk means also new chunk sample, so check for required changes
+                               if (bold_font_was_used)
+                                       painter.setFont(normal_font);
+                               if ((highlighted_sample_ >= current_chunk_sample_) && (highlighted_sample_ < next_chunk_sample_)) {
+                                       painter.setFont(bold_font);
+                                       bold_font_was_used = true;
+                               }
+                       }
+
+                       // Restore default paint style
+                       painter.setBackground(regular_brush);
+                       painter.setBackgroundMode(Qt::TransparentMode);
+                       if (!multiple_chunks)
+                               painter.setPen(palette().color(QPalette::Text));
+                       else
+                               painter.setPen(chunk_colors_[chunk_color]);
+
+                       // Highlight needed because it's the range visible in main view?
+                       if ((current_chunk_sample_ >= visible_range_.first) && (current_chunk_sample_ < visible_range_.second)) {
+                               painter.setBackgroundMode(Qt::OpaqueMode);
+                               painter.setBackground(visible_range_brush);
                        }
 
+                       // Highlight for selection range needed? (takes priority over visible range highlight)
                        if ((pos >= selectBegin_) && (pos < selectEnd_)) {
                                painter.setBackgroundMode(Qt::OpaqueMode);
-                               painter.setBackground(selected);
+                               painter.setBackground(selected_brush);
                                painter.setPen(palette().color(QPalette::HighlightedText));
-                       } else {
-                               painter.setBackground(regular);
-                               painter.setBackgroundMode(Qt::TransparentMode);
-                               if (!multiple_chunks)
-                                       painter.setPen(palette().color(QPalette::Text));
-                               else
-                                       painter.setPen(chunk_colors_[chunk_color]);
                        }
 
                        // First nibble
@@ -376,7 +445,7 @@ void QHexView::paintEvent(QPaintEvent *event)
                        val = QString::number((byte_value & 0xF), 16).toUpper();
                        painter.drawText(x + charWidth_, y, val);
 
-                       if ((pos >= selectBegin_) && (pos < selectEnd_ - 1) && (i < BYTES_PER_LINE - 1))
+                       if ((i < BYTES_PER_LINE - 1) && (current_offset_ < data_size_))
                                painter.drawText(x + 2 * charWidth_, y, QString(' '));
 
                        x += 3 * charWidth_;
@@ -393,29 +462,56 @@ void QHexView::paintEvent(QPaintEvent *event)
                int x = posAscii_;
                for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) {
                        // Fetch byte
-                       uint8_t ch = get_next_byte();
+                       bool is_new_chunk;
+                       uint8_t ch = get_next_byte(&is_new_chunk);
+
+                       if (is_new_chunk) {
+                               // New chunk means also new chunk sample, so check for required changes
+                               if (bold_font_was_used)
+                                       painter.setFont(normal_font);
+                               if ((highlighted_sample_ >= current_chunk_sample_) && (highlighted_sample_ < next_chunk_sample_)) {
+                                       painter.setFont(bold_font);
+                                       bold_font_was_used = true;
+                               }
+                       }
 
                        if ((ch < 0x20) || (ch > 0x7E))
                                ch = '.';
 
+                       // Restore default paint style
+                       painter.setBackgroundMode(Qt::TransparentMode);
+                       painter.setBackground(regular_brush);
+                       painter.setPen(palette().color(QPalette::Text));
+
+                       // Highlight needed because it's the range visible in main view?
+                       if ((current_chunk_sample_ >= visible_range_.first) && (current_chunk_sample_ < visible_range_.second)) {
+                               painter.setBackgroundMode(Qt::OpaqueMode);
+                               painter.setBackground(visible_range_brush);
+                       }
+
+                       // Highlight for selection range needed? (takes priority over visible range highlight)
                        size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
                        if ((pos >= selectBegin_) && (pos < selectEnd_)) {
                                painter.setBackgroundMode(Qt::OpaqueMode);
-                               painter.setBackground(selected);
+                               painter.setBackground(selected_brush);
                                painter.setPen(palette().color(QPalette::HighlightedText));
-                       } else {
-                               painter.setBackgroundMode(Qt::TransparentMode);
-                               painter.setBackground(regular);
-                               painter.setPen(palette().color(QPalette::Text));
                        }
 
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+                       painter.drawText(x, y, QString(QChar(ch)));
+#else
                        painter.drawText(x, y, QString(ch));
+#endif
                        x += charWidth_;
                }
 
                y += charHeight_;
        }
 
+       // Restore painter defaults
+       painter.setBackgroundMode(Qt::TransparentMode);
+       painter.setBackground(regular_brush);
+
        // Paint cursor
        if (hasFocus()) {
                int x = (cursorPos_ % (2 * BYTES_PER_LINE));