From 75cbb5cf67870ef1dc06c5e5d7aac2d987af62f7 Mon Sep 17 00:00:00 2001 From: Nav Date: Sun, 5 Mar 2023 23:29:03 +0000 Subject: [PATCH] Refactored TargetRegistersPane widget to improve memory footprint --- src/Insight/CMakeLists.txt | 5 +- .../Stylesheets/InsightWindow.qss | 19 - .../TargetRegisterInspectorWindow.cpp | 3 +- .../TargetRegisterInspectorWindow.hpp | 1 - .../TargetRegistersPane/ItemWidget.cpp | 29 -- .../TargetRegistersPane/ItemWidget.hpp | 23 -- .../TargetRegistersPane/RegisterGroupItem.cpp | 166 ++++++++ .../TargetRegistersPane/RegisterGroupItem.hpp | 52 +++ .../RegisterGroupWidget.cpp | 152 ------- .../RegisterGroupWidget.hpp | 50 --- .../TargetRegistersPane/RegisterItem.cpp | 120 ++++++ .../TargetRegistersPane/RegisterItem.hpp | 42 ++ .../TargetRegistersPane/RegisterWidget.cpp | 232 ----------- .../TargetRegistersPane/RegisterWidget.hpp | 76 ---- .../TargetRegistersPaneWidget.cpp | 380 ++++++++++++++---- .../TargetRegistersPaneWidget.hpp | 45 ++- .../UiFiles/TargetRegistersSidePane.ui | 25 -- 17 files changed, 723 insertions(+), 697 deletions(-) delete mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.cpp delete mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.hpp create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.cpp create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.hpp delete mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.cpp delete mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.hpp create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.cpp create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.hpp delete mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.cpp delete mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.hpp diff --git a/src/Insight/CMakeLists.txt b/src/Insight/CMakeLists.txt index ef431021..4db830ab 100755 --- a/src/Insight/CMakeLists.txt +++ b/src/Insight/CMakeLists.txt @@ -58,9 +58,8 @@ target_sources( # Target register side pane ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.cpp # Target register inspector window ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.cpp diff --git a/src/Insight/UserInterfaces/InsightWindow/Stylesheets/InsightWindow.qss b/src/Insight/UserInterfaces/InsightWindow/Stylesheets/InsightWindow.qss index 368276ea..f266040f 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Stylesheets/InsightWindow.qss +++ b/src/Insight/UserInterfaces/InsightWindow/Stylesheets/InsightWindow.qss @@ -202,25 +202,6 @@ QScrollBar::sub-line:vertical { border: none; } -#target-registers-side-pane #register-group-header[selected=true], -#target-registers-side-pane #register-item[selected=true] { - background-color: #355A80; -} - -#target-registers-side-pane #register-item #value { - font-size: 13px; - color: #8a8a8d; - font-style: italic; -} - -#target-registers-side-pane #register-item[selected=false] #value[changed=true] { - color: #547fba; -} - -#target-registers-side-pane #register-item[selected=true] #value[changed=true] { - color: #8a8a8d; -} - /* Bottom menu bar & panels */ #bottom-menu-bar { background-color: transparent; diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.cpp index b0b47853..36a7cf7a 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.cpp @@ -27,12 +27,11 @@ namespace Bloom::Widgets TargetRegisterInspectorWindow::TargetRegisterInspectorWindow( const Targets::TargetRegisterDescriptor& registerDescriptor, TargetState currentTargetState, - const std::optional& registerValue, QWidget* parent ) : QWidget(parent) , registerDescriptor(registerDescriptor) - , registerValue(registerValue.value_or(Targets::TargetMemoryBuffer(registerDescriptor.size, 0))) + , registerValue(Targets::TargetMemoryBuffer(registerDescriptor.size, 0)) { this->setWindowFlag(Qt::Window); auto registerName = QString::fromStdString(this->registerDescriptor.name.value()).toUpper(); diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.hpp index 25a8088e..711755d0 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.hpp @@ -27,7 +27,6 @@ namespace Bloom::Widgets TargetRegisterInspectorWindow( const Targets::TargetRegisterDescriptor& registerDescriptor, Targets::TargetState currentTargetState, - const std::optional& registerValue = std::nullopt, QWidget* parent = nullptr ); diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.cpp deleted file mode 100644 index 923444cb..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include "ItemWidget.hpp" - -#include - -namespace Bloom::Widgets -{ - ItemWidget::ItemWidget(QWidget* parent): ClickableWidget(parent) { - auto onClick = [this] { - this->setSelected(true); - }; - - QObject::connect(this, &ClickableWidget::clicked, this, onClick); - QObject::connect(this, &ClickableWidget::rightClicked, this, onClick); - - this->setSelected(false); - } - - void ItemWidget::setSelected(bool selected) { - this->setProperty("selected", selected); - this->style()->unpolish(this); - this->style()->polish(this); - - if (selected) { - emit this->selected(this); - } - - this->postSetSelected(selected); - } -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.hpp deleted file mode 100644 index 2a6283fc..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/ItemWidget.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include "../ClickableWidget.hpp" - -namespace Bloom::Widgets -{ - class ItemWidget: public ClickableWidget - { - Q_OBJECT - - public: - ItemWidget(QWidget *parent); - - public slots: - void setSelected(bool selected); - - signals: - void selected(ItemWidget*); - - protected: - virtual void postSetSelected(bool selected) {}; - }; -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.cpp new file mode 100644 index 00000000..fd06d7b1 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.cpp @@ -0,0 +1,166 @@ +#include "RegisterGroupItem.hpp" + +#include +#include +#include +#include +#include +#include + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListScene.hpp" +#include "src/Services/PathService.hpp" + +namespace Bloom::Widgets +{ + RegisterGroupItem::RegisterGroupItem( + QString name, + const std::set& registerDescriptors, + std::unordered_map& registerItemsByDescriptor + ) + : groupName(name) + { + for (const auto& registerDescriptor : registerDescriptors) { + auto* registerItem = new RegisterItem(registerDescriptor); + registerItem->setParentItem(this); + registerItem->setVisible(this->isExpanded()); + + this->registerItems.push_back(registerItem); + registerItemsByDescriptor.insert(std::pair(registerDescriptor, registerItem)); + } + + if (!RegisterGroupItem::registerGroupIconPixmap.has_value()) { + this->generatePixmaps(); + } + } + + void RegisterGroupItem::setExpanded(bool expanded) { + if (expanded == this->expanded) { + return; + } + + this->expanded = expanded; + + for (auto& registerItem : this->registerItems) { + registerItem->setVisible(this->expanded); + } + } + + void RegisterGroupItem::refreshGeometry() { + const auto groupWidth = this->size.width(); + + const auto startXPosition = 0; + auto startYPosition = RegisterGroupItem::GROUP_LIST_ITEM_HEIGHT; + + if (this->isExpanded()) { + for (auto& registerItem : this->registerItems) { + if (!registerItem->isVisible() || registerItem->excluded) { + continue; + } + + registerItem->size.setWidth(groupWidth); + registerItem->setPos(startXPosition, startYPosition); + + registerItem->onGeometryChanged(); + + startYPosition += registerItem->size.height(); + } + } + + this->size.setHeight(startYPosition); + } + + void RegisterGroupItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + static constexpr auto selectedBackgroundColor = QColor(0x35, 0x5A, 0x80); + + static constexpr auto itemLeftPadding = 3; + static constexpr auto itemTopPadding = 4; + static constexpr auto iconLeftMargin = 5; + static constexpr auto labelLeftMargin = 6; + + static const auto nameLabelFont = QFont("'Ubuntu', sans-serif", 11); + static constexpr auto nameLabelFontColor = QColor(0xAF, 0xB1, 0xB3); + + if (this->selected) { + painter->setBrush(selectedBackgroundColor); + painter->setPen(Qt::PenStyle::NoPen); + painter->drawRect(0, 0, this->size.width(), RegisterGroupItem::GROUP_LIST_ITEM_HEIGHT); + } + + const auto& collapsedArrowIconPixmap = *(RegisterGroupItem::collapsedArrowIconPixmap); + const auto& expandedArrowIconPixmap = *(RegisterGroupItem::expandedArrowIconPixmap); + const auto& registerGroupIconPixmap = *(RegisterGroupItem::registerGroupIconPixmap); + + const QPixmap& arrowPixmap = this->isExpanded() ? expandedArrowIconPixmap : collapsedArrowIconPixmap; + + painter->drawPixmap(itemLeftPadding, itemTopPadding + 2, arrowPixmap); + painter->drawPixmap( + arrowPixmap.width() + itemLeftPadding + iconLeftMargin, + itemTopPadding + 1, + registerGroupIconPixmap + ); + + painter->setFont(nameLabelFont); + painter->setPen(nameLabelFontColor); + + const auto fontMetrics = painter->fontMetrics(); + + const auto nameLabelSize = fontMetrics.size(Qt::TextSingleLine, this->groupName); + const auto nameLabelRect = QRect( + arrowPixmap.width() + itemLeftPadding + registerGroupIconPixmap.width() + itemLeftPadding + labelLeftMargin, + itemTopPadding, + nameLabelSize.width(), + nameLabelSize.height() + ); + + painter->drawText(nameLabelRect, Qt::AlignLeft, this->groupName); + } + + void RegisterGroupItem::generatePixmaps() const { + auto svgRenderer = QSvgRenderer(); + + { + svgRenderer.load(QString::fromStdString( + Services::PathService::compiledResourcesPath() + + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/Images/register-group.svg" + )); + + auto registerGroupIconPixmap = QPixmap(svgRenderer.defaultSize()); + registerGroupIconPixmap.fill(Qt::GlobalColor::transparent); + + auto painter = QPainter(®isterGroupIconPixmap); + svgRenderer.render(&painter); + + RegisterGroupItem::registerGroupIconPixmap = registerGroupIconPixmap; + } + + { + svgRenderer.load(QString::fromStdString( + Services::PathService::compiledResourcesPath() + + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/Images/arrow.svg" + )); + + const auto iconSize = svgRenderer.defaultSize(); + auto arrowIconPixmap = QPixmap(iconSize); + arrowIconPixmap.fill(Qt::GlobalColor::transparent); + + auto painter = QPainter(&arrowIconPixmap); + svgRenderer.render(&painter); + + RegisterGroupItem::collapsedArrowIconPixmap = arrowIconPixmap; + + painter.translate( + std::ceil(static_cast(iconSize.width() / 2)), + std::ceil(static_cast(iconSize.height() / 2)) + ); + painter.rotate(90); + painter.translate( + -std::ceil(static_cast(iconSize.width() / 2)), + -std::ceil(static_cast(iconSize.height() / 2)) + ); + arrowIconPixmap.fill(Qt::GlobalColor::transparent); + svgRenderer.render(&painter); + + RegisterGroupItem::expandedArrowIconPixmap = arrowIconPixmap; + } + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.hpp new file mode 100644 index 00000000..8d81fc61 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupItem.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListItem.hpp" + +#include "RegisterItem.hpp" +#include "src/Targets/TargetRegister.hpp" + +namespace Bloom::Widgets +{ + class RegisterGroupItem: public ListItem + { + public: + const QString groupName; + std::vector registerItems; + + explicit RegisterGroupItem( + QString name, + const std::set& registerDescriptors, + std::unordered_map& registerItemsByDescriptor + ); + + bool isExpanded() const { + return this->expanded; + } + + void setExpanded(bool expanded); + + void onGeometryChanged() override { + return this->refreshGeometry(); + } + + void refreshGeometry(); + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + private: + static constexpr int GROUP_LIST_ITEM_HEIGHT = 25; + static inline std::optional registerGroupIconPixmap = std::nullopt; + static inline std::optional collapsedArrowIconPixmap = std::nullopt; + static inline std::optional expandedArrowIconPixmap = std::nullopt; + + bool expanded = false; + + void generatePixmaps() const; + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.cpp deleted file mode 100644 index 253a3970..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "RegisterGroupWidget.hpp" - -#include -#include -#include - -#include "RegisterWidget.hpp" - -#include "src/Services/PathService.hpp" - -namespace Bloom::Widgets -{ - using Bloom::Targets::TargetRegisterDescriptor; - - RegisterGroupWidget::RegisterGroupWidget( - QString name, - const std::set& registerDescriptors, - TargetRegistersPaneWidget* parent - ) - : ItemWidget(parent) - , name(std::move(name)) - { - this->setObjectName(this->name); - - this->headerWidget->setObjectName("register-group-header"); - this->headerWidget->setFixedHeight(25); - auto* headerLayout = new QHBoxLayout(this->headerWidget); - headerLayout->setContentsMargins(5, 0, 0, 0); - - this->label->setText(this->name); - - this->arrowIcon->setObjectName("arrow-icon"); - auto static arrowIconPath = QString::fromStdString( - Services::PathService::compiledResourcesPath() - + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/Images/arrow.svg" - ); - this->arrowIcon->setSvgFilePath(arrowIconPath); - this->arrowIcon->setContainerHeight(15); - this->arrowIcon->setContainerWidth(14); - - this->registerGroupIcon->setObjectName("register-group-icon"); - auto static registerIconPath = QString::fromStdString( - Services::PathService::compiledResourcesPath() - + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/Images/register-group.svg" - ); - this->registerGroupIcon->setSvgFilePath(registerIconPath); - this->registerGroupIcon->setContainerHeight(15); - this->registerGroupIcon->setContainerWidth(15); - - headerLayout->addWidget(this->arrowIcon); - headerLayout->addWidget(this->registerGroupIcon); - headerLayout->addWidget(this->label); - - auto* bodyLayout = new QVBoxLayout(this->bodyWidget); - bodyLayout->setContentsMargins(0, 0,0,0); - bodyLayout->setSpacing(0); - - for (const auto& descriptor : registerDescriptors) { - if (!descriptor.name.has_value()) { - continue; - } - - if (!descriptor.readable) { - continue; - } - - auto* registerWidget = new RegisterWidget(descriptor, this->bodyWidget); - bodyLayout->addWidget(registerWidget, 0, Qt::AlignmentFlag::AlignTop); - - QObject::connect( - registerWidget, - &ItemWidget::selected, - parent, - &TargetRegistersPaneWidget::onItemSelectionChange - ); - - this->registerWidgets.insert(registerWidget); - this->registerWidgetsMappedByDescriptor.insert(std::pair(descriptor, registerWidget)); - } - - bodyLayout->addStretch(1); - - this->layout->setContentsMargins(0,0,0,0); - this->layout->setSpacing(0); - this->layout->addWidget(this->headerWidget, 0, Qt::AlignmentFlag::AlignTop); - this->layout->addWidget(this->bodyWidget, 0, Qt::AlignmentFlag::AlignTop); - this->layout->addStretch(1); - - this->collapse(); - - QObject::connect(this->headerWidget, &ClickableWidget::doubleClicked, [this] { - if (this->collapsed) { - this->expand(); - - } else { - this->collapse(); - } - }); - - QObject::connect( - this->headerWidget, - &ItemWidget::selected, - parent, - &TargetRegistersPaneWidget::onItemSelectionChange - ); - } - - void RegisterGroupWidget::collapse() { - this->arrowIcon->setAngle(0); - this->bodyWidget->setVisible(false); - this->collapsed = true; - } - - void RegisterGroupWidget::expand() { - this->arrowIcon->setAngle(90); - this->bodyWidget->setVisible(true); - this->collapsed = false; - } - - void RegisterGroupWidget::setAllRegistersVisible(bool visible) { - for (const auto& registerWidget : this->registerWidgets) { - registerWidget->setVisible(visible); - } - } - - void RegisterGroupWidget::filterRegisters(const QString& keyword) { - int matchingWidgetCount = 0; - for (const auto& registerWidget : this->registerWidgets) { - if (keyword.isEmpty() || (registerWidget->searchKeywords.contains(keyword, Qt::CaseInsensitive))) { - matchingWidgetCount++; - registerWidget->setVisible(true); - - } else { - registerWidget->setVisible(false); - } - } - - if (matchingWidgetCount == 0) { - this->collapse(); - this->setVisible(false); - - } else { - this->setVisible(true); - if (!keyword.isEmpty()) { - this->expand(); - - } else { - this->collapse(); - } - } - } -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.hpp deleted file mode 100644 index 3edc2cfd..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterGroupWidget.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "TargetRegistersPaneWidget.hpp" -#include "ItemWidget.hpp" - -#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ClickableWidget.hpp" -#include "src/Insight/UserInterfaces/InsightWindow/Widgets/SvgWidget.hpp" -#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp" - -namespace Bloom::Widgets -{ - class RegisterWidget; - class RegisterGroupWidget: public ItemWidget - { - Q_OBJECT - - public: - QString name; - bool collapsed = true; - - std::set registerWidgets; - std::map registerWidgetsMappedByDescriptor; - - RegisterGroupWidget( - QString name, - const std::set& registerDescriptors, - TargetRegistersPaneWidget* parent - ); - - void collapse(); - void expand(); - void setAllRegistersVisible(bool visible); - - void filterRegisters(const QString& keyword); - - private: - QVBoxLayout* layout = new QVBoxLayout(this); - ItemWidget* headerWidget = new ItemWidget(this); - SvgWidget* arrowIcon = new SvgWidget(this->headerWidget); - SvgWidget* registerGroupIcon = new SvgWidget(this->headerWidget); - Label* label = new Label(this->headerWidget); - QWidget* bodyWidget = new QWidget(this); - }; -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.cpp new file mode 100644 index 00000000..6ffb5a9b --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.cpp @@ -0,0 +1,120 @@ +#include "RegisterItem.hpp" + +#include +#include +#include +#include +#include +#include + +#include "src/Services/PathService.hpp" + +namespace Bloom::Widgets +{ + RegisterItem::RegisterItem( + const Targets::TargetRegisterDescriptor& registerDescriptor + ) + : registerDescriptor(registerDescriptor) + , registerName(QString::fromStdString(registerDescriptor.name.value()).toUpper()) + , searchKeywords(this->registerName + " " + QString::fromStdString(registerDescriptor.description.value_or(""))) + { + this->size = QSize(0, RegisterItem::HEIGHT); + + if (!RegisterItem::registerIconPixmap.has_value()) { + this->generatePixmaps(); + } + } + + void RegisterItem::setValue(const Targets::TargetMemoryBuffer& value) { + const auto valueByteArray = QByteArray( + reinterpret_cast(value.data()), + static_cast(value.size()) + ); + + auto hexValueByteArray = valueByteArray.toHex(); + this->valueText = ": 0x" + QString(hexValueByteArray).toUpper() + + " | " + QString::number(hexValueByteArray.toUInt(nullptr, 16)); + + if (value.size() == 1 && value[0] >= 32 && value[0] <= 126) { + this->valueText += " | '" + QString(valueByteArray) + "'"; + } + } + + void RegisterItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + if (this->excluded) { + return; + } + + static constexpr auto selectedBackgroundColor = QColor(0x35, 0x5A, 0x80); + + static constexpr auto itemLeftPadding = 41; + static constexpr auto itemTopPadding = 4; + static constexpr auto labelLeftMargin = 5; + + static const auto nameLabelFont = QFont("'Ubuntu', sans-serif", 11); + static constexpr auto nameLabelFontColor = QColor(0xAF, 0xB1, 0xB3); + static const auto valueLabelFont = QFont("'Ubuntu', sans-serif", 10, -1, true); + static constexpr auto valueLabelFontColor = QColor(0x8A, 0x8A, 0x8D); + static constexpr auto highlightedValueLabelFontColor = QColor(0x54, 0x7F, 0xBA); + + if (this->selected) { + painter->setBrush(selectedBackgroundColor); + painter->setPen(Qt::PenStyle::NoPen); + painter->drawRect(QRect(QPoint(0, 0), this->size)); + } + + const auto& registerIconPixmap = *(RegisterItem::registerIconPixmap); + painter->drawPixmap(itemLeftPadding, itemTopPadding + 4, registerIconPixmap); + + painter->setFont(nameLabelFont); + painter->setPen(nameLabelFontColor); + + const auto fontMetrics = painter->fontMetrics(); + + const auto nameLabelSize = fontMetrics.size(Qt::TextSingleLine, this->registerName); + const auto nameLabelRect = QRect( + registerIconPixmap.width() + itemLeftPadding + labelLeftMargin, + itemTopPadding, + nameLabelSize.width(), + nameLabelSize.height() + ); + + painter->drawText(nameLabelRect, Qt::AlignLeft, this->registerName); + + if (!this->valueText.isEmpty()) { + painter->setFont(valueLabelFont); + painter->setPen( + this->valueChanged && !this->selected ? highlightedValueLabelFontColor : valueLabelFontColor + ); + + const auto fontMetrics = painter->fontMetrics(); + + const auto valueLabelSize = fontMetrics.size(Qt::TextSingleLine, this->valueText); + const auto valueLabelRect = QRect( + nameLabelRect.right() + labelLeftMargin, + itemTopPadding + 1, + valueLabelSize.width(), + valueLabelSize.height() + ); + + painter->drawText(valueLabelRect, Qt::AlignLeft, this->valueText); + } + } + + void RegisterItem::generatePixmaps() const { + auto svgRegisterIconRenderer = QSvgRenderer(QString::fromStdString( + Services::PathService::compiledResourcesPath() + + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/Images/register.svg" + )); + + const auto registerIconSize = svgRegisterIconRenderer.defaultSize(); + + auto registerIconPixmap = QPixmap(registerIconSize); + registerIconPixmap.fill(Qt::GlobalColor::transparent); + + auto painter = QPainter(®isterIconPixmap); + svgRegisterIconRenderer.render(&painter); + + RegisterItem::registerIconPixmap = registerIconPixmap; + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.hpp new file mode 100644 index 00000000..710b9393 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterItem.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListItem.hpp" + +#include "src/Targets/TargetRegister.hpp" + +namespace Bloom::Widgets +{ + class RegisterItem: public ListItem + { + public: + const Targets::TargetRegisterDescriptor registerDescriptor; + const QString registerName; + const QString searchKeywords; + bool excluded = false; + bool valueChanged = false; + + explicit RegisterItem( + const Targets::TargetRegisterDescriptor& registerDescriptor + ); + + void setValue(const Targets::TargetMemoryBuffer& value); + + void clearValue() { + this->valueText.clear(); + } + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + private: + static constexpr int HEIGHT = 25; + static inline std::optional registerIconPixmap = std::nullopt; + + QString valueText = ""; + + void generatePixmaps() const; + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.cpp deleted file mode 100644 index b995c6dc..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.cpp +++ /dev/null @@ -1,232 +0,0 @@ -#include "RegisterWidget.hpp" - -#include -#include -#include -#include - -#include "src/Services/PathService.hpp" - -#include "src/Insight/InsightSignals.hpp" -#include "src/Insight/InsightWorker/InsightWorker.hpp" -#include "src/Insight/InsightWorker/Tasks/ReadTargetRegisters.hpp" - -namespace Bloom::Widgets -{ - using namespace Bloom::Exceptions; - - using Bloom::Targets::TargetRegisterDescriptor; - - RegisterWidget::RegisterWidget( - TargetRegisterDescriptor descriptor, - QWidget *parent - ) - : ItemWidget(parent) - , descriptor(std::move(descriptor)) - , searchKeywords(QString::fromStdString( - this->descriptor.name.value_or("") + this->descriptor.description.value_or("") - ).toLower()) - { - this->setObjectName("register-item"); - this->setFixedHeight(25); - - this->nameLabel->setText(QString::fromStdString(this->descriptor.name.value()).toUpper()); - this->valueLabel->setObjectName("value"); - - this->registerIcon->setObjectName("register-icon"); - auto static registerIconPath = QString::fromStdString( - Services::PathService::compiledResourcesPath() - + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/Images/register.svg" - ); - this->registerIcon->setSvgFilePath(registerIconPath); - this->registerIcon->setContainerHeight(15); - this->registerIcon->setContainerWidth(15); - - this->layout->setContentsMargins(47, 0, 0, 0); - this->layout->setSpacing(0); - this->layout->addWidget(this->registerIcon); - this->layout->addSpacing(7); - this->layout->addWidget(this->nameLabel); - this->layout->addSpacing(5); - this->layout->addWidget(this->valueLabel); - this->layout->addStretch(1); - - QObject::connect(this, &ClickableWidget::doubleClicked, this, &RegisterWidget::openInspectionWindow); - QObject::connect(this->openInspectionWindowAction, &QAction::triggered, this, &RegisterWidget::openInspectionWindow); - QObject::connect(this->refreshValueAction, &QAction::triggered, this, &RegisterWidget::refreshValue); - QObject::connect(this->copyValueNameAction, &QAction::triggered, this, &RegisterWidget::copyName); - QObject::connect(this->copyValueHexAction, &QAction::triggered, this, &RegisterWidget::copyValueHex); - QObject::connect(this->copyValueDecimalAction, &QAction::triggered, this, &RegisterWidget::copyValueDecimal); - QObject::connect(this->copyValueBinaryAction, &QAction::triggered, this, &RegisterWidget::copyValueBinary); - - QObject::connect( - InsightSignals::instance(), - &InsightSignals::targetStateUpdated, - this, - &RegisterWidget::onTargetStateChange - ); - } - - void RegisterWidget::setRegisterValue(const Targets::TargetRegister& targetRegister) { - const auto valueChanged = this->currentRegister.has_value() - && this->currentRegister.value().value != targetRegister.value; - this->currentRegister = targetRegister; - - auto valueByteArray = QByteArray( - reinterpret_cast(targetRegister.value.data()), - static_cast(targetRegister.value.size()) - ); - - auto hexValueByteArray = valueByteArray.toHex(); - auto registerValue = ": 0x" + QString(hexValueByteArray).toUpper() - + " | " + QString::number(hexValueByteArray.toUInt(nullptr, 16)); - - if (targetRegister.value.size() == 1 && targetRegister.value[0] >= 32 && targetRegister.value[0] <= 126) { - registerValue += " | '" + QString(valueByteArray) + "'"; - } - - this->valueLabel->setProperty("changed", valueChanged); - this->valueLabel->style()->unpolish(this->valueLabel); - this->valueLabel->style()->polish(this->valueLabel); - - this->valueLabel->setText(registerValue); - - if (this->inspectWindow != nullptr) { - this->inspectWindow->setValue(targetRegister.value); - } - } - - void RegisterWidget::clearInlineValue() { - this->valueLabel->clear(); - } - - void RegisterWidget::contextMenuEvent(QContextMenuEvent* event) { - this->setSelected(true); - - auto* menu = new QMenu(this); - menu->addAction(this->openInspectionWindowAction); - menu->addAction(this->refreshValueAction); - menu->addSeparator(); - - auto* copyMenu = new QMenu("Copy", this); - copyMenu->addAction(this->copyValueNameAction); - copyMenu->addSeparator(); - copyMenu->addAction(this->copyValueDecimalAction); - copyMenu->addAction(this->copyValueHexAction); - copyMenu->addAction(this->copyValueBinaryAction); - - menu->addMenu(copyMenu); - - this->openInspectionWindowAction->setEnabled(TargetRegisterInspectorWindow::registerSupported(this->descriptor)); - - const auto targetStopped = this->targetState == Targets::TargetState::STOPPED; - const auto targetStoppedAndValuePresent = targetStopped && this->currentRegister.has_value(); - this->refreshValueAction->setEnabled(targetStopped); - this->copyValueDecimalAction->setEnabled(targetStoppedAndValuePresent); - this->copyValueHexAction->setEnabled(targetStoppedAndValuePresent); - this->copyValueBinaryAction->setEnabled(targetStoppedAndValuePresent); - - menu->exec(event->globalPos()); - } - - void RegisterWidget::openInspectionWindow() { - if (!TargetRegisterInspectorWindow::registerSupported(this->descriptor)) { - return; - } - - if (this->inspectWindow == nullptr) { - this->inspectWindow = new TargetRegisterInspectorWindow( - this->descriptor, - this->targetState, - this->currentRegister.has_value() ? std::optional(this->currentRegister->value) - : std::nullopt, - this - ); - - } else { - if (this->currentRegister.has_value()) { - this->inspectWindow->setValue(this->currentRegister->value); - } - - if (!this->inspectWindow->isVisible()) { - this->inspectWindow->show(); - - } else { - this->inspectWindow->activateWindow(); - } - } - } - - void RegisterWidget::refreshValue() { - auto* readRegisterTask = new ReadTargetRegisters({this->descriptor}); - - QObject::connect( - readRegisterTask, - &ReadTargetRegisters::targetRegistersRead, - this, - [this] (Targets::TargetRegisters registers) { - for (const auto& targetRegister : registers) { - if (targetRegister.descriptor == this->descriptor) { - this->setRegisterValue(targetRegister); - } - } - } - ); - - InsightWorker::queueTask(readRegisterTask); - } - - void RegisterWidget::copyName() { - if (this->nameLabel != nullptr) { - QApplication::clipboard()->setText(this->nameLabel->text()); - } - } - - void RegisterWidget::copyValueHex() { - if (this->currentRegister.has_value()) { - auto valueByteArray = QByteArray( - reinterpret_cast(this->currentRegister.value().value.data()), - static_cast(this->currentRegister.value().value.size()) - ).toHex(); - QApplication::clipboard()->setText(QString(valueByteArray).toUpper()); - } - } - - void RegisterWidget::copyValueDecimal() { - if (this->currentRegister.has_value()) { - auto valueByteArray = QByteArray( - reinterpret_cast(this->currentRegister.value().value.data()), - static_cast(this->currentRegister.value().value.size()) - ).toHex(); - QApplication::clipboard()->setText(QString::number(valueByteArray.toUInt(nullptr, 16))); - } - } - - void RegisterWidget::copyValueBinary() { - if (this->currentRegister.has_value()) { - const auto registerValueSize = static_cast(this->currentRegister.value().size()); - auto valueByteArray = QByteArray( - reinterpret_cast(this->currentRegister.value().value.data()), - registerValueSize - ).toHex(); - - auto bitString = QString::number(valueByteArray.toUInt(nullptr, 16), 2); - - if (bitString.size() < (registerValueSize * 8)) { - bitString = bitString.rightJustified((registerValueSize * 8), '0'); - } - - QApplication::clipboard()->setText(bitString); - } - } - - void RegisterWidget::postSetSelected(bool selected) { - auto valueLabelStyle = this->valueLabel->style(); - valueLabelStyle->unpolish(this->valueLabel); - valueLabelStyle->polish(this->valueLabel); - } - - void RegisterWidget::onTargetStateChange(Targets::TargetState newState) { - this->targetState = newState; - } -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.hpp deleted file mode 100644 index 8ef34cf5..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/RegisterWidget.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ItemWidget.hpp" - -#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp" -#include "src/Insight/UserInterfaces/InsightWindow/Widgets/SvgWidget.hpp" -#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.hpp" - - -namespace Bloom::Widgets -{ - class RegisterWidget: public ItemWidget - { - Q_OBJECT - - public: - Targets::TargetRegisterDescriptor descriptor; - QString searchKeywords; - std::optional currentRegister; - - RegisterWidget( - Targets::TargetRegisterDescriptor descriptor, - QWidget *parent - ); - - [[nodiscard]] QSize minimumSizeHint() const override { - auto size = QSize(this->parentWidget()->width(), 25); - return size; - } - - void setRegisterValue(const Targets::TargetRegister& targetRegister); - void clearInlineValue(); - - void contextMenuEvent(QContextMenuEvent* event) override; - - public slots: - void openInspectionWindow(); - void refreshValue(); - void copyName(); - void copyValueHex(); - void copyValueDecimal(); - void copyValueBinary(); - - private: - QHBoxLayout* layout = new QHBoxLayout(this); - SvgWidget* registerIcon = new SvgWidget(this); - Label* nameLabel = new Label(this); - Label* valueLabel = new Label(this); - - // Context-menu actions - QAction* openInspectionWindowAction = new QAction("Inspect", this); - QAction* refreshValueAction = new QAction("Refresh Value", this); - QAction* copyValueNameAction = new QAction("Copy Register Name", this); - QAction* copyValueHexAction = new QAction("Copy Hexadecimal Value", this); - QAction* copyValueDecimalAction = new QAction("Copy Decimal Value", this); - QAction* copyValueBinaryAction = new QAction("Copy Binary Value", this); - - TargetRegisterInspectorWindow* inspectWindow = nullptr; - - void postSetSelected(bool selected) override; - - Targets::TargetState targetState = Targets::TargetState::UNKNOWN; - - private slots: - void onTargetStateChange(Targets::TargetState newState); - }; -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.cpp index 222a0556..f5e6e0f2 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.cpp @@ -1,13 +1,14 @@ #include "TargetRegistersPaneWidget.hpp" #include +#include +#include +#include #include #include "src/Insight/UserInterfaces/InsightWindow/UiLoader.hpp" #include "src/Insight/InsightSignals.hpp" #include "src/Insight/InsightWorker/InsightWorker.hpp" -#include "RegisterGroupWidget.hpp" -#include "RegisterWidget.hpp" #include "src/Services/PathService.hpp" #include "src/Exceptions/Exception.hpp" @@ -47,6 +48,7 @@ namespace Bloom::Widgets auto uiLoader = UiLoader(this); this->container = uiLoader.load(&targetRegistersPaneUiFile, this); this->container->setFixedSize(parent->width(), parent->maximumHeight()); + auto* containerLayout = this->container->findChild(); auto* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -70,48 +72,126 @@ namespace Bloom::Widgets this->filterRegisters(this->searchInput->text()); }); - this->itemScrollArea = this->container->findChild("item-scroll-area"); + const auto& registerDescriptors = targetDescriptor.registerDescriptorsByType; - this->itemContainer = this->container->findChild("item-container"); - auto itemLayout = this->itemContainer->findChild(); + auto registerDescriptorsByGroupName = std::map>({ + { + "CPU General Purpose", + std::set( + registerDescriptors.at(TargetRegisterType::GENERAL_PURPOSE_REGISTER).begin(), + registerDescriptors.at(TargetRegisterType::GENERAL_PURPOSE_REGISTER).end() + ) + } + }); - auto& registerDescriptors = targetDescriptor.registerDescriptorsByType; - this->renderedDescriptors = std::set( - registerDescriptors.at(TargetRegisterType::GENERAL_PURPOSE_REGISTER).begin(), - registerDescriptors.at(TargetRegisterType::GENERAL_PURPOSE_REGISTER).end() - ); - - auto* generalPurposeRegisterGroupWidget = new RegisterGroupWidget( - "CPU General Purpose", - this->renderedDescriptors, - this - ); - - itemLayout->addWidget(generalPurposeRegisterGroupWidget, 0, Qt::AlignTop); - this->registerGroupWidgets.insert(generalPurposeRegisterGroupWidget); - - auto registerDescriptorsByGroupName = std::map>(); for (const auto& registerDescriptor : registerDescriptors.at(TargetRegisterType::OTHER)) { - registerDescriptorsByGroupName[registerDescriptor.groupName.value_or("other")].insert(registerDescriptor); + const auto groupName = QString::fromStdString(registerDescriptor.groupName.value_or("other")).toUpper(); + registerDescriptorsByGroupName[groupName].insert(registerDescriptor); } for (const auto& registerDescriptor : registerDescriptors.at(TargetRegisterType::PORT_REGISTER)) { - registerDescriptorsByGroupName[registerDescriptor.groupName.value_or("other")].insert(registerDescriptor); + const auto groupName = QString::fromStdString(registerDescriptor.groupName.value_or("other")).toUpper(); + registerDescriptorsByGroupName[groupName].insert(registerDescriptor); } for (const auto& [groupName, registerDescriptors] : registerDescriptorsByGroupName) { - auto* registerGroupWidget = new RegisterGroupWidget( - QString::fromStdString(groupName).toUpper(), + this->registerGroupItems.emplace_back(new RegisterGroupItem( + groupName, registerDescriptors, - this - ); + this->registerItemsByDescriptor + )); - itemLayout->addWidget(registerGroupWidget, 0, Qt::AlignTop); - this->registerGroupWidgets.insert(registerGroupWidget); - this->renderedDescriptors.insert(registerDescriptors.begin(), registerDescriptors.end()); + this->registerDescriptors.insert(registerDescriptors.begin(), registerDescriptors.end()); } - itemLayout->addStretch(1); + this->registerListView = new ListView( + std::vector(this->registerGroupItems.begin(), this->registerGroupItems.end()), + this + ); + + this->registerListScene = this->registerListView->listScene(); + + containerLayout->addWidget(this->registerListView); + + QObject::connect( + this->registerListScene, + &ListScene::itemDoubleClicked, + this, + &TargetRegistersPaneWidget::onItemDoubleClicked + ); + + QObject::connect( + this->registerListScene, + &ListScene::itemContextMenu, + this, + &TargetRegistersPaneWidget::onItemContextMenu + ); + + QObject::connect( + this->openInspectionWindowAction, + &QAction::triggered, + this, + [this] { + if (this->contextMenuRegisterItem != nullptr) { + this->openInspectionWindow(this->contextMenuRegisterItem->registerDescriptor); + } + } + ); + + QObject::connect( + this->refreshValueAction, + &QAction::triggered, + this, + [this] { + if (this->contextMenuRegisterItem != nullptr) { + this->refreshRegisterValues(this->contextMenuRegisterItem->registerDescriptor, std::nullopt); + } + } + ); + + QObject::connect( + this->copyNameAction, + &QAction::triggered, + this, + [this] { + if (this->contextMenuRegisterItem != nullptr) { + this->copyRegisterName(this->contextMenuRegisterItem->registerDescriptor); + } + } + ); + + QObject::connect( + this->copyValueHexAction, + &QAction::triggered, + this, + [this] { + if (this->contextMenuRegisterItem != nullptr) { + this->copyRegisterValueHex(this->contextMenuRegisterItem->registerDescriptor); + } + } + ); + + QObject::connect( + this->copyValueDecimalAction, + &QAction::triggered, + this, + [this] { + if (this->contextMenuRegisterItem != nullptr) { + this->copyRegisterValueDecimal(this->contextMenuRegisterItem->registerDescriptor); + } + } + ); + + QObject::connect( + this->copyValueBinaryAction, + &QAction::triggered, + this, + [this] { + if (this->contextMenuRegisterItem != nullptr) { + this->copyRegisterValueBinary(this->contextMenuRegisterItem->registerDescriptor); + } + } + ); auto* insightSignals = InsightSignals::instance(); @@ -142,45 +222,56 @@ namespace Bloom::Widgets } void TargetRegistersPaneWidget::filterRegisters(const QString& keyword) { - for (const auto& registerGroupWidget : this->registerGroupWidgets) { - // If the group name matches the keyword, then don't bother iterating through all the register widgets - if (keyword.isEmpty() || registerGroupWidget->name.contains(keyword, Qt::CaseInsensitive)) { - registerGroupWidget->setVisible(true); - registerGroupWidget->setAllRegistersVisible(true); + for (const auto& groupItem : this->registerGroupItems) { + auto visibleItems = std::uint32_t{0}; + auto displayEntireGroup = keyword.isEmpty() || groupItem->groupName.contains(keyword, Qt::CaseInsensitive); - if (!keyword.isEmpty()) { - registerGroupWidget->expand(); + for (auto& registerItem : groupItem->registerItems) { + registerItem->excluded = !displayEntireGroup + && !registerItem->searchKeywords.contains(keyword, Qt::CaseInsensitive); - } else { - registerGroupWidget->collapse(); + if (!registerItem->excluded) { + ++visibleItems; } - - } else { - registerGroupWidget->filterRegisters(keyword); } + + groupItem->setVisible(visibleItems > 0 || keyword.isEmpty()); + groupItem->setExpanded(visibleItems > 0 && !keyword.isEmpty()); } + + this->registerListScene->refreshGeometry(); } void TargetRegistersPaneWidget::collapseAllRegisterGroups() { - for (const auto& registerGroupWidget : this->registerGroupWidgets) { - registerGroupWidget->collapse(); + for (auto& registerGroupItem : this->registerGroupItems) { + registerGroupItem->setExpanded(false); } + + this->registerListScene->refreshGeometry(); } void TargetRegistersPaneWidget::expandAllRegisterGroups() { - for (const auto& registerGroupWidget : this->registerGroupWidgets) { - registerGroupWidget->expand(); + for (auto& registerGroupItem : this->registerGroupItems) { + registerGroupItem->setExpanded(true); } + + this->registerListScene->refreshGeometry(); } - void TargetRegistersPaneWidget::refreshRegisterValues(std::optional> callback) { - auto& descriptors = this->renderedDescriptors; - - if (descriptors.empty()) { + void TargetRegistersPaneWidget::refreshRegisterValues( + std::optional registerDescriptor, + std::optional> callback + ) { + if (!registerDescriptor.has_value() && this->registerDescriptors.empty()) { return; } - auto* readRegisterTask = new ReadTargetRegisters(descriptors); + auto* readRegisterTask = new ReadTargetRegisters( + registerDescriptor.has_value() + ? Targets::TargetRegisterDescriptors({*registerDescriptor}) + : this->registerDescriptors + ); + QObject::connect( readRegisterTask, &ReadTargetRegisters::targetRegistersRead, @@ -200,17 +291,6 @@ namespace Bloom::Widgets InsightWorker::queueTask(readRegisterTask); } - void TargetRegistersPaneWidget::onItemSelectionChange(ItemWidget* newlySelectedWidget) { - // Only one item in the target registers pane can be selected at any given time. - if (this->selectedItemWidget != newlySelectedWidget) { - if (this->selectedItemWidget != nullptr) { - this->selectedItemWidget->setSelected(false); - } - - this->selectedItemWidget = newlySelectedWidget; - } - } - void TargetRegistersPaneWidget::resizeEvent(QResizeEvent* event) { const auto parentSize = this->parentPanel->size(); const auto width = parentSize.width() - 1; @@ -221,11 +301,65 @@ namespace Bloom::Widgets * In order to avoid the panel resize handle overlapping the scroll bar handle, we reduce the size of * the scroll area. */ - this->itemScrollArea->setFixedWidth(width - this->parentPanel->getHandleSize()); + this->registerListView->setFixedWidth(width - this->parentPanel->getHandleSize()); PaneWidget::resizeEvent(event); } + void TargetRegistersPaneWidget::onItemDoubleClicked(ListItem* clickedItem) { + auto* registerGroupItem = dynamic_cast(clickedItem); + + if (registerGroupItem != nullptr) { + registerGroupItem->setExpanded(!registerGroupItem->isExpanded()); + this->registerListScene->refreshGeometry(); + } + + auto* registerItem = dynamic_cast(clickedItem); + + if (registerItem != nullptr) { + this->openInspectionWindow(registerItem->registerDescriptor); + } + } + + void TargetRegistersPaneWidget::onItemContextMenu(ListItem* item, QPoint sourcePosition) { + auto* registerItem = dynamic_cast(item); + + if (registerItem == nullptr) { + return; + } + + this->contextMenuRegisterItem = registerItem; + + auto* menu = new QMenu(this); + menu->addAction(this->openInspectionWindowAction); + menu->addAction(this->refreshValueAction); + menu->addSeparator(); + + auto* copyMenu = new QMenu("Copy", this); + copyMenu->addAction(this->copyNameAction); + copyMenu->addSeparator(); + copyMenu->addAction(this->copyValueDecimalAction); + copyMenu->addAction(this->copyValueHexAction); + copyMenu->addAction(this->copyValueBinaryAction); + + menu->addMenu(copyMenu); + + this->openInspectionWindowAction->setEnabled( + TargetRegisterInspectorWindow::registerSupported(this->contextMenuRegisterItem->registerDescriptor) + ); + + const auto targetStopped = this->targetState == Targets::TargetState::STOPPED; + const auto targetStoppedAndValuePresent = targetStopped + && this->currentRegisterValues.contains(this->contextMenuRegisterItem->registerDescriptor); + + this->refreshValueAction->setEnabled(targetStopped); + this->copyValueDecimalAction->setEnabled(targetStoppedAndValuePresent); + this->copyValueHexAction->setEnabled(targetStoppedAndValuePresent); + this->copyValueBinaryAction->setEnabled(targetStoppedAndValuePresent); + + menu->exec(sourcePosition); + } + void TargetRegistersPaneWidget::onTargetStateChanged(Targets::TargetState newState) { if (this->targetState == newState) { return; @@ -241,23 +375,121 @@ namespace Bloom::Widgets void TargetRegistersPaneWidget::onRegistersRead(const Targets::TargetRegisters& registers) { for (const auto& targetRegister : registers) { const auto& descriptor = targetRegister.descriptor; + const auto& previousValueIt = this->currentRegisterValues.find(descriptor); + const auto& registerItemIt = this->registerItemsByDescriptor.find(descriptor); - for (const auto& registerGroupWidget : this->registerGroupWidgets) { - const auto registerWidgetit = registerGroupWidget->registerWidgetsMappedByDescriptor.find(descriptor); + if (registerItemIt != this->registerItemsByDescriptor.end()) { + auto& registerItem = registerItemIt->second; - if (registerWidgetit != registerGroupWidget->registerWidgetsMappedByDescriptor.end()) { - registerWidgetit->second->setRegisterValue(targetRegister); - break; - } + registerItem->setValue(targetRegister.value); + registerItem->valueChanged = previousValueIt != this->currentRegisterValues.end() + ? previousValueIt->second != targetRegister.value + : false; } + + this->currentRegisterValues[descriptor] = targetRegister.value; } + + this->registerListScene->update(); } void TargetRegistersPaneWidget::clearInlineRegisterValues() { - for (const auto& registerGroupWidget : this->registerGroupWidgets) { - for (auto* registerWidget : registerGroupWidget->registerWidgets) { - registerWidget->clearInlineValue(); - } + for (auto& [registerDescriptor, registerItem] : this->registerItemsByDescriptor) { + registerItem->clearValue(); } + + this->registerListScene->update(); + } + + void TargetRegistersPaneWidget::openInspectionWindow(const TargetRegisterDescriptor& registerDescriptor) { + if (!TargetRegisterInspectorWindow::registerSupported(registerDescriptor)) { + return; + } + + TargetRegisterInspectorWindow* inspectionWindow = nullptr; + + const auto& currentValueIt = this->currentRegisterValues.find(registerDescriptor); + const auto& inspectionWindowIt = this->inspectionWindowsByDescriptor.find(registerDescriptor); + + if (inspectionWindowIt != this->inspectionWindowsByDescriptor.end()) { + inspectionWindow = inspectionWindowIt->second; + + } else { + inspectionWindow = new TargetRegisterInspectorWindow( + registerDescriptor, + this->targetState, + this + ); + + this->inspectionWindowsByDescriptor.insert(std::pair( + registerDescriptor, + inspectionWindow + )); + } + + if (currentValueIt != this->currentRegisterValues.end()) { + inspectionWindow->setValue(currentValueIt->second); + } + + inspectionWindow->show(); + inspectionWindow->activateWindow(); + } + + void TargetRegistersPaneWidget::copyRegisterName(const TargetRegisterDescriptor& registerDescriptor) { + QApplication::clipboard()->setText(QString::fromStdString(registerDescriptor.name.value_or("")).toUpper()); + } + + void TargetRegistersPaneWidget::copyRegisterValueHex(const TargetRegisterDescriptor& registerDescriptor) { + const auto& valueIt = this->currentRegisterValues.find(registerDescriptor); + + if (valueIt == this->currentRegisterValues.end()) { + return; + } + + const auto& value = valueIt->second; + const auto valueByteArray = QByteArray( + reinterpret_cast(value.data()), + static_cast(value.size()) + ).toHex(); + + QApplication::clipboard()->setText(QString(valueByteArray).toUpper()); + } + + void TargetRegistersPaneWidget::copyRegisterValueDecimal(const TargetRegisterDescriptor& registerDescriptor) { + const auto& valueIt = this->currentRegisterValues.find(registerDescriptor); + + if (valueIt == this->currentRegisterValues.end()) { + return; + } + + const auto& value = valueIt->second; + const auto valueByteArray = QByteArray( + reinterpret_cast(value.data()), + static_cast(value.size()) + ).toHex(); + + QApplication::clipboard()->setText(QString::number(valueByteArray.toUInt(nullptr, 16))); + } + + void TargetRegistersPaneWidget::copyRegisterValueBinary(const TargetRegisterDescriptor& registerDescriptor) { + const auto& valueIt = this->currentRegisterValues.find(registerDescriptor); + + if (valueIt == this->currentRegisterValues.end()) { + return; + } + + const auto& value = valueIt->second; + const auto valueByteArray = QByteArray( + reinterpret_cast(value.data()), + static_cast(value.size()) + ).toHex(); + + auto bitString = QString::number(valueByteArray.toUInt(nullptr, 16), 2); + + if (bitString.size() < (value.size() * 8)) { + bitString = bitString.rightJustified(static_cast(value.size() * 8), '0'); + } + + QApplication::clipboard()->setText(bitString); } } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.hpp index 9848f3be..881b5fdc 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/TargetRegistersPaneWidget.hpp @@ -4,22 +4,26 @@ #include #include #include +#include #include #include #include +#include #include #include "src/Insight/UserInterfaces/InsightWindow/Widgets/PaneWidget.hpp" #include "src/Insight/UserInterfaces/InsightWindow/Widgets/PanelWidget.hpp" -#include "ItemWidget.hpp" +#include "RegisterItem.hpp" +#include "RegisterGroupItem.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegisterInspector/TargetRegisterInspectorWindow.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListView.hpp" #include "src/Insight/UserInterfaces/InsightWindow/Widgets/SvgToolButton.hpp" #include "src/Targets/TargetState.hpp" #include "src/Targets/TargetDescriptor.hpp" namespace Bloom::Widgets { - class RegisterGroupWidget; class TargetRegistersPaneWidget: public PaneWidget { Q_OBJECT @@ -35,9 +39,10 @@ namespace Bloom::Widgets void collapseAllRegisterGroups(); void expandAllRegisterGroups(); - void refreshRegisterValues(std::optional> callback = std::nullopt); - - void onItemSelectionChange(Bloom::Widgets::ItemWidget* newlySelectedWidget); + void refreshRegisterValues( + std::optional registerDescriptor = std::nullopt, + std::optional> callback = std::nullopt + ); protected: void resizeEvent(QResizeEvent* event) override; @@ -52,18 +57,36 @@ namespace Bloom::Widgets SvgToolButton* expandAllButton = nullptr; QLineEdit* searchInput = nullptr; - QScrollArea* itemScrollArea = nullptr; - QWidget* itemContainer = nullptr; + ListView* registerListView = nullptr; + ListScene* registerListScene = nullptr; - ItemWidget* selectedItemWidget = nullptr; - - std::set registerGroupWidgets; - Targets::TargetRegisterDescriptors renderedDescriptors; + Targets::TargetRegisterDescriptors registerDescriptors; + std::vector registerGroupItems; + std::unordered_map registerItemsByDescriptor; + std::unordered_map inspectionWindowsByDescriptor; + std::unordered_map currentRegisterValues; Targets::TargetState targetState = Targets::TargetState::UNKNOWN; + // Context-menu actions + QAction* openInspectionWindowAction = new QAction("Inspect", this); + QAction* refreshValueAction = new QAction("Refresh Value", this); + QAction* copyNameAction = new QAction("Copy Register Name", this); + QAction* copyValueHexAction = new QAction("Copy Hexadecimal Value", this); + QAction* copyValueDecimalAction = new QAction("Copy Decimal Value", this); + QAction* copyValueBinaryAction = new QAction("Copy Binary Value", this); + + RegisterItem* contextMenuRegisterItem = nullptr; + + void onItemDoubleClicked(ListItem* clickedItem); + void onItemContextMenu(ListItem* item, QPoint sourcePosition); void onTargetStateChanged(Targets::TargetState newState); void onRegistersRead(const Targets::TargetRegisters& registers); void clearInlineRegisterValues(); + void openInspectionWindow(const Targets::TargetRegisterDescriptor& registerDescriptor); + void copyRegisterName(const Targets::TargetRegisterDescriptor& registerDescriptor); + void copyRegisterValueHex(const Targets::TargetRegisterDescriptor& registerDescriptor); + void copyRegisterValueDecimal(const Targets::TargetRegisterDescriptor& registerDescriptor); + void copyRegisterValueBinary(const Targets::TargetRegisterDescriptor& registerDescriptor); }; } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/UiFiles/TargetRegistersSidePane.ui b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/UiFiles/TargetRegistersSidePane.ui index 27349796..16356a19 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/UiFiles/TargetRegistersSidePane.ui +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetRegistersPane/UiFiles/TargetRegistersSidePane.ui @@ -116,31 +116,6 @@ - - - true - Qt::ScrollBarAsNeeded - QAbstractScrollArea::AdjustToContents - Qt::ScrollBarAlwaysOff - - - - - - - - 0 - - - 0 - - - QLayout::SetMinAndMaxSize - - - - -