]> sigrok.org Git - pulseview.git/commitdiff
Binary output view: Highlight byte range currently in view
authorSoeren Apel <redacted>
Fri, 19 Nov 2021 13:53:28 +0000 (14:53 +0100)
committerSoeren Apel <redacted>
Fri, 17 Dec 2021 21:21:21 +0000 (22:21 +0100)
pv/views/decoder_binary/QHexView.cpp
pv/views/decoder_binary/QHexView.hpp
pv/views/decoder_binary/view.cpp
pv/views/decoder_binary/view.hpp
pv/views/tabular_decoder/view.cpp

index ed003949f242c67f482667a26b315554c1b90fd8..2c893e366d6d65cdafb8eaca9d3e021eb22f0a44 100644 (file)
@@ -27,6 +27,8 @@
  * SOFTWARE.
  */
 
+#include <limits>
+
 #include <QApplication>
 #include <QClipboard>
 #include <QDebug>
@@ -40,6 +42,8 @@
 
 #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;
@@ -53,7 +57,9 @@ 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));
 
@@ -67,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
        }
 }
 
@@ -105,6 +113,20 @@ void QHexView::set_data(const DecodeBinaryClass* data)
        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();
+}
+
 unsigned int QHexView::get_bytes_per_line() const
 {
        return BYTES_PER_LINE;
@@ -116,6 +138,8 @@ void QHexView::clear()
        data_ = nullptr;
        data_size_ = 0;
 
+       highlighted_sample_ = std::numeric_limits<uint64_t>::max();
+
        viewport()->update();
 }
 
@@ -216,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_++;
 
@@ -269,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());
@@ -334,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++) {
 
@@ -349,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
@@ -379,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_;
@@ -396,20 +462,39 @@ 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));
                        }
 
                        painter.drawText(x, y, QString(ch));
@@ -419,6 +504,10 @@ void QHexView::paintEvent(QPaintEvent *event)
                y += charHeight_;
        }
 
+       // Restore painter defaults
+       painter.setBackgroundMode(Qt::TransparentMode);
+       painter.setBackground(regular_brush);
+
        // Paint cursor
        if (hasFocus()) {
                int x = (cursorPos_ % (2 * BYTES_PER_LINE));
index 5f46ba1cae72078a76ed1cbb49fd79719fa75475..4ba995e30148e44ed479adb01fe46dd14e4f18be 100644 (file)
@@ -54,6 +54,13 @@ public:
 
        void set_mode(Mode m);
        void set_data(const DecodeBinaryClass* data);
+
+       /* Sets range of samples that are visible in the main view */
+       void set_visible_sample_range(uint64_t start, uint64_t end);
+
+       /* Sets sample whose associated data we should highlight */
+       void set_highlighted_data_sample(uint64_t sample);
+
        unsigned int get_bytes_per_line() const;
 
        void clear();
@@ -67,7 +74,7 @@ public:
 
 protected:
        void initialize_byte_iterator(size_t offset);
-       uint8_t get_next_byte(bool* is_next_chunk = nullptr);
+       uint8_t get_next_byte(bool* is_new_chunk = nullptr);
 
        void paintEvent(QPaintEvent *event);
        void keyPressEvent(QKeyEvent *event);
@@ -95,8 +102,13 @@ private:
 
        size_t current_chunk_id_, current_chunk_offset_, current_offset_;
        DecodeBinaryDataChunk current_chunk_; // Cache locally so that we're not messed up when the vector is re-allocating its data
+       uint64_t current_chunk_sample_, next_chunk_sample_;
+
+       pair<uint64_t, uint64_t> visible_range_;
+       uint64_t highlighted_sample_;
 
        vector<QColor> chunk_colors_;
+       QColor visible_range_color_;
 };
 
 #endif // PULSEVIEW_PV_VIEWS_DECODER_BINARY_QHEXVIEW_HPP
index b5daaf0f84ef472dd7586cb9ab423a13cec6f156..05d5ed4612c9ba7b5897ca67076b04f9f42d4ca8 100644 (file)
@@ -129,9 +129,17 @@ View::View(Session &session, bool is_main_view, QMainWindow *parent) :
 
        parent->setSizePolicy(hex_view_->sizePolicy()); // TODO Must be updated when selected widget changes
 
+       // Set up metadata event handler
+       session_.metadata_obj_manager()->add_observer(this);
+
        reset_view_state();
 }
 
+View::~View()
+{
+       session_.metadata_obj_manager()->remove_observer(this);
+}
+
 ViewType View::get_type() const
 {
        return ViewTypeDecoderBinary;
@@ -466,6 +474,25 @@ void View::on_actionSave_triggered(QAction* action)
        }
 }
 
+void View::on_metadata_object_changed(MetadataObject* obj,
+       MetadataValueType value_type)
+{
+       // Check if we need to update the model's data range. We only work on the
+       // end sample value because the start sample value is updated first and
+       // we need both
+       if ((obj->type() == MetadataObjMainViewRange) &&
+               (value_type == MetadataValueEndSample)) {
+
+               int64_t start_sample = obj->value(MetadataValueStartSample).toLongLong();
+               int64_t end_sample = obj->value(MetadataValueEndSample).toLongLong();
+
+               hex_view_->set_visible_sample_range(start_sample, end_sample);
+       }
+
+       if (obj->type() == MetadataObjMousePos)
+               hex_view_->set_highlighted_data_sample(obj->value(MetadataValueStartSample).toLongLong());
+}
+
 void View::perform_delayed_view_update()
 {
        if (signal_ && !binary_data_exists_)
index eea6666139a34b76685c36917de26f8d0a953837..530e60369de6e379941ef77ec1adbcff3c37e5c9 100644 (file)
@@ -25,8 +25,9 @@
 #include <QStackedWidget>
 #include <QToolButton>
 
-#include <pv/views/viewbase.hpp>
-#include <pv/data/decodesignal.hpp>
+#include "pv/metadata_obj.hpp"
+#include "pv/views/viewbase.hpp"
+#include "pv/data/decodesignal.hpp"
 
 #include "QHexView.hpp"
 
@@ -50,12 +51,13 @@ enum SaveType {
 extern const char* SaveTypeNames[SaveTypeCount];
 
 
-class View : public ViewBase
+class View : public ViewBase, public MetadataObjObserverInterface
 {
        Q_OBJECT
 
 public:
        explicit View(Session &session, bool is_main_view=false, QMainWindow *parent = nullptr);
+       ~View();
 
        virtual ViewType get_type() const;
 
@@ -90,6 +92,9 @@ private Q_SLOTS:
 
        void on_actionSave_triggered(QAction* action = nullptr);
 
+       virtual void on_metadata_object_changed(MetadataObject* obj,
+               MetadataValueType value_type);
+
        virtual void perform_delayed_view_update();
 
 private:
index 0aca4790e8ecc6d984df00129811a1a82fae7077..7e4b3ddb414dd28887842aad9e2f4cf421e5086e 100644 (file)
@@ -662,7 +662,6 @@ void View::on_metadata_object_changed(MetadataObject* obj,
        // Check if we need to update the model's data range. We only work on the
        // end sample value because the start sample value is updated first and
        // we don't want to update the model twice
-
        if ((view_mode_selector_->currentIndex() == ViewModeVisible) &&
                (obj->type() == MetadataObjMainViewRange) &&
                (value_type == MetadataValueEndSample)) {