SnapshotViewer window
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -100,6 +100,18 @@ namespace Bloom::Widgets
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->snapshotListScene,
|
||||
&ListScene::itemDoubleClicked,
|
||||
this,
|
||||
[this] (ListItem* item) {
|
||||
auto* snapshotItem = dynamic_cast<MemorySnapshotItem*>(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<MemorySnapshotItem*>(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();
|
||||
|
||||
@@ -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<QString, MemorySnapshot> snapshotsById;
|
||||
QMap<QString, MemorySnapshotItem*> snapshotItemsById;
|
||||
QMap<QString, SnapshotViewer*> 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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
#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<const MemoryRegionItem&>(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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
#include "SnapshotViewer.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QVBoxLayout>
|
||||
#include <algorithm>
|
||||
#include <QSize>
|
||||
#include <QDesktopServices>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#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<QVBoxLayout*>();
|
||||
this->detailsContainer = this->container->findChild<QWidget*>("details-container");
|
||||
|
||||
this->nameInput = this->detailsContainer->findChild<TextInput*>("name-input");
|
||||
this->descriptionInput = this->detailsContainer->findChild<QPlainTextEdit*>("description-input");
|
||||
|
||||
auto* detailsContainerLayout = this->detailsContainer->findChild<QHBoxLayout*>();
|
||||
detailsContainerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
auto* attributesLayout = this->detailsContainer->findChild<QVBoxLayout*>("attributes-layout");
|
||||
attributesLayout->setContentsMargins(10, 10, 10, 0);
|
||||
|
||||
auto* rightPanelLayout = this->detailsContainer->findChild<QVBoxLayout*>("right-panel-layout");
|
||||
rightPanelLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
if (!this->snapshot.excludedRegions.empty() || !this->snapshot.focusedRegions.empty()) {
|
||||
auto* memoryRegionsContainer = this->detailsContainer->findChild<QWidget*>("memory-regions-container");
|
||||
auto* memoryRegionsLayout = memoryRegionsContainer->findChild<QVBoxLayout*>();
|
||||
auto* noMemoryRegionsLabel = memoryRegionsContainer->findChild<Label*>("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<ListItem*>(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<QWidget*>("bottom-bar");
|
||||
this->bottomBarLayout = this->bottomBar->findChild<QHBoxLayout*>();
|
||||
|
||||
auto* memoryCapacityLabel = this->bottomBar->findChild<Label*>("memory-capacity-label");
|
||||
auto* snapshotIdLabel = this->bottomBar->findChild<Label*>("id-label");
|
||||
auto* programCounterLabel = this->bottomBar->findChild<Label*>("program-counter-label");
|
||||
auto* dateLabel = this->bottomBar->findChild<Label*>("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<Targets::TargetMemoryAddress, ByteItem*>& 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<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress,
|
||||
bool confirmationPromptEnabled
|
||||
) {
|
||||
auto sortedByteItemsByAddress = std::map<Targets::TargetMemoryAddress, ByteItem*>();
|
||||
|
||||
// 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")
|
||||
+ ".<br/><br/>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<WriteTargetMemory::Block>();
|
||||
|
||||
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<WriteTargetMemory>(
|
||||
new WriteTargetMemory(this->memoryDescriptor, std::move(writeBlocks)),
|
||||
&QObject::deleteLater
|
||||
);
|
||||
|
||||
this->taskProgressIndicator->addTask(writeMemoryTask);
|
||||
InsightWorker::queueTask(writeMemoryTask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QShowEvent>
|
||||
#include <QResizeEvent>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPlainTextEdit>
|
||||
#include <optional>
|
||||
|
||||
#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<MemoryRegionItem*> memoryRegionItems;
|
||||
|
||||
std::optional<Targets::TargetMemoryBuffer> 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<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress,
|
||||
bool confirmationPromptEnabled
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<widget class="QWidget" name="container">
|
||||
<layout class="QVBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="details-container">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"/>
|
||||
</property>
|
||||
<layout class="QHBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="attributes-layout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="LabeledSeparator" name="details-separator">
|
||||
<property name="title">
|
||||
<string>Details</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="vertical-spacer">
|
||||
<property name="sizeHint">
|
||||
<size>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item alignment="Qt::AlignVCenter">
|
||||
<widget class="TextInput" name="name-input">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"/>
|
||||
</property>
|
||||
<property name="maximumWidth">
|
||||
<number>800</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="vertical-spacer">
|
||||
<property name="sizeHint">
|
||||
<size>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="description-input">
|
||||
<property name="minimumHeight">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="vertical-spacer">
|
||||
<property name="sizeHint">
|
||||
<size>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="right-panel-container">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding"/>
|
||||
</property>
|
||||
<property name="minimumWidth">
|
||||
<number>400</number>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="right-panel-layout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QWidget" name="memory-regions-container">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item alignment="Qt::AlignHCenter">
|
||||
<widget class="Label" name="no-regions-label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"/>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<enum>Qt::AlignCenter</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>No memory regions were captured in this snapshot</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="bottom-bar">
|
||||
<property name="minimumHeight">
|
||||
<number>28</number>
|
||||
</property>
|
||||
<property name="maximumHeight">
|
||||
<number>28</number>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"/>
|
||||
</property>
|
||||
<layout class="QHBoxLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="Label" name="memory-capacity-label">
|
||||
<property name="toolTip">
|
||||
<string>Memory capacity</string>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="separator"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="separator"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Label" name="program-counter-label">
|
||||
<property name="toolTip">
|
||||
<string>Program counter at point of capture</string>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="separator"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Label" name="date-label">
|
||||
<property name="toolTip">
|
||||
<string>Date of capture</string>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QFrame" name="separator"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Label" name="id-label">
|
||||
<property name="toolTip">
|
||||
<string>Snapshot ID</string>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</ui>
|
||||
Reference in New Issue
Block a user