From: Soeren Apel Date: Fri, 19 Nov 2021 13:53:28 +0000 (+0100) Subject: Binary output view: Highlight byte range currently in view X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=978dbc9a3e3c4bf8b42f00a269d04d0dca62b1a5;p=pulseview.git Binary output view: Highlight byte range currently in view --- diff --git a/pv/views/decoder_binary/QHexView.cpp b/pv/views/decoder_binary/QHexView.cpp index ed003949..2c893e36 100644 --- a/pv/views/decoder_binary/QHexView.cpp +++ b/pv/views/decoder_binary/QHexView.cpp @@ -27,6 +27,8 @@ * SOFTWARE. */ +#include + #include #include #include @@ -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::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::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::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::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)); diff --git a/pv/views/decoder_binary/QHexView.hpp b/pv/views/decoder_binary/QHexView.hpp index 5f46ba1c..4ba995e3 100644 --- a/pv/views/decoder_binary/QHexView.hpp +++ b/pv/views/decoder_binary/QHexView.hpp @@ -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 visible_range_; + uint64_t highlighted_sample_; vector chunk_colors_; + QColor visible_range_color_; }; #endif // PULSEVIEW_PV_VIEWS_DECODER_BINARY_QHEXVIEW_HPP diff --git a/pv/views/decoder_binary/view.cpp b/pv/views/decoder_binary/view.cpp index b5daaf0f..05d5ed46 100644 --- a/pv/views/decoder_binary/view.cpp +++ b/pv/views/decoder_binary/view.cpp @@ -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_) diff --git a/pv/views/decoder_binary/view.hpp b/pv/views/decoder_binary/view.hpp index eea66661..530e6036 100644 --- a/pv/views/decoder_binary/view.hpp +++ b/pv/views/decoder_binary/view.hpp @@ -25,8 +25,9 @@ #include #include -#include -#include +#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: diff --git a/pv/views/tabular_decoder/view.cpp b/pv/views/tabular_decoder/view.cpp index 0aca4790..7e4b3ddb 100644 --- a/pv/views/tabular_decoder/view.cpp +++ b/pv/views/tabular_decoder/view.cpp @@ -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)) {