Rename decoder output view to binary decoder output view
authorSoeren Apel <soeren@apelpie.net>
Sat, 29 Feb 2020 21:50:21 +0000 (22:50 +0100)
committerUwe Hermann <uwe@hermann-uwe.de>
Mon, 16 Mar 2020 22:16:40 +0000 (23:16 +0100)
13 files changed:
CMakeLists.txt
pv/mainwindow.cpp
pv/views/decoder_binary/QHexView.cpp [new file with mode: 0644]
pv/views/decoder_binary/QHexView.hpp [new file with mode: 0644]
pv/views/decoder_binary/view.cpp [new file with mode: 0644]
pv/views/decoder_binary/view.hpp [new file with mode: 0644]
pv/views/decoder_output/QHexView.cpp [deleted file]
pv/views/decoder_output/QHexView.hpp [deleted file]
pv/views/decoder_output/view.cpp [deleted file]
pv/views/decoder_output/view.hpp [deleted file]
pv/views/viewbase.cpp
pv/views/viewbase.hpp
test/CMakeLists.txt

index 5330663dbf755100dfb99c42296e14a9681d8a43..31164a9e77c2b8336fa7b0032d8c45227057f9b8 100644 (file)
@@ -389,8 +389,8 @@ if(ENABLE_DECODE)
                pv/subwindows/decoder_selector/item.cpp
                pv/subwindows/decoder_selector/model.cpp
                pv/subwindows/decoder_selector/subwindow.cpp
-               pv/views/decoder_output/view.cpp
-               pv/views/decoder_output/QHexView.cpp
+               pv/views/decoder_binary/view.cpp
+               pv/views/decoder_binary/QHexView.cpp
                pv/views/trace/decodetrace.cpp
                pv/widgets/decodergroupbox.cpp
                pv/widgets/decodermenu.cpp
@@ -399,8 +399,8 @@ if(ENABLE_DECODE)
        list(APPEND pulseview_HEADERS
                pv/data/decodesignal.hpp
                pv/subwindows/decoder_selector/subwindow.hpp
-               pv/views/decoder_output/view.hpp
-               pv/views/decoder_output/QHexView.hpp
+               pv/views/decoder_binary/view.hpp
+               pv/views/decoder_binary/QHexView.hpp
                pv/views/trace/decodetrace.hpp
                pv/widgets/decodergroupbox.hpp
                pv/widgets/decodermenu.hpp
index 6662180c7f19fde5fc5dad428e10bad8d412613c..ea9f01af7fd13f6ca056ef7dfe37333b158cbb9d 100644 (file)
@@ -52,7 +52,7 @@
 
 #ifdef ENABLE_DECODE
 #include "subwindows/decoder_selector/subwindow.hpp"
-#include "views/decoder_output/view.hpp"
+#include "views/decoder_binary/view.hpp"
 #endif
 
 #include <libsigrokcxx/libsigrokcxx.hpp>
@@ -162,8 +162,8 @@ shared_ptr<views::ViewBase> MainWindow::add_view(views::ViewType type,
                // This view will be the main view if there's no main bar yet
                v = make_shared<views::trace::View>(session, (main_bar ? false : true), dock_main);
 #ifdef ENABLE_DECODE
-       if (type == views::ViewTypeDecoderOutput)
-               v = make_shared<views::decoder_output::View>(session, false, dock_main);
+       if (type == views::ViewTypeDecoderBinary)
+               v = make_shared<views::decoder_binary::View>(session, false, dock_main);
 #endif
 
        if (!v)
diff --git a/pv/views/decoder_binary/QHexView.cpp b/pv/views/decoder_binary/QHexView.cpp
new file mode 100644 (file)
index 0000000..ccd50f8
--- /dev/null
@@ -0,0 +1,689 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2015 Victor Anjin <virinext@gmail.com>
+ * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <QApplication>
+#include <QClipboard>
+#include <QDebug>
+#include <QFont>
+#include <QKeyEvent>
+#include <QScrollBar>
+#include <QSize>
+#include <QPainter>
+#include <QPaintEvent>
+
+#include "QHexView.hpp"
+
+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;
+
+
+QHexView::QHexView(QWidget *parent):
+       QAbstractScrollArea(parent),
+       mode_(ChunkedDataMode),
+       data_(nullptr),
+       selectBegin_(0),
+       selectEnd_(0),
+       cursorPos_(0)
+{
+       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) {
+               // Color is bright
+               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
+       } 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
+       }
+}
+
+void QHexView::set_mode(Mode m)
+{
+       mode_ = m;
+
+       // This is not expected to be set when data is showing,
+       // so we don't update the viewport here
+}
+
+void QHexView::set_data(const DecodeBinaryClass* data)
+{
+       data_ = data;
+
+       size_t size = 0;
+       if (data) {
+               size_t chunks = data_->chunks.size();
+               for (size_t i = 0; i < chunks; i++)
+                       size += data_->chunks[i].data.size();
+       }
+       data_size_ = size;
+
+       viewport()->update();
+}
+
+unsigned int QHexView::get_bytes_per_line() const
+{
+       return BYTES_PER_LINE;
+}
+
+void QHexView::clear()
+{
+       verticalScrollBar()->setValue(0);
+       data_ = nullptr;
+       data_size_ = 0;
+
+       viewport()->update();
+}
+
+void QHexView::showFromOffset(size_t offset)
+{
+       if (data_ && (offset < data_size_)) {
+               setCursorPos(offset * 2);
+
+               int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
+               verticalScrollBar() -> setValue(cursorY);
+       }
+
+       viewport()->update();
+}
+
+QSizePolicy QHexView::sizePolicy() const
+{
+       return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
+}
+
+pair<size_t, size_t> QHexView::get_selection() const
+{
+       size_t start = selectBegin_ / 2;
+       size_t end = selectEnd_ / 2;
+
+       if (start == end) {
+               // Nothing is currently selected
+               start = 0;
+               end = data_size_;
+       } if (end < data_size_)
+               end++;
+
+       return std::make_pair(start, end);
+}
+
+size_t QHexView::create_hex_line(size_t start, size_t end, QString* dest,
+       bool with_offset, bool with_ascii)
+{
+       dest->clear();
+
+       // Determine start address for the row
+       uint64_t row = start / BYTES_PER_LINE;
+       uint64_t offset = row * BYTES_PER_LINE;
+       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());
+
+       initialize_byte_iterator(offset);
+       for (size_t i = offset; i < offset + BYTES_PER_LINE; i++) {
+               uint8_t value = 0;
+
+               if (i < end)
+                       value = get_next_byte();
+
+               if ((i < start) || (i >= end))
+                       dest->append("   ");
+               else
+                       dest->append(QString("%1 ").arg(value, 2, 16, QChar('0')).toUpper());
+       }
+
+       if (with_ascii) {
+               initialize_byte_iterator(offset);
+               for (size_t i = offset; i < end; i++) {
+                       uint8_t value = get_next_byte();
+
+                       if ((value < 0x20) || (value > 0x7E))
+                               value = '.';
+
+                       if (i < start)
+                               dest->append(' ');
+                       else
+                               dest->append((char)value);
+               }
+       }
+
+       return end;
+}
+
+void QHexView::initialize_byte_iterator(size_t offset)
+{
+       current_chunk_id_ = 0;
+       current_chunk_offset_ = 0;
+       current_offset_ = offset;
+
+       size_t chunks = data_->chunks.size();
+       for (size_t i = 0; i < chunks; i++) {
+               size_t size = data_->chunks[i].data.size();
+
+               if (offset >= size) {
+                       current_chunk_id_++;
+                       offset -= size;
+               } else {
+                       current_chunk_offset_ = offset;
+                       break;
+               }
+       }
+
+       if (current_chunk_id_ < data_->chunks.size())
+               current_chunk_ = data_->chunks[current_chunk_id_];
+}
+
+uint8_t QHexView::get_next_byte(bool* is_next_chunk)
+{
+       if (is_next_chunk != nullptr)
+               *is_next_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_offset_++;
+       current_chunk_offset_++;
+
+       if (current_offset_ > data_size_) {
+               qWarning() << "QHexView::get_next_byte() overran binary data boundary:" <<
+                       current_offset_ << "of" << data_size_ << "bytes";
+               return 0xEE;
+       }
+
+       if ((current_chunk_offset_ == current_chunk_.data.size()) && (current_offset_ < data_size_)) {
+               current_chunk_id_++;
+               current_chunk_offset_ = 0;
+               current_chunk_ = data_->chunks[current_chunk_id_];
+       }
+
+       return v;
+}
+
+QSize QHexView::getFullSize() const
+{
+       size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_);
+
+       if (verticalScrollBar()->isEnabled())
+               width += GAP_ASCII_SLIDER + verticalScrollBar()->width();
+
+       if (!data_ || (data_size_ == 0))
+               return QSize(width, 0);
+
+       size_t height = data_size_ / BYTES_PER_LINE;
+
+       if (data_size_ % BYTES_PER_LINE)
+               height++;
+
+       height *= charHeight_;
+
+       return QSize(width, height);
+}
+
+void QHexView::paintEvent(QPaintEvent *event)
+{
+       QPainter painter(viewport());
+
+       // Calculate and update the widget and paint area sizes
+       QSize widgetSize = getFullSize();
+       setMinimumWidth(widgetSize.width());
+       setMaximumWidth(widgetSize.width());
+       QSize areaSize = viewport()->size() - QSize(0, charHeight_);
+
+       // Only show scrollbar if the content goes beyond the visible area
+       if (widgetSize.height() > areaSize.height()) {
+               verticalScrollBar()->setEnabled(true);
+               verticalScrollBar()->setPageStep(areaSize.height() / charHeight_);
+               verticalScrollBar()->setRange(0, ((widgetSize.height() - areaSize.height())) / charHeight_ + 1);
+       } else
+               verticalScrollBar()->setEnabled(false);
+
+       // Fill widget background
+       painter.fillRect(event->rect(), palette().color(QPalette::Base));
+
+       if (!data_ || (data_size_ == 0)) {
+               painter.setPen(palette().color(QPalette::Text));
+               QString s = tr("No data available");
+               int x = (areaSize.width() - fontMetrics().boundingRect(s).width()) / 2;
+               int y = areaSize.height() / 2;
+               painter.drawText(x, y, s);
+               return;
+       }
+
+       // Determine first/last line indices
+       size_t firstLineIdx = verticalScrollBar()->value();
+
+       size_t lastLineIdx = firstLineIdx + (areaSize.height() / charHeight_);
+       if (lastLineIdx > (data_size_ / BYTES_PER_LINE)) {
+               lastLineIdx = data_size_ / BYTES_PER_LINE;
+               if (data_size_ % BYTES_PER_LINE)
+                       lastLineIdx++;
+       }
+
+       // 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());
+
+       // Fill address area background
+       painter.fillRect(QRect(posAddr_, event->rect().top(),
+               posHex_ - (GAP_ADR_HEX / 2), height()), palette().color(QPalette::Window));
+       painter.fillRect(QRect(posAddr_, event->rect().top(),
+               posAscii_ - (GAP_HEX_ASCII / 2), charHeight_ + 2), palette().color(QPalette::Window));
+
+       // Paint address area
+       painter.setPen(palette().color(QPalette::ButtonText));
+
+       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();
+               painter.drawText(posAddr_, y, address);
+               y += charHeight_;
+       }
+
+       // Paint top row with hex offsets
+       painter.setPen(palette().color(QPalette::ButtonText));
+       for (int offset = 0; offset <= 0xF; offset++)
+               painter.drawText(posHex_ + (1 + offset * 3) * charWidth_,
+                       charHeight_ - 3, QString::number(offset, 16).toUpper());
+
+       // Paint hex values
+       QBrush regular = palette().buttonText();
+       QBrush selected = palette().highlight();
+
+       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++) {
+
+               int x = posHex_;
+               for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) {
+                       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);
+
+                       if (is_next_chunk) {
+                               chunk_color++;
+                               if (chunk_color == chunk_colors_.size())
+                                       chunk_color = 0;
+                       }
+
+                       if ((pos >= selectBegin_) && (pos < selectEnd_)) {
+                               painter.setBackgroundMode(Qt::OpaqueMode);
+                               painter.setBackground(selected);
+                               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
+                       QString val = QString::number((byte_value & 0xF0) >> 4, 16).toUpper();
+                       painter.drawText(x, y, val);
+
+                       // Second nibble
+                       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))
+                               painter.drawText(x + 2 * charWidth_, y, QString(' '));
+
+                       x += 3 * charWidth_;
+               }
+
+               y += charHeight_;
+       }
+
+       // Paint ASCII characters
+       initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE);
+       yStart = 2 * charHeight_;
+       for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
+
+               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();
+
+                       if ((ch < 0x20) || (ch > 0x7E))
+                               ch = '.';
+
+                       size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
+                       if ((pos >= selectBegin_) && (pos < selectEnd_)) {
+                               painter.setBackgroundMode(Qt::OpaqueMode);
+                               painter.setBackground(selected);
+                               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));
+                       x += charWidth_;
+               }
+
+               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 = charHeight_ + y * charHeight_ + 4;
+               painter.fillRect(cursorX, cursorY, 2, charHeight_, palette().color(QPalette::WindowText));
+       }
+}
+
+void QHexView::keyPressEvent(QKeyEvent *event)
+{
+       bool setVisible = false;
+
+       // Cursor movements
+       if (event->matches(QKeySequence::MoveToNextChar)) {
+               setCursorPos(cursorPos_ + 1);
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::MoveToPreviousChar)) {
+               setCursorPos(cursorPos_ - 1);
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+
+       if (event->matches(QKeySequence::MoveToEndOfLine)) {
+               setCursorPos(cursorPos_ | ((BYTES_PER_LINE * 2) - 1));
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::MoveToStartOfLine)) {
+               setCursorPos(cursorPos_ | (cursorPos_ % (BYTES_PER_LINE * 2)));
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::MoveToPreviousLine)) {
+               setCursorPos(cursorPos_ - BYTES_PER_LINE * 2);
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::MoveToNextLine)) {
+               setCursorPos(cursorPos_ + BYTES_PER_LINE * 2);
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+
+       if (event->matches(QKeySequence::MoveToNextPage)) {
+               setCursorPos(cursorPos_ + (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::MoveToPreviousPage)) {
+               setCursorPos(cursorPos_ - (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::MoveToEndOfDocument)) {
+               setCursorPos(data_size_ * 2);
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::MoveToStartOfDocument)) {
+               setCursorPos(0);
+               resetSelection(cursorPos_);
+               setVisible = true;
+       }
+
+       // Select commands
+       if (event->matches(QKeySequence::SelectAll)) {
+               resetSelection(0);
+               setSelection(2 * data_size_);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectNextChar)) {
+               int pos = cursorPos_ + 1;
+               setCursorPos(pos);
+               setSelection(pos);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectPreviousChar)) {
+               int pos = cursorPos_ - 1;
+               setSelection(pos);
+               setCursorPos(pos);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectEndOfLine)) {
+               int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
+               setCursorPos(pos);
+               setSelection(pos);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectStartOfLine)) {
+               int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE));
+               setCursorPos(pos);
+               setSelection(pos);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectPreviousLine)) {
+               int pos = cursorPos_ - (2 * BYTES_PER_LINE);
+               setCursorPos(pos);
+               setSelection(pos);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectNextLine)) {
+               int pos = cursorPos_ + (2 * BYTES_PER_LINE);
+               setCursorPos(pos);
+               setSelection(pos);
+               setVisible = true;
+       }
+
+       if (event->matches(QKeySequence::SelectNextPage)) {
+               int pos = cursorPos_ + (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
+               setCursorPos(pos);
+               setSelection(pos);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectPreviousPage)) {
+               int pos = cursorPos_ - (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
+               setCursorPos(pos);
+               setSelection(pos);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectEndOfDocument)) {
+               int pos = data_size_ * 2;
+               setCursorPos(pos);
+               setSelection(pos);
+               setVisible = true;
+       }
+       if (event->matches(QKeySequence::SelectStartOfDocument)) {
+               setCursorPos(0);
+               setSelection(0);
+               setVisible = true;
+       }
+
+       if (event->matches(QKeySequence::Copy) && (data_)) {
+               QString text;
+
+               initialize_byte_iterator(selectBegin_ / 2);
+
+               size_t selectedSize = (selectEnd_ - selectBegin_ + 1) / 2;
+               for (size_t i = 0; i < selectedSize; i++) {
+                       uint8_t byte_value = get_next_byte();
+
+                       QString s = QString::number((byte_value & 0xF0) >> 4, 16).toUpper() +
+                               QString::number((byte_value & 0xF), 16).toUpper() + " ";
+                       text += s;
+
+                       if (i % BYTES_PER_LINE == (BYTES_PER_LINE - 1))
+                               text += "\n";
+               }
+
+               QClipboard *clipboard = QApplication::clipboard();
+               clipboard->setText(text, QClipboard::Clipboard);
+               if (clipboard->supportsSelection())
+                       clipboard->setText(text, QClipboard::Selection);
+       }
+
+       if (setVisible)
+               ensureVisible();
+
+       viewport()->update();
+}
+
+void QHexView::mouseMoveEvent(QMouseEvent *event)
+{
+       int actPos = cursorPosFromMousePos(event->pos());
+       setCursorPos(actPos);
+       setSelection(actPos);
+
+       viewport()->update();
+}
+
+void QHexView::mousePressEvent(QMouseEvent *event)
+{
+       int cPos = cursorPosFromMousePos(event->pos());
+
+       if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) && (event->button() == Qt::LeftButton))
+               setSelection(cPos);
+       else
+               resetSelection(cPos);
+
+       setCursorPos(cPos);
+
+       viewport()->update();
+}
+
+size_t QHexView::cursorPosFromMousePos(const QPoint &position)
+{
+       size_t pos = -1;
+
+       if (((size_t)position.x() >= posHex_) &&
+               ((size_t)position.x() < (posHex_ + HEXCHARS_IN_LINE * charWidth_))) {
+
+               // Note: We add 1.5 character widths so that selection across
+               // byte gaps is smoother
+               size_t x = (position.x() + (1.5 * charWidth_ / 2) - posHex_) / charWidth_;
+
+               // Note: We allow only full bytes to be selected, not nibbles,
+               // so we round to the nearest byte gap
+               x = (2 * x + 1) / 3;
+
+               size_t firstLineIdx = verticalScrollBar()->value();
+               size_t y = ((position.y() / charHeight_) - 1) * 2 * BYTES_PER_LINE;
+               pos = x + y + firstLineIdx * BYTES_PER_LINE * 2;
+       }
+
+       size_t max_pos = data_size_ * 2;
+
+       return std::min(pos, max_pos);
+}
+
+void QHexView::resetSelection()
+{
+       selectBegin_ = selectInit_;
+       selectEnd_ = selectInit_;
+}
+
+void QHexView::resetSelection(int pos)
+{
+       if (pos < 0)
+               pos = 0;
+
+       selectInit_ = pos;
+       selectBegin_ = pos;
+       selectEnd_ = pos;
+}
+
+void QHexView::setSelection(int pos)
+{
+       if (pos < 0)
+               pos = 0;
+
+       if ((size_t)pos >= selectInit_) {
+               selectEnd_ = pos;
+               selectBegin_ = selectInit_;
+       } else {
+               selectBegin_ = pos;
+               selectEnd_ = selectInit_;
+       }
+}
+
+void QHexView::setCursorPos(int position)
+{
+       if (position < 0)
+               position = 0;
+
+       int max_pos = data_size_ * 2;
+
+       if (position > max_pos)
+               position = max_pos;
+
+       cursorPos_ = position;
+}
+
+void QHexView::ensureVisible()
+{
+       QSize areaSize = viewport()->size();
+
+       int firstLineIdx = verticalScrollBar()->value();
+       int lastLineIdx = firstLineIdx + areaSize.height() / charHeight_;
+
+       int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
+
+       if (cursorY < firstLineIdx)
+               verticalScrollBar()->setValue(cursorY);
+       else
+               if(cursorY >= lastLineIdx)
+                       verticalScrollBar()->setValue(cursorY - areaSize.height() / charHeight_ + 1);
+}
diff --git a/pv/views/decoder_binary/QHexView.hpp b/pv/views/decoder_binary/QHexView.hpp
new file mode 100644 (file)
index 0000000..c39dcb2
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2015 Victor Anjin <virinext@gmail.com>
+ * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H
+#define PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H
+
+#include <QAbstractScrollArea>
+
+#include <pv/data/decodesignal.hpp>
+
+using std::pair;
+using std::size_t;
+using pv::data::DecodeBinaryClass;
+using pv::data::DecodeBinaryDataChunk;
+
+class QHexView: public QAbstractScrollArea
+{
+       Q_OBJECT
+
+public:
+       enum Mode {
+               ChunkedDataMode,    ///< Displays all data chunks in succession
+               MemoryEmulationMode ///< Reconstructs memory contents from data chunks
+       };
+
+public:
+       QHexView(QWidget *parent = nullptr);
+
+       void set_mode(Mode m);
+       void set_data(const DecodeBinaryClass* data);
+       unsigned int get_bytes_per_line() const;
+
+       void clear();
+       void showFromOffset(size_t offset);
+       virtual QSizePolicy sizePolicy() const;
+
+       pair<size_t, size_t> get_selection() const;
+
+       size_t create_hex_line(size_t start, size_t end, QString* dest,
+               bool with_offset=false, bool with_ascii=false);
+
+protected:
+       void initialize_byte_iterator(size_t offset);
+       uint8_t get_next_byte(bool* is_next_chunk = nullptr);
+
+       void paintEvent(QPaintEvent *event);
+       void keyPressEvent(QKeyEvent *event);
+       void mouseMoveEvent(QMouseEvent *event);
+       void mousePressEvent(QMouseEvent *event);
+
+private:
+       QSize getFullSize() const;
+       void resetSelection();
+       void resetSelection(int pos);
+       void setSelection(int pos);
+       void ensureVisible();
+       void setCursorPos(int pos);
+       size_t cursorPosFromMousePos(const QPoint &position);
+
+private:
+       Mode mode_;
+       const DecodeBinaryClass* data_;
+       size_t data_size_;
+
+       size_t posAddr_, posHex_, posAscii_;
+       size_t charWidth_, charHeight_;
+       size_t selectBegin_, selectEnd_, selectInit_, cursorPos_;
+
+       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
+
+       vector<QColor> chunk_colors_;
+};
+
+#endif /* PULSEVIEW_PV_VIEWS_DECODERBINARY_QHEXVIEW_H */
diff --git a/pv/views/decoder_binary/view.cpp b/pv/views/decoder_binary/view.cpp
new file mode 100644 (file)
index 0000000..5f55b19
--- /dev/null
@@ -0,0 +1,480 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <climits>
+
+#include <QByteArray>
+#include <QDebug>
+#include <QFileDialog>
+#include <QLabel>
+#include <QMenu>
+#include <QMessageBox>
+#include <QToolBar>
+#include <QVBoxLayout>
+
+#include <libsigrokdecode/libsigrokdecode.h>
+
+#include "view.hpp"
+#include "QHexView.hpp"
+
+#include "pv/globalsettings.hpp"
+#include "pv/session.hpp"
+#include "pv/util.hpp"
+#include "pv/data/decode/decoder.hpp"
+
+using pv::data::DecodeSignal;
+using pv::data::SignalBase;
+using pv::data::decode::Decoder;
+using pv::util::Timestamp;
+
+using std::shared_ptr;
+
+namespace pv {
+namespace views {
+namespace decoder_binary {
+
+const char* SaveTypeNames[SaveTypeCount] = {
+       "Binary",
+       "Hex Dump, plain",
+       "Hex Dump, with offset",
+       "Hex Dump, canonical"
+};
+
+
+View::View(Session &session, bool is_main_view, QMainWindow *parent) :
+       ViewBase(session, is_main_view, parent),
+
+       // Note: Place defaults in View::reset_view_state(), not here
+       parent_(parent),
+       decoder_selector_(new QComboBox()),
+       format_selector_(new QComboBox()),
+       class_selector_(new QComboBox()),
+       stacked_widget_(new QStackedWidget()),
+       hex_view_(new QHexView()),
+       save_button_(new QToolButton()),
+       save_action_(new QAction(this)),
+       signal_(nullptr)
+{
+       QVBoxLayout *root_layout = new QVBoxLayout(this);
+       root_layout->setContentsMargins(0, 0, 0, 0);
+
+       // Create toolbar
+       QToolBar* toolbar = new QToolBar();
+       toolbar->setContextMenuPolicy(Qt::PreventContextMenu);
+       parent->addToolBar(toolbar);
+
+       // Populate toolbar
+       toolbar->addWidget(new QLabel(tr("Decoder:")));
+       toolbar->addWidget(decoder_selector_);
+       toolbar->addWidget(class_selector_);
+       toolbar->addSeparator();
+       toolbar->addWidget(new QLabel(tr("Show data as")));
+       toolbar->addWidget(format_selector_);
+       toolbar->addSeparator();
+       toolbar->addWidget(save_button_);
+
+       // 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_);
+       stacked_widget_->setCurrentIndex(0);
+
+       connect(decoder_selector_, SIGNAL(currentIndexChanged(int)),
+               this, SLOT(on_selected_decoder_changed(int)));
+       connect(class_selector_, SIGNAL(currentIndexChanged(int)),
+               this, SLOT(on_selected_class_changed(int)));
+
+       // Configure widgets
+       decoder_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+       class_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+
+       // Configure actions
+       save_action_->setText(tr("&Save..."));
+       save_action_->setIcon(QIcon::fromTheme("document-save-as",
+               QIcon(":/icons/document-save-as.png")));
+       save_action_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
+       connect(save_action_, SIGNAL(triggered(bool)),
+               this, SLOT(on_actionSave_triggered()));
+
+       QMenu *save_menu = new QMenu();
+       connect(save_menu, SIGNAL(triggered(QAction*)),
+               this, SLOT(on_actionSave_triggered(QAction*)));
+
+       for (int i = 0; i < SaveTypeCount; i++) {
+               QAction *const action = save_menu->addAction(tr(SaveTypeNames[i]));
+               action->setData(qVariantFromValue(i));
+       }
+
+       save_button_->setMenu(save_menu);
+       save_button_->setDefaultAction(save_action_);
+       save_button_->setPopupMode(QToolButton::MenuButtonPopup);
+
+       parent->setSizePolicy(hex_view_->sizePolicy()); // TODO Must be updated when selected widget changes
+
+       reset_view_state();
+}
+
+ViewType View::get_type() const
+{
+       return ViewTypeDecoderBinary;
+}
+
+void View::reset_view_state()
+{
+       ViewBase::reset_view_state();
+
+       decoder_selector_->clear();
+       class_selector_->clear();
+       format_selector_->setCurrentIndex(0);
+       save_button_->setEnabled(false);
+
+       hex_view_->clear();
+}
+
+void View::clear_decode_signals()
+{
+       ViewBase::clear_decode_signals();
+
+       reset_data();
+       reset_view_state();
+}
+
+void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
+{
+       ViewBase::add_decode_signal(signal);
+
+       connect(signal.get(), SIGNAL(name_changed(const QString&)),
+               this, SLOT(on_signal_name_changed(const QString&)));
+       connect(signal.get(), SIGNAL(decoder_stacked(void*)),
+               this, SLOT(on_decoder_stacked(void*)));
+       connect(signal.get(), SIGNAL(decoder_removed(void*)),
+               this, SLOT(on_decoder_removed(void*)));
+
+       // Add all decoders provided by this signal
+       auto stack = signal->decoder_stack();
+       if (stack.size() > 1) {
+               for (const shared_ptr<Decoder>& dec : stack)
+                       // Only add the decoder if it has binary output
+                       if (dec->get_binary_class_count() > 0) {
+                               QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
+                               decoder_selector_->addItem(title, QVariant::fromValue((void*)dec.get()));
+                       }
+       } else
+               if (!stack.empty()) {
+                       shared_ptr<Decoder>& dec = stack.at(0);
+                       if (dec->get_binary_class_count() > 0)
+                               decoder_selector_->addItem(signal->name(), QVariant::fromValue((void*)dec.get()));
+               }
+}
+
+void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
+{
+       // Remove all decoders provided by this signal
+       for (const shared_ptr<Decoder>& dec : signal->decoder_stack()) {
+               int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
+
+               if (index != -1)
+                       decoder_selector_->removeItem(index);
+       }
+
+       ViewBase::remove_decode_signal(signal);
+
+       if (signal.get() == signal_) {
+               reset_data();
+               update_data();
+               reset_view_state();
+       }
+}
+
+void View::save_settings(QSettings &settings) const
+{
+       (void)settings;
+}
+
+void View::restore_settings(QSettings &settings)
+{
+       // Note: It is assumed that this function is only called once,
+       // immediately after restoring a previous session.
+       (void)settings;
+}
+
+void View::reset_data()
+{
+       signal_ = nullptr;
+       decoder_ = nullptr;
+       bin_class_id_ = 0;
+       binary_data_exists_ = false;
+
+       hex_view_->clear();
+}
+
+void View::update_data()
+{
+       if (!signal_)
+               return;
+
+       const DecodeBinaryClass* bin_class =
+               signal_->get_binary_data_class(current_segment_, decoder_, bin_class_id_);
+
+       hex_view_->set_data(bin_class);
+
+       if (!binary_data_exists_)
+               return;
+
+       if (!save_button_->isEnabled())
+               save_button_->setEnabled(true);
+}
+
+void View::save_data() const
+{
+       assert(decoder_);
+       assert(signal_);
+
+       if (!signal_)
+               return;
+
+       GlobalSettings settings;
+       const QString dir = settings.value("MainWindow/SaveDirectory").toString();
+
+       const QString file_name = QFileDialog::getSaveFileName(
+               parent_, tr("Save Binary Data"), dir, tr("Binary Data Files (*.bin);;All Files (*)"));
+
+       if (file_name.isEmpty())
+               return;
+
+       QFile file(file_name);
+       if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+               pair<size_t, size_t> selection = hex_view_->get_selection();
+
+               vector<uint8_t> data;
+               data.resize(selection.second - selection.first + 1);
+
+               signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
+                       bin_class_id_, selection.first, selection.second, &data);
+
+               int64_t bytes_written = file.write((const char*)data.data(), data.size());
+
+               if ((bytes_written == -1) || ((uint64_t)bytes_written != data.size())) {
+                       QMessageBox msg(parent_);
+                       msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
+                       msg.setStandardButtons(QMessageBox::Ok);
+                       msg.setIcon(QMessageBox::Warning);
+                       msg.exec();
+                       return;
+               }
+       }
+}
+
+void View::save_data_as_hex_dump(bool with_offset, bool with_ascii) const
+{
+       assert(decoder_);
+       assert(signal_);
+
+       if (!signal_)
+               return;
+
+       GlobalSettings settings;
+       const QString dir = settings.value("MainWindow/SaveDirectory").toString();
+
+       const QString file_name = QFileDialog::getSaveFileName(
+               parent_, tr("Save Binary Data"), dir, tr("Hex Dumps (*.txt);;All Files (*)"));
+
+       if (file_name.isEmpty())
+               return;
+
+       QFile file(file_name);
+       if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
+               pair<size_t, size_t> selection = hex_view_->get_selection();
+
+               vector<uint8_t> data;
+               data.resize(selection.second - selection.first + 1);
+
+               signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
+                       bin_class_id_, selection.first, selection.second, &data);
+
+               QTextStream out_stream(&file);
+
+               uint64_t offset = selection.first;
+               uint64_t n = hex_view_->get_bytes_per_line();
+               QString s;
+
+               while (offset < selection.second) {
+                       size_t end = std::min((uint64_t)(selection.second), offset + n);
+                       offset = hex_view_->create_hex_line(offset, end, &s, with_offset, with_ascii);
+                       out_stream << s << endl;
+               }
+
+               out_stream << endl;
+
+               if (out_stream.status() != QTextStream::Ok) {
+                       QMessageBox msg(parent_);
+                       msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
+                       msg.setStandardButtons(QMessageBox::Ok);
+                       msg.setIcon(QMessageBox::Warning);
+                       msg.exec();
+                       return;
+               }
+       }
+}
+
+void View::on_selected_decoder_changed(int index)
+{
+       if (signal_)
+               disconnect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)));
+
+       reset_data();
+
+       decoder_ = (Decoder*)decoder_selector_->itemData(index).value<void*>();
+
+       // Find the signal that contains the selected decoder
+       for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
+               for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
+                       if (decoder_ == dec.get())
+                               signal_ = ds.get();
+
+       class_selector_->clear();
+
+       if (signal_) {
+               // Populate binary class selector
+               uint32_t bin_classes = decoder_->get_binary_class_count();
+               for (uint32_t i = 0; i < bin_classes; i++) {
+                       const data::decode::DecodeBinaryClassInfo* class_info = decoder_->get_binary_class(i);
+                       class_selector_->addItem(class_info->description, QVariant::fromValue(i));
+               }
+
+               connect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)),
+                       this, SLOT(on_new_binary_data(unsigned int, void*, unsigned int)));
+       }
+
+       update_data();
+}
+
+void View::on_selected_class_changed(int index)
+{
+       bin_class_id_ = class_selector_->itemData(index).value<uint32_t>();
+
+       binary_data_exists_ =
+               signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_);
+
+       update_data();
+}
+
+void View::on_signal_name_changed(const QString &name)
+{
+       (void)name;
+
+       SignalBase* sb = qobject_cast<SignalBase*>(QObject::sender());
+       assert(sb);
+
+       DecodeSignal* signal = dynamic_cast<DecodeSignal*>(sb);
+       assert(signal);
+
+       // Update all decoder entries provided by this signal
+       auto stack = signal->decoder_stack();
+       if (stack.size() > 1) {
+               for (const shared_ptr<Decoder>& dec : stack) {
+                       QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
+                       int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
+
+                       if (index != -1)
+                               decoder_selector_->setItemText(index, title);
+               }
+       } else
+               if (!stack.empty()) {
+                       shared_ptr<Decoder>& dec = stack.at(0);
+                       int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
+
+                       if (index != -1)
+                               decoder_selector_->setItemText(index, signal->name());
+               }
+}
+
+void View::on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id)
+{
+       if ((segment_id == current_segment_) && (decoder == decoder_) && (bin_class_id == bin_class_id_))
+               if (!delayed_view_updater_.isActive())
+                       delayed_view_updater_.start();
+}
+
+void View::on_decoder_stacked(void* decoder)
+{
+       // TODO This doesn't change existing entries for the same signal - but it should as the naming scheme may change
+
+       Decoder* d = static_cast<Decoder*>(decoder);
+
+       // Only add the decoder if it has binary output
+       if (d->get_binary_class_count() == 0)
+               return;
+
+       // Find the signal that contains the selected decoder
+       DecodeSignal* signal = nullptr;
+
+       for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
+               for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
+                       if (d == dec.get())
+                               signal = ds.get();
+
+       assert(signal);
+
+       // Add the decoder to the list
+       QString title = QString("%1 (%2)").arg(signal->name(), d->name());
+       decoder_selector_->addItem(title, QVariant::fromValue((void*)d));
+}
+
+void View::on_decoder_removed(void* decoder)
+{
+       Decoder* d = static_cast<Decoder*>(decoder);
+
+       // Remove the decoder from the list
+       int index = decoder_selector_->findData(QVariant::fromValue((void*)d));
+
+       if (index != -1)
+               decoder_selector_->removeItem(index);
+}
+
+void View::on_actionSave_triggered(QAction* action)
+{
+       int save_type = SaveTypeBinary;
+       if (action)
+               save_type = action->data().toInt();
+
+       switch (save_type)
+       {
+       case SaveTypeBinary: save_data(); break;
+       case SaveTypeHexDumpPlain: save_data_as_hex_dump(false, false); break;
+       case SaveTypeHexDumpWithOffset: save_data_as_hex_dump(true, false); break;
+       case SaveTypeHexDumpComplete: save_data_as_hex_dump(true, true); break;
+       }
+}
+
+void View::perform_delayed_view_update()
+{
+       if (signal_ && !binary_data_exists_)
+               if (signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_))
+                       binary_data_exists_ = true;
+
+       update_data();
+}
+
+
+} // namespace decoder_binary
+} // namespace views
+} // namespace pv
diff --git a/pv/views/decoder_binary/view.hpp b/pv/views/decoder_binary/view.hpp
new file mode 100644 (file)
index 0000000..c1d70b1
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * This file is part of the PulseView project.
+ *
+ * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP
+#define PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP
+
+#include <QAction>
+#include <QComboBox>
+#include <QStackedWidget>
+#include <QToolButton>
+
+#include <pv/views/viewbase.hpp>
+#include <pv/data/decodesignal.hpp>
+
+#include "QHexView.hpp"
+
+namespace pv {
+
+class Session;
+
+namespace views {
+
+namespace decoder_binary {
+
+// When adding an entry here, don't forget to update SaveTypeNames as well
+enum SaveType {
+       SaveTypeBinary,
+       SaveTypeHexDumpPlain,
+       SaveTypeHexDumpWithOffset,
+       SaveTypeHexDumpComplete,
+       SaveTypeCount  // Indicates how many save types there are, must always be last
+};
+
+extern const char* SaveTypeNames[SaveTypeCount];
+
+
+class View : public ViewBase
+{
+       Q_OBJECT
+
+public:
+       explicit View(Session &session, bool is_main_view=false, QMainWindow *parent = nullptr);
+
+       virtual ViewType get_type() const;
+
+       /**
+        * Resets the view to its default state after construction. It does however
+        * not reset the signal bases or any other connections with the session.
+        */
+       virtual void reset_view_state();
+
+       virtual void clear_decode_signals();
+       virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
+       virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
+
+       virtual void save_settings(QSettings &settings) const;
+       virtual void restore_settings(QSettings &settings);
+
+private:
+       void reset_data();
+       void update_data();
+
+       void save_data() const;
+       void save_data_as_hex_dump(bool with_offset=false, bool with_ascii=false) const;
+
+private Q_SLOTS:
+       void on_selected_decoder_changed(int index);
+       void on_selected_class_changed(int index);
+       void on_signal_name_changed(const QString &name);
+       void on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id);
+
+       void on_decoder_stacked(void* decoder);
+       void on_decoder_removed(void* decoder);
+
+       void on_actionSave_triggered(QAction* action = nullptr);
+
+       virtual void perform_delayed_view_update();
+
+private:
+       QWidget* parent_;
+
+       QComboBox *decoder_selector_, *format_selector_, *class_selector_;
+       QStackedWidget *stacked_widget_;
+       QHexView *hex_view_;
+
+       QToolButton* save_button_;
+       QAction* save_action_;
+
+       data::DecodeSignal *signal_;
+       const data::decode::Decoder *decoder_;
+       uint32_t bin_class_id_;
+       bool binary_data_exists_;
+};
+
+} // namespace decoder_binary
+} // namespace views
+} // namespace pv
+
+#endif // PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP
diff --git a/pv/views/decoder_output/QHexView.cpp b/pv/views/decoder_output/QHexView.cpp
deleted file mode 100644 (file)
index ccd50f8..0000000
+++ /dev/null
@@ -1,689 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2015 Victor Anjin <virinext@gmail.com>
- * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
- *
- * The MIT License (MIT)
- *
- * Copyright (c) 2015
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#include <QApplication>
-#include <QClipboard>
-#include <QDebug>
-#include <QFont>
-#include <QKeyEvent>
-#include <QScrollBar>
-#include <QSize>
-#include <QPainter>
-#include <QPaintEvent>
-
-#include "QHexView.hpp"
-
-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;
-
-
-QHexView::QHexView(QWidget *parent):
-       QAbstractScrollArea(parent),
-       mode_(ChunkedDataMode),
-       data_(nullptr),
-       selectBegin_(0),
-       selectEnd_(0),
-       cursorPos_(0)
-{
-       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) {
-               // Color is bright
-               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
-       } 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
-       }
-}
-
-void QHexView::set_mode(Mode m)
-{
-       mode_ = m;
-
-       // This is not expected to be set when data is showing,
-       // so we don't update the viewport here
-}
-
-void QHexView::set_data(const DecodeBinaryClass* data)
-{
-       data_ = data;
-
-       size_t size = 0;
-       if (data) {
-               size_t chunks = data_->chunks.size();
-               for (size_t i = 0; i < chunks; i++)
-                       size += data_->chunks[i].data.size();
-       }
-       data_size_ = size;
-
-       viewport()->update();
-}
-
-unsigned int QHexView::get_bytes_per_line() const
-{
-       return BYTES_PER_LINE;
-}
-
-void QHexView::clear()
-{
-       verticalScrollBar()->setValue(0);
-       data_ = nullptr;
-       data_size_ = 0;
-
-       viewport()->update();
-}
-
-void QHexView::showFromOffset(size_t offset)
-{
-       if (data_ && (offset < data_size_)) {
-               setCursorPos(offset * 2);
-
-               int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
-               verticalScrollBar() -> setValue(cursorY);
-       }
-
-       viewport()->update();
-}
-
-QSizePolicy QHexView::sizePolicy() const
-{
-       return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding);
-}
-
-pair<size_t, size_t> QHexView::get_selection() const
-{
-       size_t start = selectBegin_ / 2;
-       size_t end = selectEnd_ / 2;
-
-       if (start == end) {
-               // Nothing is currently selected
-               start = 0;
-               end = data_size_;
-       } if (end < data_size_)
-               end++;
-
-       return std::make_pair(start, end);
-}
-
-size_t QHexView::create_hex_line(size_t start, size_t end, QString* dest,
-       bool with_offset, bool with_ascii)
-{
-       dest->clear();
-
-       // Determine start address for the row
-       uint64_t row = start / BYTES_PER_LINE;
-       uint64_t offset = row * BYTES_PER_LINE;
-       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());
-
-       initialize_byte_iterator(offset);
-       for (size_t i = offset; i < offset + BYTES_PER_LINE; i++) {
-               uint8_t value = 0;
-
-               if (i < end)
-                       value = get_next_byte();
-
-               if ((i < start) || (i >= end))
-                       dest->append("   ");
-               else
-                       dest->append(QString("%1 ").arg(value, 2, 16, QChar('0')).toUpper());
-       }
-
-       if (with_ascii) {
-               initialize_byte_iterator(offset);
-               for (size_t i = offset; i < end; i++) {
-                       uint8_t value = get_next_byte();
-
-                       if ((value < 0x20) || (value > 0x7E))
-                               value = '.';
-
-                       if (i < start)
-                               dest->append(' ');
-                       else
-                               dest->append((char)value);
-               }
-       }
-
-       return end;
-}
-
-void QHexView::initialize_byte_iterator(size_t offset)
-{
-       current_chunk_id_ = 0;
-       current_chunk_offset_ = 0;
-       current_offset_ = offset;
-
-       size_t chunks = data_->chunks.size();
-       for (size_t i = 0; i < chunks; i++) {
-               size_t size = data_->chunks[i].data.size();
-
-               if (offset >= size) {
-                       current_chunk_id_++;
-                       offset -= size;
-               } else {
-                       current_chunk_offset_ = offset;
-                       break;
-               }
-       }
-
-       if (current_chunk_id_ < data_->chunks.size())
-               current_chunk_ = data_->chunks[current_chunk_id_];
-}
-
-uint8_t QHexView::get_next_byte(bool* is_next_chunk)
-{
-       if (is_next_chunk != nullptr)
-               *is_next_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_offset_++;
-       current_chunk_offset_++;
-
-       if (current_offset_ > data_size_) {
-               qWarning() << "QHexView::get_next_byte() overran binary data boundary:" <<
-                       current_offset_ << "of" << data_size_ << "bytes";
-               return 0xEE;
-       }
-
-       if ((current_chunk_offset_ == current_chunk_.data.size()) && (current_offset_ < data_size_)) {
-               current_chunk_id_++;
-               current_chunk_offset_ = 0;
-               current_chunk_ = data_->chunks[current_chunk_id_];
-       }
-
-       return v;
-}
-
-QSize QHexView::getFullSize() const
-{
-       size_t width = posAscii_ + (BYTES_PER_LINE * charWidth_);
-
-       if (verticalScrollBar()->isEnabled())
-               width += GAP_ASCII_SLIDER + verticalScrollBar()->width();
-
-       if (!data_ || (data_size_ == 0))
-               return QSize(width, 0);
-
-       size_t height = data_size_ / BYTES_PER_LINE;
-
-       if (data_size_ % BYTES_PER_LINE)
-               height++;
-
-       height *= charHeight_;
-
-       return QSize(width, height);
-}
-
-void QHexView::paintEvent(QPaintEvent *event)
-{
-       QPainter painter(viewport());
-
-       // Calculate and update the widget and paint area sizes
-       QSize widgetSize = getFullSize();
-       setMinimumWidth(widgetSize.width());
-       setMaximumWidth(widgetSize.width());
-       QSize areaSize = viewport()->size() - QSize(0, charHeight_);
-
-       // Only show scrollbar if the content goes beyond the visible area
-       if (widgetSize.height() > areaSize.height()) {
-               verticalScrollBar()->setEnabled(true);
-               verticalScrollBar()->setPageStep(areaSize.height() / charHeight_);
-               verticalScrollBar()->setRange(0, ((widgetSize.height() - areaSize.height())) / charHeight_ + 1);
-       } else
-               verticalScrollBar()->setEnabled(false);
-
-       // Fill widget background
-       painter.fillRect(event->rect(), palette().color(QPalette::Base));
-
-       if (!data_ || (data_size_ == 0)) {
-               painter.setPen(palette().color(QPalette::Text));
-               QString s = tr("No data available");
-               int x = (areaSize.width() - fontMetrics().boundingRect(s).width()) / 2;
-               int y = areaSize.height() / 2;
-               painter.drawText(x, y, s);
-               return;
-       }
-
-       // Determine first/last line indices
-       size_t firstLineIdx = verticalScrollBar()->value();
-
-       size_t lastLineIdx = firstLineIdx + (areaSize.height() / charHeight_);
-       if (lastLineIdx > (data_size_ / BYTES_PER_LINE)) {
-               lastLineIdx = data_size_ / BYTES_PER_LINE;
-               if (data_size_ % BYTES_PER_LINE)
-                       lastLineIdx++;
-       }
-
-       // 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());
-
-       // Fill address area background
-       painter.fillRect(QRect(posAddr_, event->rect().top(),
-               posHex_ - (GAP_ADR_HEX / 2), height()), palette().color(QPalette::Window));
-       painter.fillRect(QRect(posAddr_, event->rect().top(),
-               posAscii_ - (GAP_HEX_ASCII / 2), charHeight_ + 2), palette().color(QPalette::Window));
-
-       // Paint address area
-       painter.setPen(palette().color(QPalette::ButtonText));
-
-       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();
-               painter.drawText(posAddr_, y, address);
-               y += charHeight_;
-       }
-
-       // Paint top row with hex offsets
-       painter.setPen(palette().color(QPalette::ButtonText));
-       for (int offset = 0; offset <= 0xF; offset++)
-               painter.drawText(posHex_ + (1 + offset * 3) * charWidth_,
-                       charHeight_ - 3, QString::number(offset, 16).toUpper());
-
-       // Paint hex values
-       QBrush regular = palette().buttonText();
-       QBrush selected = palette().highlight();
-
-       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++) {
-
-               int x = posHex_;
-               for (size_t i = 0; (i < BYTES_PER_LINE) && (current_offset_ < data_size_); i++) {
-                       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);
-
-                       if (is_next_chunk) {
-                               chunk_color++;
-                               if (chunk_color == chunk_colors_.size())
-                                       chunk_color = 0;
-                       }
-
-                       if ((pos >= selectBegin_) && (pos < selectEnd_)) {
-                               painter.setBackgroundMode(Qt::OpaqueMode);
-                               painter.setBackground(selected);
-                               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
-                       QString val = QString::number((byte_value & 0xF0) >> 4, 16).toUpper();
-                       painter.drawText(x, y, val);
-
-                       // Second nibble
-                       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))
-                               painter.drawText(x + 2 * charWidth_, y, QString(' '));
-
-                       x += 3 * charWidth_;
-               }
-
-               y += charHeight_;
-       }
-
-       // Paint ASCII characters
-       initialize_byte_iterator(firstLineIdx * BYTES_PER_LINE);
-       yStart = 2 * charHeight_;
-       for (size_t lineIdx = firstLineIdx, y = yStart; lineIdx < lastLineIdx; lineIdx++) {
-
-               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();
-
-                       if ((ch < 0x20) || (ch > 0x7E))
-                               ch = '.';
-
-                       size_t pos = (lineIdx * BYTES_PER_LINE + i) * 2;
-                       if ((pos >= selectBegin_) && (pos < selectEnd_)) {
-                               painter.setBackgroundMode(Qt::OpaqueMode);
-                               painter.setBackground(selected);
-                               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));
-                       x += charWidth_;
-               }
-
-               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 = charHeight_ + y * charHeight_ + 4;
-               painter.fillRect(cursorX, cursorY, 2, charHeight_, palette().color(QPalette::WindowText));
-       }
-}
-
-void QHexView::keyPressEvent(QKeyEvent *event)
-{
-       bool setVisible = false;
-
-       // Cursor movements
-       if (event->matches(QKeySequence::MoveToNextChar)) {
-               setCursorPos(cursorPos_ + 1);
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::MoveToPreviousChar)) {
-               setCursorPos(cursorPos_ - 1);
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-
-       if (event->matches(QKeySequence::MoveToEndOfLine)) {
-               setCursorPos(cursorPos_ | ((BYTES_PER_LINE * 2) - 1));
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::MoveToStartOfLine)) {
-               setCursorPos(cursorPos_ | (cursorPos_ % (BYTES_PER_LINE * 2)));
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::MoveToPreviousLine)) {
-               setCursorPos(cursorPos_ - BYTES_PER_LINE * 2);
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::MoveToNextLine)) {
-               setCursorPos(cursorPos_ + BYTES_PER_LINE * 2);
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-
-       if (event->matches(QKeySequence::MoveToNextPage)) {
-               setCursorPos(cursorPos_ + (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::MoveToPreviousPage)) {
-               setCursorPos(cursorPos_ - (viewport()->height() / charHeight_ - 1) * 2 * BYTES_PER_LINE);
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::MoveToEndOfDocument)) {
-               setCursorPos(data_size_ * 2);
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::MoveToStartOfDocument)) {
-               setCursorPos(0);
-               resetSelection(cursorPos_);
-               setVisible = true;
-       }
-
-       // Select commands
-       if (event->matches(QKeySequence::SelectAll)) {
-               resetSelection(0);
-               setSelection(2 * data_size_);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectNextChar)) {
-               int pos = cursorPos_ + 1;
-               setCursorPos(pos);
-               setSelection(pos);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectPreviousChar)) {
-               int pos = cursorPos_ - 1;
-               setSelection(pos);
-               setCursorPos(pos);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectEndOfLine)) {
-               int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
-               setCursorPos(pos);
-               setSelection(pos);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectStartOfLine)) {
-               int pos = cursorPos_ - (cursorPos_ % (2 * BYTES_PER_LINE));
-               setCursorPos(pos);
-               setSelection(pos);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectPreviousLine)) {
-               int pos = cursorPos_ - (2 * BYTES_PER_LINE);
-               setCursorPos(pos);
-               setSelection(pos);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectNextLine)) {
-               int pos = cursorPos_ + (2 * BYTES_PER_LINE);
-               setCursorPos(pos);
-               setSelection(pos);
-               setVisible = true;
-       }
-
-       if (event->matches(QKeySequence::SelectNextPage)) {
-               int pos = cursorPos_ + (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
-               setCursorPos(pos);
-               setSelection(pos);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectPreviousPage)) {
-               int pos = cursorPos_ - (((viewport()->height() / charHeight_) - 1) * 2 * BYTES_PER_LINE);
-               setCursorPos(pos);
-               setSelection(pos);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectEndOfDocument)) {
-               int pos = data_size_ * 2;
-               setCursorPos(pos);
-               setSelection(pos);
-               setVisible = true;
-       }
-       if (event->matches(QKeySequence::SelectStartOfDocument)) {
-               setCursorPos(0);
-               setSelection(0);
-               setVisible = true;
-       }
-
-       if (event->matches(QKeySequence::Copy) && (data_)) {
-               QString text;
-
-               initialize_byte_iterator(selectBegin_ / 2);
-
-               size_t selectedSize = (selectEnd_ - selectBegin_ + 1) / 2;
-               for (size_t i = 0; i < selectedSize; i++) {
-                       uint8_t byte_value = get_next_byte();
-
-                       QString s = QString::number((byte_value & 0xF0) >> 4, 16).toUpper() +
-                               QString::number((byte_value & 0xF), 16).toUpper() + " ";
-                       text += s;
-
-                       if (i % BYTES_PER_LINE == (BYTES_PER_LINE - 1))
-                               text += "\n";
-               }
-
-               QClipboard *clipboard = QApplication::clipboard();
-               clipboard->setText(text, QClipboard::Clipboard);
-               if (clipboard->supportsSelection())
-                       clipboard->setText(text, QClipboard::Selection);
-       }
-
-       if (setVisible)
-               ensureVisible();
-
-       viewport()->update();
-}
-
-void QHexView::mouseMoveEvent(QMouseEvent *event)
-{
-       int actPos = cursorPosFromMousePos(event->pos());
-       setCursorPos(actPos);
-       setSelection(actPos);
-
-       viewport()->update();
-}
-
-void QHexView::mousePressEvent(QMouseEvent *event)
-{
-       int cPos = cursorPosFromMousePos(event->pos());
-
-       if ((QApplication::keyboardModifiers() & Qt::ShiftModifier) && (event->button() == Qt::LeftButton))
-               setSelection(cPos);
-       else
-               resetSelection(cPos);
-
-       setCursorPos(cPos);
-
-       viewport()->update();
-}
-
-size_t QHexView::cursorPosFromMousePos(const QPoint &position)
-{
-       size_t pos = -1;
-
-       if (((size_t)position.x() >= posHex_) &&
-               ((size_t)position.x() < (posHex_ + HEXCHARS_IN_LINE * charWidth_))) {
-
-               // Note: We add 1.5 character widths so that selection across
-               // byte gaps is smoother
-               size_t x = (position.x() + (1.5 * charWidth_ / 2) - posHex_) / charWidth_;
-
-               // Note: We allow only full bytes to be selected, not nibbles,
-               // so we round to the nearest byte gap
-               x = (2 * x + 1) / 3;
-
-               size_t firstLineIdx = verticalScrollBar()->value();
-               size_t y = ((position.y() / charHeight_) - 1) * 2 * BYTES_PER_LINE;
-               pos = x + y + firstLineIdx * BYTES_PER_LINE * 2;
-       }
-
-       size_t max_pos = data_size_ * 2;
-
-       return std::min(pos, max_pos);
-}
-
-void QHexView::resetSelection()
-{
-       selectBegin_ = selectInit_;
-       selectEnd_ = selectInit_;
-}
-
-void QHexView::resetSelection(int pos)
-{
-       if (pos < 0)
-               pos = 0;
-
-       selectInit_ = pos;
-       selectBegin_ = pos;
-       selectEnd_ = pos;
-}
-
-void QHexView::setSelection(int pos)
-{
-       if (pos < 0)
-               pos = 0;
-
-       if ((size_t)pos >= selectInit_) {
-               selectEnd_ = pos;
-               selectBegin_ = selectInit_;
-       } else {
-               selectBegin_ = pos;
-               selectEnd_ = selectInit_;
-       }
-}
-
-void QHexView::setCursorPos(int position)
-{
-       if (position < 0)
-               position = 0;
-
-       int max_pos = data_size_ * 2;
-
-       if (position > max_pos)
-               position = max_pos;
-
-       cursorPos_ = position;
-}
-
-void QHexView::ensureVisible()
-{
-       QSize areaSize = viewport()->size();
-
-       int firstLineIdx = verticalScrollBar()->value();
-       int lastLineIdx = firstLineIdx + areaSize.height() / charHeight_;
-
-       int cursorY = cursorPos_ / (2 * BYTES_PER_LINE);
-
-       if (cursorY < firstLineIdx)
-               verticalScrollBar()->setValue(cursorY);
-       else
-               if(cursorY >= lastLineIdx)
-                       verticalScrollBar()->setValue(cursorY - areaSize.height() / charHeight_ + 1);
-}
diff --git a/pv/views/decoder_output/QHexView.hpp b/pv/views/decoder_output/QHexView.hpp
deleted file mode 100644 (file)
index e3c182a..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2015 Victor Anjin <virinext@gmail.com>
- * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
- *
- * The MIT License (MIT)
- *
- * Copyright (c) 2015
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-#ifndef PULSEVIEW_PV_VIEWS_DECODEROUTPUT_QHEXVIEW_H
-#define PULSEVIEW_PV_VIEWS_DECODEROUTPUT_QHEXVIEW_H
-
-#include <QAbstractScrollArea>
-
-#include <pv/data/decodesignal.hpp>
-
-using std::pair;
-using std::size_t;
-using pv::data::DecodeBinaryClass;
-using pv::data::DecodeBinaryDataChunk;
-
-class QHexView: public QAbstractScrollArea
-{
-       Q_OBJECT
-
-public:
-       enum Mode {
-               ChunkedDataMode,    ///< Displays all data chunks in succession
-               MemoryEmulationMode ///< Reconstructs memory contents from data chunks
-       };
-
-public:
-       QHexView(QWidget *parent = nullptr);
-
-       void set_mode(Mode m);
-       void set_data(const DecodeBinaryClass* data);
-       unsigned int get_bytes_per_line() const;
-
-       void clear();
-       void showFromOffset(size_t offset);
-       virtual QSizePolicy sizePolicy() const;
-
-       pair<size_t, size_t> get_selection() const;
-
-       size_t create_hex_line(size_t start, size_t end, QString* dest,
-               bool with_offset=false, bool with_ascii=false);
-
-protected:
-       void initialize_byte_iterator(size_t offset);
-       uint8_t get_next_byte(bool* is_next_chunk = nullptr);
-
-       void paintEvent(QPaintEvent *event);
-       void keyPressEvent(QKeyEvent *event);
-       void mouseMoveEvent(QMouseEvent *event);
-       void mousePressEvent(QMouseEvent *event);
-
-private:
-       QSize getFullSize() const;
-       void resetSelection();
-       void resetSelection(int pos);
-       void setSelection(int pos);
-       void ensureVisible();
-       void setCursorPos(int pos);
-       size_t cursorPosFromMousePos(const QPoint &position);
-
-private:
-       Mode mode_;
-       const DecodeBinaryClass* data_;
-       size_t data_size_;
-
-       size_t posAddr_, posHex_, posAscii_;
-       size_t charWidth_, charHeight_;
-       size_t selectBegin_, selectEnd_, selectInit_, cursorPos_;
-
-       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
-
-       vector<QColor> chunk_colors_;
-};
-
-#endif /* PULSEVIEW_PV_VIEWS_DECODEROUTPUT_QHEXVIEW_H */
diff --git a/pv/views/decoder_output/view.cpp b/pv/views/decoder_output/view.cpp
deleted file mode 100644 (file)
index 0f127c8..0000000
+++ /dev/null
@@ -1,480 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <climits>
-
-#include <QByteArray>
-#include <QDebug>
-#include <QFileDialog>
-#include <QLabel>
-#include <QMenu>
-#include <QMessageBox>
-#include <QToolBar>
-#include <QVBoxLayout>
-
-#include <libsigrokdecode/libsigrokdecode.h>
-
-#include "view.hpp"
-#include "QHexView.hpp"
-
-#include "pv/globalsettings.hpp"
-#include "pv/session.hpp"
-#include "pv/util.hpp"
-#include "pv/data/decode/decoder.hpp"
-
-using pv::data::DecodeSignal;
-using pv::data::SignalBase;
-using pv::data::decode::Decoder;
-using pv::util::Timestamp;
-
-using std::shared_ptr;
-
-namespace pv {
-namespace views {
-namespace decoder_output {
-
-const char* SaveTypeNames[SaveTypeCount] = {
-       "Binary",
-       "Hex Dump, plain",
-       "Hex Dump, with offset",
-       "Hex Dump, canonical"
-};
-
-
-View::View(Session &session, bool is_main_view, QMainWindow *parent) :
-       ViewBase(session, is_main_view, parent),
-
-       // Note: Place defaults in View::reset_view_state(), not here
-       parent_(parent),
-       decoder_selector_(new QComboBox()),
-       format_selector_(new QComboBox()),
-       class_selector_(new QComboBox()),
-       stacked_widget_(new QStackedWidget()),
-       hex_view_(new QHexView()),
-       save_button_(new QToolButton()),
-       save_action_(new QAction(this)),
-       signal_(nullptr)
-{
-       QVBoxLayout *root_layout = new QVBoxLayout(this);
-       root_layout->setContentsMargins(0, 0, 0, 0);
-
-       // Create toolbar
-       QToolBar* toolbar = new QToolBar();
-       toolbar->setContextMenuPolicy(Qt::PreventContextMenu);
-       parent->addToolBar(toolbar);
-
-       // Populate toolbar
-       toolbar->addWidget(new QLabel(tr("Decoder:")));
-       toolbar->addWidget(decoder_selector_);
-       toolbar->addWidget(class_selector_);
-       toolbar->addSeparator();
-       toolbar->addWidget(new QLabel(tr("Show data as")));
-       toolbar->addWidget(format_selector_);
-       toolbar->addSeparator();
-       toolbar->addWidget(save_button_);
-
-       // 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_);
-       stacked_widget_->setCurrentIndex(0);
-
-       connect(decoder_selector_, SIGNAL(currentIndexChanged(int)),
-               this, SLOT(on_selected_decoder_changed(int)));
-       connect(class_selector_, SIGNAL(currentIndexChanged(int)),
-               this, SLOT(on_selected_class_changed(int)));
-
-       // Configure widgets
-       decoder_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
-       class_selector_->setSizeAdjustPolicy(QComboBox::AdjustToContents);
-
-       // Configure actions
-       save_action_->setText(tr("&Save..."));
-       save_action_->setIcon(QIcon::fromTheme("document-save-as",
-               QIcon(":/icons/document-save-as.png")));
-       save_action_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
-       connect(save_action_, SIGNAL(triggered(bool)),
-               this, SLOT(on_actionSave_triggered()));
-
-       QMenu *save_menu = new QMenu();
-       connect(save_menu, SIGNAL(triggered(QAction*)),
-               this, SLOT(on_actionSave_triggered(QAction*)));
-
-       for (int i = 0; i < SaveTypeCount; i++) {
-               QAction *const action = save_menu->addAction(tr(SaveTypeNames[i]));
-               action->setData(qVariantFromValue(i));
-       }
-
-       save_button_->setMenu(save_menu);
-       save_button_->setDefaultAction(save_action_);
-       save_button_->setPopupMode(QToolButton::MenuButtonPopup);
-
-       parent->setSizePolicy(hex_view_->sizePolicy()); // TODO Must be updated when selected widget changes
-
-       reset_view_state();
-}
-
-ViewType View::get_type() const
-{
-       return ViewTypeDecoderOutput;
-}
-
-void View::reset_view_state()
-{
-       ViewBase::reset_view_state();
-
-       decoder_selector_->clear();
-       class_selector_->clear();
-       format_selector_->setCurrentIndex(0);
-       save_button_->setEnabled(false);
-
-       hex_view_->clear();
-}
-
-void View::clear_decode_signals()
-{
-       ViewBase::clear_decode_signals();
-
-       reset_data();
-       reset_view_state();
-}
-
-void View::add_decode_signal(shared_ptr<data::DecodeSignal> signal)
-{
-       ViewBase::add_decode_signal(signal);
-
-       connect(signal.get(), SIGNAL(name_changed(const QString&)),
-               this, SLOT(on_signal_name_changed(const QString&)));
-       connect(signal.get(), SIGNAL(decoder_stacked(void*)),
-               this, SLOT(on_decoder_stacked(void*)));
-       connect(signal.get(), SIGNAL(decoder_removed(void*)),
-               this, SLOT(on_decoder_removed(void*)));
-
-       // Add all decoders provided by this signal
-       auto stack = signal->decoder_stack();
-       if (stack.size() > 1) {
-               for (const shared_ptr<Decoder>& dec : stack)
-                       // Only add the decoder if it has binary output
-                       if (dec->get_binary_class_count() > 0) {
-                               QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
-                               decoder_selector_->addItem(title, QVariant::fromValue((void*)dec.get()));
-                       }
-       } else
-               if (!stack.empty()) {
-                       shared_ptr<Decoder>& dec = stack.at(0);
-                       if (dec->get_binary_class_count() > 0)
-                               decoder_selector_->addItem(signal->name(), QVariant::fromValue((void*)dec.get()));
-               }
-}
-
-void View::remove_decode_signal(shared_ptr<data::DecodeSignal> signal)
-{
-       // Remove all decoders provided by this signal
-       for (const shared_ptr<Decoder>& dec : signal->decoder_stack()) {
-               int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
-
-               if (index != -1)
-                       decoder_selector_->removeItem(index);
-       }
-
-       ViewBase::remove_decode_signal(signal);
-
-       if (signal.get() == signal_) {
-               reset_data();
-               update_data();
-               reset_view_state();
-       }
-}
-
-void View::save_settings(QSettings &settings) const
-{
-       (void)settings;
-}
-
-void View::restore_settings(QSettings &settings)
-{
-       // Note: It is assumed that this function is only called once,
-       // immediately after restoring a previous session.
-       (void)settings;
-}
-
-void View::reset_data()
-{
-       signal_ = nullptr;
-       decoder_ = nullptr;
-       bin_class_id_ = 0;
-       binary_data_exists_ = false;
-
-       hex_view_->clear();
-}
-
-void View::update_data()
-{
-       if (!signal_)
-               return;
-
-       const DecodeBinaryClass* bin_class =
-               signal_->get_binary_data_class(current_segment_, decoder_, bin_class_id_);
-
-       hex_view_->set_data(bin_class);
-
-       if (!binary_data_exists_)
-               return;
-
-       if (!save_button_->isEnabled())
-               save_button_->setEnabled(true);
-}
-
-void View::save_data() const
-{
-       assert(decoder_);
-       assert(signal_);
-
-       if (!signal_)
-               return;
-
-       GlobalSettings settings;
-       const QString dir = settings.value("MainWindow/SaveDirectory").toString();
-
-       const QString file_name = QFileDialog::getSaveFileName(
-               parent_, tr("Save Binary Data"), dir, tr("Binary Data Files (*.bin);;All Files (*)"));
-
-       if (file_name.isEmpty())
-               return;
-
-       QFile file(file_name);
-       if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
-               pair<size_t, size_t> selection = hex_view_->get_selection();
-
-               vector<uint8_t> data;
-               data.resize(selection.second - selection.first + 1);
-
-               signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
-                       bin_class_id_, selection.first, selection.second, &data);
-
-               int64_t bytes_written = file.write((const char*)data.data(), data.size());
-
-               if ((bytes_written == -1) || ((uint64_t)bytes_written != data.size())) {
-                       QMessageBox msg(parent_);
-                       msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
-                       msg.setStandardButtons(QMessageBox::Ok);
-                       msg.setIcon(QMessageBox::Warning);
-                       msg.exec();
-                       return;
-               }
-       }
-}
-
-void View::save_data_as_hex_dump(bool with_offset, bool with_ascii) const
-{
-       assert(decoder_);
-       assert(signal_);
-
-       if (!signal_)
-               return;
-
-       GlobalSettings settings;
-       const QString dir = settings.value("MainWindow/SaveDirectory").toString();
-
-       const QString file_name = QFileDialog::getSaveFileName(
-               parent_, tr("Save Binary Data"), dir, tr("Hex Dumps (*.txt);;All Files (*)"));
-
-       if (file_name.isEmpty())
-               return;
-
-       QFile file(file_name);
-       if (file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
-               pair<size_t, size_t> selection = hex_view_->get_selection();
-
-               vector<uint8_t> data;
-               data.resize(selection.second - selection.first + 1);
-
-               signal_->get_merged_binary_data_chunks_by_offset(current_segment_, decoder_,
-                       bin_class_id_, selection.first, selection.second, &data);
-
-               QTextStream out_stream(&file);
-
-               uint64_t offset = selection.first;
-               uint64_t n = hex_view_->get_bytes_per_line();
-               QString s;
-
-               while (offset < selection.second) {
-                       size_t end = std::min((uint64_t)(selection.second), offset + n);
-                       offset = hex_view_->create_hex_line(offset, end, &s, with_offset, with_ascii);
-                       out_stream << s << endl;
-               }
-
-               out_stream << endl;
-
-               if (out_stream.status() != QTextStream::Ok) {
-                       QMessageBox msg(parent_);
-                       msg.setText(tr("Error") + "\n\n" + tr("File %1 could not be written to.").arg(file_name));
-                       msg.setStandardButtons(QMessageBox::Ok);
-                       msg.setIcon(QMessageBox::Warning);
-                       msg.exec();
-                       return;
-               }
-       }
-}
-
-void View::on_selected_decoder_changed(int index)
-{
-       if (signal_)
-               disconnect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)));
-
-       reset_data();
-
-       decoder_ = (Decoder*)decoder_selector_->itemData(index).value<void*>();
-
-       // Find the signal that contains the selected decoder
-       for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
-               for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
-                       if (decoder_ == dec.get())
-                               signal_ = ds.get();
-
-       class_selector_->clear();
-
-       if (signal_) {
-               // Populate binary class selector
-               uint32_t bin_classes = decoder_->get_binary_class_count();
-               for (uint32_t i = 0; i < bin_classes; i++) {
-                       const data::decode::DecodeBinaryClassInfo* class_info = decoder_->get_binary_class(i);
-                       class_selector_->addItem(class_info->description, QVariant::fromValue(i));
-               }
-
-               connect(signal_, SIGNAL(new_binary_data(unsigned int, void*, unsigned int)),
-                       this, SLOT(on_new_binary_data(unsigned int, void*, unsigned int)));
-       }
-
-       update_data();
-}
-
-void View::on_selected_class_changed(int index)
-{
-       bin_class_id_ = class_selector_->itemData(index).value<uint32_t>();
-
-       binary_data_exists_ =
-               signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_);
-
-       update_data();
-}
-
-void View::on_signal_name_changed(const QString &name)
-{
-       (void)name;
-
-       SignalBase* sb = qobject_cast<SignalBase*>(QObject::sender());
-       assert(sb);
-
-       DecodeSignal* signal = dynamic_cast<DecodeSignal*>(sb);
-       assert(signal);
-
-       // Update all decoder entries provided by this signal
-       auto stack = signal->decoder_stack();
-       if (stack.size() > 1) {
-               for (const shared_ptr<Decoder>& dec : stack) {
-                       QString title = QString("%1 (%2)").arg(signal->name(), dec->name());
-                       int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
-
-                       if (index != -1)
-                               decoder_selector_->setItemText(index, title);
-               }
-       } else
-               if (!stack.empty()) {
-                       shared_ptr<Decoder>& dec = stack.at(0);
-                       int index = decoder_selector_->findData(QVariant::fromValue((void*)dec.get()));
-
-                       if (index != -1)
-                               decoder_selector_->setItemText(index, signal->name());
-               }
-}
-
-void View::on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id)
-{
-       if ((segment_id == current_segment_) && (decoder == decoder_) && (bin_class_id == bin_class_id_))
-               if (!delayed_view_updater_.isActive())
-                       delayed_view_updater_.start();
-}
-
-void View::on_decoder_stacked(void* decoder)
-{
-       // TODO This doesn't change existing entries for the same signal - but it should as the naming scheme may change
-
-       Decoder* d = static_cast<Decoder*>(decoder);
-
-       // Only add the decoder if it has binary output
-       if (d->get_binary_class_count() == 0)
-               return;
-
-       // Find the signal that contains the selected decoder
-       DecodeSignal* signal = nullptr;
-
-       for (const shared_ptr<DecodeSignal>& ds : decode_signals_)
-               for (const shared_ptr<Decoder>& dec : ds->decoder_stack())
-                       if (d == dec.get())
-                               signal = ds.get();
-
-       assert(signal);
-
-       // Add the decoder to the list
-       QString title = QString("%1 (%2)").arg(signal->name(), d->name());
-       decoder_selector_->addItem(title, QVariant::fromValue((void*)d));
-}
-
-void View::on_decoder_removed(void* decoder)
-{
-       Decoder* d = static_cast<Decoder*>(decoder);
-
-       // Remove the decoder from the list
-       int index = decoder_selector_->findData(QVariant::fromValue((void*)d));
-
-       if (index != -1)
-               decoder_selector_->removeItem(index);
-}
-
-void View::on_actionSave_triggered(QAction* action)
-{
-       int save_type = SaveTypeBinary;
-       if (action)
-               save_type = action->data().toInt();
-
-       switch (save_type)
-       {
-       case SaveTypeBinary: save_data(); break;
-       case SaveTypeHexDumpPlain: save_data_as_hex_dump(false, false); break;
-       case SaveTypeHexDumpWithOffset: save_data_as_hex_dump(true, false); break;
-       case SaveTypeHexDumpComplete: save_data_as_hex_dump(true, true); break;
-       }
-}
-
-void View::perform_delayed_view_update()
-{
-       if (signal_ && !binary_data_exists_)
-               if (signal_->get_binary_data_chunk_count(current_segment_, decoder_, bin_class_id_))
-                       binary_data_exists_ = true;
-
-       update_data();
-}
-
-
-} // namespace decoder_output
-} // namespace views
-} // namespace pv
diff --git a/pv/views/decoder_output/view.hpp b/pv/views/decoder_output/view.hpp
deleted file mode 100644 (file)
index 16d35e8..0000000
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * This file is part of the PulseView project.
- *
- * Copyright (C) 2019 Soeren Apel <soeren@apelpie.net>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef PULSEVIEW_PV_VIEWS_DECODEROUTPUT_VIEW_HPP
-#define PULSEVIEW_PV_VIEWS_DECODEROUTPUT_VIEW_HPP
-
-#include <QAction>
-#include <QComboBox>
-#include <QStackedWidget>
-#include <QToolButton>
-
-#include <pv/views/viewbase.hpp>
-#include <pv/data/decodesignal.hpp>
-
-#include "QHexView.hpp"
-
-namespace pv {
-
-class Session;
-
-namespace views {
-
-namespace decoder_output {
-
-// When adding an entry here, don't forget to update SaveTypeNames as well
-enum SaveType {
-       SaveTypeBinary,
-       SaveTypeHexDumpPlain,
-       SaveTypeHexDumpWithOffset,
-       SaveTypeHexDumpComplete,
-       SaveTypeCount  // Indicates how many save types there are, must always be last
-};
-
-extern const char* SaveTypeNames[SaveTypeCount];
-
-
-class View : public ViewBase
-{
-       Q_OBJECT
-
-public:
-       explicit View(Session &session, bool is_main_view=false, QMainWindow *parent = nullptr);
-
-       virtual ViewType get_type() const;
-
-       /**
-        * Resets the view to its default state after construction. It does however
-        * not reset the signal bases or any other connections with the session.
-        */
-       virtual void reset_view_state();
-
-       virtual void clear_decode_signals();
-       virtual void add_decode_signal(shared_ptr<data::DecodeSignal> signal);
-       virtual void remove_decode_signal(shared_ptr<data::DecodeSignal> signal);
-
-       virtual void save_settings(QSettings &settings) const;
-       virtual void restore_settings(QSettings &settings);
-
-private:
-       void reset_data();
-       void update_data();
-
-       void save_data() const;
-       void save_data_as_hex_dump(bool with_offset=false, bool with_ascii=false) const;
-
-private Q_SLOTS:
-       void on_selected_decoder_changed(int index);
-       void on_selected_class_changed(int index);
-       void on_signal_name_changed(const QString &name);
-       void on_new_binary_data(unsigned int segment_id, void* decoder, unsigned int bin_class_id);
-
-       void on_decoder_stacked(void* decoder);
-       void on_decoder_removed(void* decoder);
-
-       void on_actionSave_triggered(QAction* action = nullptr);
-
-       virtual void perform_delayed_view_update();
-
-private:
-       QWidget* parent_;
-
-       QComboBox *decoder_selector_, *format_selector_, *class_selector_;
-       QStackedWidget *stacked_widget_;
-       QHexView *hex_view_;
-
-       QToolButton* save_button_;
-       QAction* save_action_;
-
-       data::DecodeSignal *signal_;
-       const data::decode::Decoder *decoder_;
-       uint32_t bin_class_id_;
-       bool binary_data_exists_;
-};
-
-} // namespace decoder_output
-} // namespace views
-} // namespace pv
-
-#endif // PULSEVIEW_PV_VIEWS_DECODEROUTPUT_VIEW_HPP
index 26b4ef1f893ca882abf563b5f026a9aba55766f5..24e4bb9a8816b2dcf945907f297637b4d8e1ea07 100644 (file)
@@ -36,7 +36,7 @@ namespace views {
 const char* ViewTypeNames[ViewTypeCount] = {
        "Trace View",
 #ifdef ENABLE_DECODE
-       "Decoder Output View"
+       "Binary Decoder Output View"
 #endif
 };
 
index 3fff28f0cf09da7230ee9ec41809431857b05cb3..585dfa0c91ffd6e204c4cfe203e2de5846881834 100644 (file)
@@ -55,7 +55,7 @@ namespace views {
 enum ViewType {
        ViewTypeTrace,
 #ifdef ENABLE_DECODE
-       ViewTypeDecoderOutput,
+       ViewTypeDecoderBinary,
 #endif
        ViewTypeCount  // Indicates how many view types there are, must always be last
 };
index dc874d59cc5d88498641c7ee0e655fa2d1b1b268..e3362ac8ef961d1ce7da45568acef3510b8097a3 100644 (file)
@@ -174,8 +174,8 @@ if(ENABLE_DECODE)
                ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/item.cpp
                ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/model.cpp
                ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/subwindow.cpp
-               ${PROJECT_SOURCE_DIR}/pv/views/decoder_output/view.cpp
-               ${PROJECT_SOURCE_DIR}/pv/views/decoder_output/QHexView.cpp
+               ${PROJECT_SOURCE_DIR}/pv/views/decoder_binary/view.cpp
+               ${PROJECT_SOURCE_DIR}/pv/views/decoder_binary/QHexView.cpp
                ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.cpp
                ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.cpp
                ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.cpp
@@ -184,8 +184,8 @@ if(ENABLE_DECODE)
        list(APPEND pulseview_TEST_HEADERS
                ${PROJECT_SOURCE_DIR}/pv/data/decodesignal.hpp
                ${PROJECT_SOURCE_DIR}/pv/subwindows/decoder_selector/subwindow.hpp
-               ${PROJECT_SOURCE_DIR}/pv/views/decoder_output/view.hpp
-               ${PROJECT_SOURCE_DIR}/pv/views/decoder_output/QHexView.hpp
+               ${PROJECT_SOURCE_DIR}/pv/views/decoder_binary/view.hpp
+               ${PROJECT_SOURCE_DIR}/pv/views/decoder_binary/QHexView.hpp
                ${PROJECT_SOURCE_DIR}/pv/views/trace/decodetrace.hpp
                ${PROJECT_SOURCE_DIR}/pv/widgets/decodergroupbox.hpp
                ${PROJECT_SOURCE_DIR}/pv/widgets/decodermenu.hpp