From: Soeren Apel Date: Sat, 29 Feb 2020 21:50:21 +0000 (+0100) Subject: Rename decoder output view to binary decoder output view X-Git-Url: https://sigrok.org/gitaction?a=commitdiff_plain;h=121307b3c50d981638cbe1e33ba5410bb2b11dd1;p=pulseview.git Rename decoder output view to binary decoder output view --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 5330663d..31164a9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/pv/mainwindow.cpp b/pv/mainwindow.cpp index 6662180c..ea9f01af 100644 --- a/pv/mainwindow.cpp +++ b/pv/mainwindow.cpp @@ -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 @@ -162,8 +162,8 @@ shared_ptr MainWindow::add_view(views::ViewType type, // This view will be the main view if there's no main bar yet v = make_shared(session, (main_bar ? false : true), dock_main); #ifdef ENABLE_DECODE - if (type == views::ViewTypeDecoderOutput) - v = make_shared(session, false, dock_main); + if (type == views::ViewTypeDecoderBinary) + v = make_shared(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 index 00000000..ccd50f87 --- /dev/null +++ b/pv/views/decoder_binary/QHexView.cpp @@ -0,0 +1,689 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2015 Victor Anjin + * Copyright (C) 2019 Soeren Apel + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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 index 00000000..c39dcb20 --- /dev/null +++ b/pv/views/decoder_binary/QHexView.hpp @@ -0,0 +1,101 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2015 Victor Anjin + * Copyright (C) 2019 Soeren Apel + * + * 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 + +#include + +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 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 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 index 00000000..5f55b191 --- /dev/null +++ b/pv/views/decoder_binary/view.cpp @@ -0,0 +1,480 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2019 Soeren Apel + * + * 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 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 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& 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& 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 signal) +{ + // Remove all decoders provided by this signal + for (const shared_ptr& 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 selection = hex_view_->get_selection(); + + vector 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 selection = hex_view_->get_selection(); + + vector 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(); + + // Find the signal that contains the selected decoder + for (const shared_ptr& ds : decode_signals_) + for (const shared_ptr& 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(); + + 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(QObject::sender()); + assert(sb); + + DecodeSignal* signal = dynamic_cast(sb); + assert(signal); + + // Update all decoder entries provided by this signal + auto stack = signal->decoder_stack(); + if (stack.size() > 1) { + for (const shared_ptr& 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& 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); + + // 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& ds : decode_signals_) + for (const shared_ptr& 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); + + // 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 index 00000000..c1d70b1b --- /dev/null +++ b/pv/views/decoder_binary/view.hpp @@ -0,0 +1,115 @@ +/* + * This file is part of the PulseView project. + * + * Copyright (C) 2019 Soeren Apel + * + * 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 . + */ + +#ifndef PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP +#define PULSEVIEW_PV_VIEWS_DECODERBINARY_VIEW_HPP + +#include +#include +#include +#include + +#include +#include + +#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 signal); + virtual void remove_decode_signal(shared_ptr 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 index ccd50f87..00000000 --- a/pv/views/decoder_output/QHexView.cpp +++ /dev/null @@ -1,689 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2015 Victor Anjin - * Copyright (C) 2019 Soeren Apel - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#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 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 index e3c182a7..00000000 --- a/pv/views/decoder_output/QHexView.hpp +++ /dev/null @@ -1,101 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2015 Victor Anjin - * Copyright (C) 2019 Soeren Apel - * - * 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 - -#include - -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 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 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 index 0f127c87..00000000 --- a/pv/views/decoder_output/view.cpp +++ /dev/null @@ -1,480 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2019 Soeren Apel - * - * 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 . - */ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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 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& 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& 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 signal) -{ - // Remove all decoders provided by this signal - for (const shared_ptr& 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 selection = hex_view_->get_selection(); - - vector 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 selection = hex_view_->get_selection(); - - vector 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(); - - // Find the signal that contains the selected decoder - for (const shared_ptr& ds : decode_signals_) - for (const shared_ptr& 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(); - - 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(QObject::sender()); - assert(sb); - - DecodeSignal* signal = dynamic_cast(sb); - assert(signal); - - // Update all decoder entries provided by this signal - auto stack = signal->decoder_stack(); - if (stack.size() > 1) { - for (const shared_ptr& 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& 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); - - // 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& ds : decode_signals_) - for (const shared_ptr& 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); - - // 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 index 16d35e82..00000000 --- a/pv/views/decoder_output/view.hpp +++ /dev/null @@ -1,115 +0,0 @@ -/* - * This file is part of the PulseView project. - * - * Copyright (C) 2019 Soeren Apel - * - * 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 . - */ - -#ifndef PULSEVIEW_PV_VIEWS_DECODEROUTPUT_VIEW_HPP -#define PULSEVIEW_PV_VIEWS_DECODEROUTPUT_VIEW_HPP - -#include -#include -#include -#include - -#include -#include - -#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 signal); - virtual void remove_decode_signal(shared_ptr 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 diff --git a/pv/views/viewbase.cpp b/pv/views/viewbase.cpp index 26b4ef1f..24e4bb9a 100644 --- a/pv/views/viewbase.cpp +++ b/pv/views/viewbase.cpp @@ -36,7 +36,7 @@ namespace views { const char* ViewTypeNames[ViewTypeCount] = { "Trace View", #ifdef ENABLE_DECODE - "Decoder Output View" + "Binary Decoder Output View" #endif }; diff --git a/pv/views/viewbase.hpp b/pv/views/viewbase.hpp index 3fff28f0..585dfa0c 100644 --- a/pv/views/viewbase.hpp +++ b/pv/views/viewbase.hpp @@ -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 }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dc874d59..e3362ac8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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