From 761fef0cae117e69da1150b5b0b4cd48d5ed187a Mon Sep 17 00:00:00 2001 From: Nav Date: Wed, 12 Apr 2023 22:52:28 +0100 Subject: [PATCH] SnapshotViewer window --- src/Insight/CMakeLists.txt | 3 + .../SnapshotManager/SnapshotManager.cpp | 47 +++ .../SnapshotManager/SnapshotManager.hpp | 6 + .../SnapshotViewer/MemoryRegionItem.cpp | 100 ++++++ .../SnapshotViewer/MemoryRegionItem.hpp | 34 +++ .../SnapshotViewer/SnapshotViewer.cpp | 284 ++++++++++++++++++ .../SnapshotViewer/SnapshotViewer.hpp | 72 +++++ .../Stylesheets/SnapshotViewer.qss | 49 +++ .../SnapshotViewer/UiFiles/SnapshotViewer.ui | 227 ++++++++++++++ 9 files changed, 822 insertions(+) create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.cpp create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.hpp create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.cpp create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.hpp create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/Stylesheets/SnapshotViewer.qss create mode 100644 src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/UiFiles/SnapshotViewer.ui diff --git a/src/Insight/CMakeLists.txt b/src/Insight/CMakeLists.txt index f42b8184..0dbf7685 100755 --- a/src/Insight/CMakeLists.txt +++ b/src/Insight/CMakeLists.txt @@ -106,6 +106,7 @@ target_sources( ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/MemorySnapshotItem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/CreateSnapshotWindow.cpp ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.cpp # Memory region manager window ${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegionManager/MemoryRegionManagerWindow.cpp @@ -188,6 +189,8 @@ qt_add_resources( # Memory snapshots "./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/UiFiles/SnapshotManager.ui" "./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/UiFiles/CreateSnapshotWindow.ui" + "./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/UiFiles/SnapshotViewer.ui" + "./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/Stylesheets/SnapshotViewer.qss" # Memory region manager window "./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegionManager/UiFiles/MemoryRegionManagerWindow.ui" diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.cpp index 888a5ed2..59cbbc7b 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.cpp @@ -100,6 +100,18 @@ namespace Bloom::Widgets } ); + QObject::connect( + this->snapshotListScene, + &ListScene::itemDoubleClicked, + this, + [this] (ListItem* item) { + auto* snapshotItem = dynamic_cast(item); + + if (snapshotItem != nullptr) { + this->onSnapshotItemDoubleClick(snapshotItem); + } + } + ); QObject::connect( this->snapshotListScene, @@ -108,6 +120,17 @@ namespace Bloom::Widgets &SnapshotManager::onSnapshotItemContextMenu ); + QObject::connect( + this->openSnapshotViewerAction, + &QAction::triggered, + this, + [this] { + if (this->contextMenuSnapshotItem != nullptr) { + this->openSnapshotViewer(this->contextMenuSnapshotItem->memorySnapshot.id); + } + } + ); + QObject::connect( this->restoreSnapshotAction, &QAction::triggered, @@ -223,6 +246,25 @@ namespace Bloom::Widgets this->selectedItem = item; } + void SnapshotManager::openSnapshotViewer(const QString& snapshotId) { + auto snapshotViewerIt = this->snapshotViewersById.find(snapshotId); + + if (snapshotViewerIt == this->snapshotViewersById.end()) { + const auto& snapshotIt = this->snapshotsById.find(snapshotId); + + assert(snapshotIt != this->snapshotsById.end()); + + snapshotViewerIt = this->snapshotViewersById.insert( + snapshotId, + new SnapshotViewer(snapshotIt.value(), this->memoryDescriptor, this) + ); + } + + auto* snapshotViewer = snapshotViewerIt.value(); + snapshotViewer->show(); + snapshotViewer->activateWindow(); + } + void SnapshotManager::restoreSnapshot(const QString& snapshotId, bool confirmationPromptEnabled) { const auto& snapshotIt = this->snapshotsById.find(snapshotId); assert(snapshotIt != this->snapshotsById.end()); @@ -316,6 +358,10 @@ namespace Bloom::Widgets InsightWorker::queueTask(writeMemoryTask); } + void SnapshotManager::onSnapshotItemDoubleClick(MemorySnapshotItem* item) { + this->openSnapshotViewer(item->memorySnapshot.id); + } + void SnapshotManager::onSnapshotItemContextMenu(ListItem *item, QPoint sourcePosition) { auto* snapshotItem = dynamic_cast(item); @@ -326,6 +372,7 @@ namespace Bloom::Widgets this->contextMenuSnapshotItem = snapshotItem; auto* menu = new QMenu(this); + menu->addAction(this->openSnapshotViewerAction); menu->addAction(this->deleteSnapshotAction); menu->addSeparator(); diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.hpp index 8d0bdb1f..6e0e21d8 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.hpp @@ -22,6 +22,7 @@ #include "./CreateSnapshotWindow/CreateSnapshotWindow.hpp" #include "MemorySnapshotItem.hpp" +#include "SnapshotViewer/SnapshotViewer.hpp" namespace Bloom::Widgets { @@ -63,6 +64,7 @@ namespace Bloom::Widgets QMap snapshotsById; QMap snapshotItemsById; + QMap snapshotViewersById; QWidget* container = nullptr; QWidget* toolBar = nullptr; @@ -75,6 +77,7 @@ namespace Bloom::Widgets MemorySnapshotItem* contextMenuSnapshotItem = nullptr; + QAction* openSnapshotViewerAction = new QAction("Open", this); QAction* deleteSnapshotAction = new QAction("Delete", this); QAction* restoreSnapshotAction = new QAction("Restore", this); @@ -84,9 +87,12 @@ namespace Bloom::Widgets bool captureFocusedRegions, bool captureDirectlyFromTarget ); + void addSnapshot(MemorySnapshot&& snapshotTmp); void onSnapshotItemSelected(MemorySnapshotItem* item); + void openSnapshotViewer(const QString& snapshotId); void restoreSnapshot(const QString& snapshotId, bool confirmationPromptEnabled); + void onSnapshotItemDoubleClick(MemorySnapshotItem* item); void onSnapshotItemContextMenu(ListItem* item, QPoint sourcePosition); void onTargetStateChanged(Targets::TargetState newState); }; diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.cpp new file mode 100644 index 00000000..a9d0a9fd --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.cpp @@ -0,0 +1,100 @@ +#include "MemoryRegionItem.hpp" + +#include "src/Services/DateTimeService.hpp" + +namespace Bloom::Widgets +{ + MemoryRegionItem::MemoryRegionItem(const MemoryRegion& memoryRegion) + : memoryRegion(memoryRegion) + { + this->size = QSize(0, MemoryRegionItem::HEIGHT); + + this->nameText = memoryRegion.name; + this->addressRangeText = "0x" + QString::number(this->memoryRegion.addressRange.startAddress, 16).toUpper() + + QString(" -> ") + "0x" + QString::number(this->memoryRegion.addressRange.endAddress, 16).toUpper(); + this->regionTypeText = this->memoryRegion.type == MemoryRegionType::EXCLUDED ? "Excluded" : "Focused"; + this->createdDateText = memoryRegion.createdDate.toString( + memoryRegion.createdDate.date() == Services::DateTimeService::currentDate() + ? "hh:mm" + : "dd/MM/yyyy hh:mm" + ); + + this->setToolTip(this->nameText); + } + + void MemoryRegionItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { + static constexpr auto margins = QMargins(10, 5, 10, 0); + + painter->setOpacity(0.7); + static auto font = QFont("'Ubuntu', sans-serif"); + font.setPixelSize(14); + static auto secondaryFont = QFont("'Ubuntu', sans-serif"); + secondaryFont.setPixelSize(13); + + static constexpr auto fontColor = QColor(0xAF, 0xB1, 0xB3); + static constexpr auto secondaryFontColor = QColor(0x8A, 0x8A, 0x8D); + + painter->setFont(font); + painter->setPen(fontColor); + + const auto fontMetrics = painter->fontMetrics(); + + const auto addressRangeTextSize = fontMetrics.size(Qt::TextSingleLine, this->addressRangeText); + const auto regionTypeTextSize = fontMetrics.size(Qt::TextSingleLine, this->regionTypeText); + const auto createdDateTextSize = fontMetrics.size(Qt::TextSingleLine, this->createdDateText); + + constexpr auto nameTextRightMargin = 10; + const auto availableNameTextWidth = this->size.width() - margins.left() - margins.right() + - regionTypeTextSize.width() - nameTextRightMargin; + + const auto nameText = fontMetrics.elidedText( + this->nameText, + Qt::TextElideMode::ElideRight, + availableNameTextWidth + ); + + const auto nameTextSize = fontMetrics.size(Qt::TextSingleLine, nameText); + const auto nameTextRect = QRect( + margins.left(), + margins.top(), + nameTextSize.width(), + nameTextSize.height() + ); + + painter->drawText(nameTextRect, Qt::AlignLeft, nameText); + + painter->setFont(secondaryFont); + painter->setPen(secondaryFontColor); + + const auto addressRangeTextRect = QRect( + margins.left(), + nameTextRect.bottom() + 5, + addressRangeTextSize.width(), + addressRangeTextSize.height() + ); + + painter->drawText(addressRangeTextRect, Qt::AlignLeft, this->addressRangeText); + + const auto regionTypeTextRect = QRect( + this->size.width() - margins.right() - regionTypeTextSize.width(), + margins.top(), + regionTypeTextSize.width(), + regionTypeTextSize.height() + ); + + painter->drawText(regionTypeTextRect, Qt::AlignRight, this->regionTypeText); + + const auto createdDateTextRect = QRect( + this->size.width() - margins.right() - createdDateTextSize.width(), + nameTextRect.bottom() + 5, + createdDateTextSize.width(), + createdDateTextSize.height() + ); + + painter->drawText(createdDateTextRect, Qt::AlignRight, this->createdDateText); + + static constexpr auto borderColor = QColor(0x41, 0x42, 0x3F); + painter->setPen(borderColor); + painter->drawLine(0, this->size.height() - 1, this->size.width(), this->size.height() - 1); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.hpp new file mode 100644 index 00000000..8c1d8353 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/MemoryRegionItem.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListItem.hpp" + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegion.hpp" +#include "src/Targets/TargetMemory.hpp" + +namespace Bloom::Widgets +{ + class MemoryRegionItem: public ListItem + { + public: + const MemoryRegion& memoryRegion; + + MemoryRegionItem(const MemoryRegion& memoryRegion); + + bool operator < (const ListItem& rhs) const override { + const auto& rhsRegionItem = dynamic_cast(rhs); + return this->memoryRegion.createdDate < rhsRegionItem.memoryRegion.createdDate; + } + + void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; + + private: + static constexpr int HEIGHT = 50; + + QString nameText; + QString addressRangeText; + QString regionTypeText; + QString createdDateText; + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.cpp new file mode 100644 index 00000000..d3c59337 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.cpp @@ -0,0 +1,284 @@ +#include "SnapshotViewer.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "src/Insight/InsightWorker/Tasks/WriteTargetMemory.hpp" +#include "src/Insight/InsightWorker/InsightWorker.hpp" + +#include "src/Insight/UserInterfaces/InsightWindow/UiLoader.hpp" + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ConfirmationDialog.hpp" + +#include "src/Services/PathService.hpp" +#include "src/Exceptions/Exception.hpp" + +#include "src/Insight/InsightSignals.hpp" +#include "src/Logger/Logger.hpp" + +namespace Bloom::Widgets +{ + using Bloom::Exceptions::Exception; + + SnapshotViewer::SnapshotViewer( + MemorySnapshot& snapshot, + const Targets::TargetMemoryDescriptor& memoryDescriptor, + QWidget* parent + ) + : QWidget(parent) + , snapshot(snapshot) + , memoryDescriptor(memoryDescriptor) + , hexViewerData(snapshot.data) + { + this->setWindowFlag(Qt::Window); + this->setObjectName("snapshot-viewer"); + this->setWindowTitle(this->snapshot.name + " (" + this->snapshot.id + ")"); + + auto windowUiFile = QFile( + QString::fromStdString(Services::PathService::compiledResourcesPath() + + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane" + + "/SnapshotManager/SnapshotViewer/UiFiles/SnapshotViewer.ui" + ) + ); + + auto stylesheetFile = QFile( + QString::fromStdString(Services::PathService::compiledResourcesPath() + + "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane" + + "/SnapshotManager/SnapshotViewer/Stylesheets/SnapshotViewer.qss" + ) + ); + + if (!windowUiFile.open(QFile::ReadOnly)) { + throw Exception("Failed to open SnapshotViewer UI file"); + } + + if (!stylesheetFile.open(QFile::ReadOnly)) { + throw Exception("Failed to open SnapshotViewer stylesheet file"); + } + + // Set ideal window size + this->setFixedSize(1200, 850); + this->setMinimumSize(700, 600); + this->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + + auto uiLoader = UiLoader(this); + this->setStyleSheet(stylesheetFile.readAll()); + this->container = uiLoader.load(&windowUiFile, this); + + auto* containerLayout = this->container->findChild(); + this->detailsContainer = this->container->findChild("details-container"); + + this->nameInput = this->detailsContainer->findChild("name-input"); + this->descriptionInput = this->detailsContainer->findChild("description-input"); + + auto* detailsContainerLayout = this->detailsContainer->findChild(); + detailsContainerLayout->setContentsMargins(0, 0, 0, 0); + + auto* attributesLayout = this->detailsContainer->findChild("attributes-layout"); + attributesLayout->setContentsMargins(10, 10, 10, 0); + + auto* rightPanelLayout = this->detailsContainer->findChild("right-panel-layout"); + rightPanelLayout->setContentsMargins(0, 0, 0, 0); + + if (!this->snapshot.excludedRegions.empty() || !this->snapshot.focusedRegions.empty()) { + auto* memoryRegionsContainer = this->detailsContainer->findChild("memory-regions-container"); + auto* memoryRegionsLayout = memoryRegionsContainer->findChild(); + auto* noMemoryRegionsLabel = memoryRegionsContainer->findChild("no-regions-label"); + + std::transform( + this->snapshot.focusedRegions.begin(), + this->snapshot.focusedRegions.end(), + std::back_inserter(this->memoryRegionItems), + [] (const MemoryRegion& focusedRegion) { + return new MemoryRegionItem(focusedRegion); + } + ); + + std::transform( + this->snapshot.excludedRegions.begin(), + this->snapshot.excludedRegions.end(), + std::back_inserter(this->memoryRegionItems), + [] (const MemoryRegion& excludedRegion) { + return new MemoryRegionItem(excludedRegion); + } + ); + + this->memoryRegionListView = new ListView( + std::vector(this->memoryRegionItems.begin(), this->memoryRegionItems.end()), + this + ); + this->memoryRegionListView->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + + this->memoryRegionListScene = this->memoryRegionListView->listScene(); + this->memoryRegionListScene->margins = QMargins(0, 5, 0, 5); + this->memoryRegionListScene->setSelectionLimit(2); + + noMemoryRegionsLabel->hide(); + memoryRegionsLayout->insertWidget(0, this->memoryRegionListView); + } + + this->restoreBytesAction = new ContextMenuAction("Restore Selected", std::nullopt, this); + + this->hexViewerWidget = new HexViewerWidget( + this->memoryDescriptor, + this->hexViewerData, + this->hexViewerWidgetSettings, + this->snapshot.focusedRegions, + this->snapshot.excludedRegions, + this + ); + + containerLayout->insertWidget(1, this->hexViewerWidget); + + this->bottomBar = this->container->findChild("bottom-bar"); + this->bottomBarLayout = this->bottomBar->findChild(); + + auto* memoryCapacityLabel = this->bottomBar->findChild("memory-capacity-label"); + auto* snapshotIdLabel = this->bottomBar->findChild("id-label"); + auto* programCounterLabel = this->bottomBar->findChild("program-counter-label"); + auto* dateLabel = this->bottomBar->findChild("date-label"); + + memoryCapacityLabel->setText(QLocale(QLocale::English).toString(this->memoryDescriptor.size()) + " Bytes"); + snapshotIdLabel->setText(this->snapshot.id); + programCounterLabel->setText( + "0x" + QString::number(this->snapshot.programCounter, 16).rightJustified(8, '0').toUpper() + ); + dateLabel->setText(this->snapshot.createdDate.toString("dd/MM/yyyy hh:mm")); + + this->nameInput->setText(this->snapshot.name); + this->descriptionInput->setPlainText(this->snapshot.description); + + this->taskProgressIndicator = new TaskProgressIndicator(this); + this->bottomBarLayout->insertWidget(2, this->taskProgressIndicator); + + auto* insightSignals = InsightSignals::instance(); + + QObject::connect( + this->restoreBytesAction, + &ContextMenuAction::invoked, + this, + [this] (const std::unordered_map& selectedByteItemsByAddress) { + this->restoreSelectedBytes(selectedByteItemsByAddress, true); + } + ); + + QObject::connect(this->hexViewerWidget, &HexViewerWidget::ready, this, &SnapshotViewer::onHexViewerReady); + + this->hexViewerWidget->init(); + this->move(this->parentWidget()->window()->geometry().center() - this->rect().center()); + } + + void SnapshotViewer::showEvent(QShowEvent* event) { + QWidget::showEvent(event); + } + + void SnapshotViewer::resizeEvent(QResizeEvent* event) { + this->container->setFixedSize(this->size()); + + QWidget::resizeEvent(event); + } + + void SnapshotViewer::onHexViewerReady() { + this->hexViewerWidget->addExternalContextMenuAction(this->restoreBytesAction); + } + + void SnapshotViewer::restoreSelectedBytes( + const std::unordered_map& selectedByteItemsByAddress, + bool confirmationPromptEnabled + ) { + auto sortedByteItemsByAddress = std::map(); + + // Ideally, we'd use std::transform here, but that would require an additional pass to remove excluded bytes + for (const auto& pair : selectedByteItemsByAddress) { + if (pair.second->excluded) { + continue; + } + + sortedByteItemsByAddress.insert(pair); + } + + if (sortedByteItemsByAddress.empty()) { + // The user has only selected bytes that are within an excluded region - nothing to do here + return; + } + + if (confirmationPromptEnabled) { + auto* confirmationDialog = new ConfirmationDialog( + "Restore selected bytes", + "This operation will write " + QString::number(sortedByteItemsByAddress.size()) + + " byte(s) to the target's " + + QString(this->memoryDescriptor.type == Targets::TargetMemoryType::EEPROM ? "EEPROM" : "RAM") + + ".

Do you wish to proceed?", + "Proceed", + std::nullopt, + this + ); + + QObject::connect( + confirmationDialog, + &ConfirmationDialog::confirmed, + this, + [this, selectedByteItemsByAddress] { + this->restoreSelectedBytes(selectedByteItemsByAddress, false); + } + ); + + confirmationDialog->show(); + return; + } + + auto writeBlocks = std::vector(); + + Targets::TargetMemoryAddress blockStartAddress = sortedByteItemsByAddress.begin()->first; + Targets::TargetMemoryAddress blockEndAddress = blockStartAddress; + + for (const auto& [address, byteItem] : sortedByteItemsByAddress) { + if (address > (blockEndAddress + 1)) { + // Commit the block + const auto dataBeginOffset = blockStartAddress - this->memoryDescriptor.addressRange.startAddress; + const auto dataEndOffset = blockEndAddress - this->memoryDescriptor.addressRange.startAddress + 1; + + writeBlocks.emplace_back( + blockStartAddress, + Targets::TargetMemoryBuffer( + this->snapshot.data.begin() + dataBeginOffset, + this->snapshot.data.begin() + dataEndOffset + ) + ); + + blockStartAddress = address; + blockEndAddress = address; + continue; + } + + blockEndAddress = address; + } + + { + const auto dataBeginOffset = blockStartAddress - this->memoryDescriptor.addressRange.startAddress; + const auto dataEndOffset = blockEndAddress - this->memoryDescriptor.addressRange.startAddress + 1; + + writeBlocks.emplace_back( + blockStartAddress, + Targets::TargetMemoryBuffer( + this->snapshot.data.begin() + dataBeginOffset, + this->snapshot.data.begin() + dataEndOffset + ) + ); + } + + const auto writeMemoryTask = QSharedPointer( + new WriteTargetMemory(this->memoryDescriptor, std::move(writeBlocks)), + &QObject::deleteLater + ); + + this->taskProgressIndicator->addTask(writeMemoryTask); + InsightWorker::queueTask(writeMemoryTask); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.hpp new file mode 100644 index 00000000..67b16f7b --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TextInput.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/PushButton.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ListView/ListView.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TaskProgressIndicator/TaskProgressIndicator.hpp" + +#include "src/Targets/TargetMemory.hpp" + +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemorySnapshot.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.hpp" +#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ContextMenuAction.hpp" +#include "MemoryRegionItem.hpp" + +namespace Bloom::Widgets +{ + class SnapshotViewer: public QWidget + { + Q_OBJECT + + public: + SnapshotViewer( + MemorySnapshot& snapshot, + const Targets::TargetMemoryDescriptor& memoryDescriptor, + QWidget* parent = nullptr + ); + + protected: + void showEvent(QShowEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + + private: + MemorySnapshot& snapshot; + const Targets::TargetMemoryDescriptor& memoryDescriptor; + + QWidget* container = nullptr; + + QWidget* detailsContainer = nullptr; + TextInput* nameInput = nullptr; + QPlainTextEdit* descriptionInput = nullptr; + + ListView* memoryRegionListView = nullptr; + ListScene* memoryRegionListScene = nullptr; + std::vector memoryRegionItems; + + std::optional hexViewerData; + HexViewerWidget* hexViewerWidget = nullptr; + HexViewerWidgetSettings hexViewerWidgetSettings = HexViewerWidgetSettings(); + + ContextMenuAction* restoreBytesAction = nullptr; + + QWidget* bottomBar = nullptr; + QHBoxLayout* bottomBarLayout = nullptr; + + TaskProgressIndicator* taskProgressIndicator = nullptr; + + PushButton* closeButton = nullptr; + + void onHexViewerReady(); + void restoreSelectedBytes( + const std::unordered_map& selectedByteItemsByAddress, + bool confirmationPromptEnabled + ); + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/Stylesheets/SnapshotViewer.qss b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/Stylesheets/SnapshotViewer.qss new file mode 100644 index 00000000..cab87fe0 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/Stylesheets/SnapshotViewer.qss @@ -0,0 +1,49 @@ +#snapshot-viewer, +#snapshot-viewer #container { + background-color: #373835; +} + +#snapshot-viewer #top-bar { + border-bottom: 1px solid #41423f; +} + +#snapshot-viewer #bottom-bar { + border-top: 1px solid #41423f; +} + +#snapshot-viewer #details-container { + border-bottom: 1px solid #41423f; +} + +#snapshot-viewer #right-panel-container { + border-left: 1px solid #41423f; +} + +#snapshot-viewer #memory-regions-container #no-regions-label { + color: #838386; +} + +#snapshot-viewer #bottom-bar #separator { + background-color: transparent; + border-right: 1px solid #41423f; + padding: 0; + min-width: 1px; + max-width: 1px; +} + +#snapshot-viewer #memory-capacity-label, +#snapshot-viewer #id-label, +#snapshot-viewer #program-counter-label, +#snapshot-viewer #date-label { + color: #8a8a8d; +} + +#snapshot-viewer #program-counter-label, +#snapshot-viewer #date-label { + padding: 1px 3px; +} + +#snapshot-viewer #memory-capacity-label, +#snapshot-viewer #id-label { + padding: 1px 5px; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/UiFiles/SnapshotViewer.ui b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/UiFiles/SnapshotViewer.ui new file mode 100644 index 00000000..5335a149 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/UiFiles/SnapshotViewer.ui @@ -0,0 +1,227 @@ + + + + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + + + 0 + + + 0 + + + + + Details + + + + + + + + 10 + + + + QSizePolicy::Fixed + + + + + + + false + + + + + + 800 + + + + + + + + 10 + + + + QSizePolicy::Fixed + + + + + + + 100 + + + false + + + + + + + + 10 + + + + QSizePolicy::Fixed + + + + + + + + + + + + 400 + + + + 0 + + + 0 + + + + + + + + + 0 + + + 0 + + + + + + + + true + + + Qt::AlignCenter + + + No memory regions were captured in this snapshot + + + + + + + + + + + + + + + + 28 + + + 28 + + + + + + + 3 + + + 0 + + + + + Memory capacity + + + + + + + + + + + + + + + + Program counter at point of capture + + + + + + + + + + + + + Date of capture + + + + + + + + + + + + + Snapshot ID + + + + + + + + + + + + +