Memory snapshot management via memory inspection pane

This commit is contained in:
Nav
2022-12-24 02:54:20 +00:00
parent 1091966f72
commit 21c2658c43
18 changed files with 1321 additions and 14 deletions

View File

@@ -2,6 +2,7 @@
#include <mutex>
#include <QDateTime>
#include <QDate>
namespace Bloom
{
@@ -23,6 +24,16 @@ namespace Bloom
return QDateTime::currentDateTime();
}
/**
* This function calls QDateTime::currentDateTime(). See comment for DateTime::currentDateTime().
*
* @return
*/
static QDate currentDate() {
const auto lock = std::unique_lock(DateTime::systemClockMutex);
return QDateTime::currentDateTime().date();
}
/**
* The QDateTime::timeZoneAbbreviation() is a non-static member function but it may still interface with the
* system clock. This can result in race conditions when called simultaneously to QDateTime::currentDateTime(),

View File

@@ -82,6 +82,9 @@ target_sources(
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/ExcludedMemoryRegion.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemorySnapshot.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/MemorySnapshotItem.cpp
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/CreateSnapshotWindow.cpp
# Memory region manager window
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegionManager/MemoryRegionManagerWindow.cpp
@@ -105,6 +108,8 @@ qt_add_resources(
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/UiFiles/TargetMemoryInspectionPane.ui"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Stylesheets/TargetMemoryInspectionPane.qss"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/UiFiles/HexViewerWidget.ui"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/UiFiles/SnapshotManager.ui"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/UiFiles/CreateSnapshotWindow.ui"
"./UserInterfaces/InsightWindow/Images/bloom-icon.svg"
"./UserInterfaces/InsightWindow/Images/RAM.svg"
"./UserInterfaces/InsightWindow/Images/refresh.svg"
@@ -126,6 +131,7 @@ qt_add_resources(
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/memory-inspection-icon-disabled.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/manage-focused-regions.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/stale-data-icon.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/manage-snapshots-icon.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/Images/highlight-stack-memory.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/Images/highlight-stack-memory-disabled.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/Images/highlight-focused-memory.svg"
@@ -136,6 +142,7 @@ qt_add_resources(
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/Images/display-annotations-disabled.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/Images/ascii-view.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/Images/ascii-view-disabled.svg"
"./UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/Images/new-snapshot-icon.svg"
# Target package widgets
"./UserInterfaces/InsightWindow/Widgets/TargetWidgets/DIP/Stylesheets/DualInlinePackage.qss"

View File

@@ -201,6 +201,22 @@ Bloom--Widgets--SvgToolButton QMenu::item {
}
Bloom--Widgets--PushButton[styleName="primary"] {
background-color: #353C41;
border: 1px solid #454C52;
background-color: rgba(53, 60, 65, 1);
border: 1px solid rgba(69, 76, 82, 1);
}
Bloom--Widgets--PushButton[styleName="primary"]:disabled {
background-color: rgba(53, 60, 65, 0.25);
border: 1px solid rgba(69, 76, 82, 0.5);
}
QCheckBox::indicator:checked {
margin-top: 2px;
image: url(":/compiled/src/Insight/UserInterfaces/InsightWindow/Images/checked-action-icon.svg");
}
QCheckBox::indicator:unchecked {
margin-top: 2px;
image: url(":/compiled/src/Insight/UserInterfaces/InsightWindow/Images/unchecked-action-icon.svg");
}

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="15"
height="15"
viewBox="0 0 3.9687501 3.96875"
version="1.1"
id="svg974"
sodipodi:docname="manage-snapshots-icon.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs968" />
<sodipodi:namedview
id="base"
pagecolor="#343532"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="39.687885"
inkscape:cx="8.6802307"
inkscape:cy="7.5337852"
inkscape:document-units="px"
inkscape:current-layer="layer2"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-page="true"
inkscape:window-width="3440"
inkscape:window-height="1353"
inkscape:window-x="2560"
inkscape:window-y="34"
inkscape:window-maximized="1"
inkscape:snap-smooth-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:pagecheckerboard="0" />
<metadata
id="metadata971">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Main"
style="display:inline">
<path
id="rect6137"
style="fill:#838382;fill-opacity:1;stroke:none;stroke-width:1.327;stroke-miterlimit:4;stroke-dasharray:none"
d="m 1,1 v 13 h 3 v -4 h 7 v 4 h 3 V 1 Z m 2,2 h 9 V 8 H 3 Z"
transform="scale(0.26458334)" />
<rect
style="fill:#838382;fill-opacity:1;stroke:none;stroke-width:0.568665;stroke-miterlimit:4;stroke-dasharray:none"
id="rect22557"
width="1.3229166"
height="0.5291667"
x="1.3229166"
y="2.9104166" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,140 @@
#include "CreateSnapshotWindow.hpp"
#include <QFile>
#include <QSize>
#include <QDesktopServices>
#include "src/Insight/UserInterfaces/InsightWindow/UiLoader.hpp"
#include "src/Insight/InsightSignals.hpp"
#include "src/Helpers/Paths.hpp"
#include "src/Exceptions/Exception.hpp"
namespace Bloom::Widgets
{
using Bloom::Exceptions::Exception;
CreateSnapshotWindow::CreateSnapshotWindow(
Targets::TargetMemoryType memoryType,
const std::optional<Targets::TargetMemoryBuffer>& data,
const bool& staleData,
QWidget* parent
)
: QWidget(parent)
, data(data)
, staleData(staleData)
{
this->setWindowFlag(Qt::Window);
this->setObjectName("create-snapshot-window");
this->setWindowTitle(
"New Snapshot - " + QString(memoryType == Targets::TargetMemoryType::EEPROM ? "EEPROM" : "RAM")
);
auto windowUiFile = QFile(
QString::fromStdString(Paths::compiledResourcesPath()
+ "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane"
+ "/SnapshotManager/CreateSnapshotWindow/UiFiles/CreateSnapshotWindow.ui"
)
);
if (!windowUiFile.open(QFile::ReadOnly)) {
throw Exception("Failed to open CreateSnapshotWindow UI file");
}
this->setFixedSize(QSize(500, 300));
auto uiLoader = UiLoader(this);
this->container = uiLoader.load(&windowUiFile, this);
this->container->setFixedSize(this->size());
this->container->setContentsMargins(15, 15, 15, 15);
auto* formContainer = this->container->findChild<QWidget*>("form-container");
this->nameInput = formContainer->findChild<TextInput*>("name-input");
this->descriptionInput = formContainer->findChild<QPlainTextEdit*>("description-input");
this->includeFocusedRegionsInput = formContainer->findChild<QCheckBox*>("include-focus-regions-input");
this->captureDirectlyFromTargetInput = formContainer->findChild<QCheckBox*>("capture-directly-from-target-input");
this->staleDataWarning = formContainer->findChild<QWidget*>("stale-data-warning");
this->captureButton = this->container->findChild<PushButton*>("capture-btn");
this->closeButton = this->container->findChild<PushButton*>("close-btn");
QObject::connect(this->nameInput, &QLineEdit::textEdited, this, &CreateSnapshotWindow::refreshForm);
QObject::connect(
this->captureDirectlyFromTargetInput,
&QCheckBox::stateChanged,
this,
&CreateSnapshotWindow::refreshForm
);
QObject::connect(this->captureButton, &QPushButton::clicked, this, &CreateSnapshotWindow::issueCaptureRequest);
QObject::connect(this->closeButton, &QPushButton::clicked, this, &QWidget::close);
auto* insightSignals = InsightSignals::instance();
QObject::connect(
insightSignals,
&InsightSignals::targetStateUpdated,
this,
[this] (Targets::TargetState newState) {
this->targetState = newState;
this->refreshForm();
}
);
}
void CreateSnapshotWindow::refreshForm() {
this->captureButton->setEnabled(this->captureEnabled());
this->staleDataWarning->setVisible(this->staleData && !this->captureDirectlyFromTargetInput->isChecked());
}
void CreateSnapshotWindow::showEvent(QShowEvent* event) {
this->move(this->parentWidget()->window()->geometry().center() - this->rect().center());
this->resetForm();
this->refreshForm();
QWidget::showEvent(event);
}
bool CreateSnapshotWindow::captureEnabled() {
if (this->targetState != Targets::TargetState::STOPPED) {
return false;
}
if (!this->data.has_value()) {
return false;
}
if (this->nameInput->text().isEmpty()) {
return false;
}
return true;
}
void CreateSnapshotWindow::resetForm() {
this->nameInput->setText("Untitled Snapshot");
this->descriptionInput->setPlainText("");
this->includeFocusedRegionsInput->setChecked(true);
this->captureDirectlyFromTargetInput->setChecked(false);
}
void CreateSnapshotWindow::issueCaptureRequest() {
if (!this->captureEnabled()) {
// Sanity check
this->refreshForm();
return;
}
emit this->snapshotCaptureRequested(
this->nameInput->text(),
this->descriptionInput->toPlainText(),
this->includeFocusedRegionsInput->isChecked(),
this->captureDirectlyFromTargetInput->isChecked()
);
this->close();
}
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include <QWidget>
#include <QShowEvent>
#include <QPlainTextEdit>
#include <QCheckBox>
#include <optional>
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TextInput.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/PushButton.hpp"
#include "src/Targets/TargetMemory.hpp"
#include "src/Targets/TargetState.hpp"
namespace Bloom::Widgets
{
class CreateSnapshotWindow: public QWidget
{
Q_OBJECT
public:
explicit CreateSnapshotWindow(
Targets::TargetMemoryType memoryType,
const std::optional<Targets::TargetMemoryBuffer>& data,
const bool& staleData,
QWidget* parent = nullptr
);
void refreshForm();
signals:
void snapshotCaptureRequested(
const QString& name,
const QString& description,
bool captureFocusedRegions,
bool refreshBeforeCapture
);
protected:
void showEvent(QShowEvent* event) override;
private:
QWidget* container = nullptr;
TextInput* nameInput = nullptr;
QPlainTextEdit* descriptionInput = nullptr;
QCheckBox* includeFocusedRegionsInput = nullptr;
QCheckBox* captureDirectlyFromTargetInput = nullptr;
QWidget* staleDataWarning = nullptr;
PushButton* captureButton = nullptr;
PushButton* closeButton = nullptr;
const std::optional<Targets::TargetMemoryBuffer>& data;
const bool& staleData;
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
bool captureEnabled();
void resetForm();
void issueCaptureRequest();
};
}

View File

@@ -0,0 +1,218 @@
<?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="form-container">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"/>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>10</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="name-row">
<item alignment="Qt::AlignVCenter">
<widget class="Label" name="name-label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"/>
</property>
<property name="minimumWidth">
<number>100</number>
</property>
<property name="text">
<string>Name:</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignVCenter">
<widget class="TextInput" name="name-input">
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="description-row">
<item alignment="Qt::AlignTop">
<widget class="Label" name="register-details-description-label">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed"/>
</property>
<property name="minimumWidth">
<number>100</number>
</property>
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="description-input">
<property name="maximumHeight">
<number>100</number>
</property>
</widget>
</item>
</layout>
</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>
<layout class="QHBoxLayout" name="include-focus-regions-row">
<item alignment="Qt::AlignLeft">
<widget class="QCheckBox" name="include-focus-regions-input">
<property name="text">
<string>Include focused regions</string>
</property>
<property name="toolTip">
<string>Captures any focused regions currently defined in the memory inspection pane.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontal-spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="capture-directly-from-target-row">
<item alignment="Qt::AlignLeft">
<widget class="QCheckBox" name="capture-directly-from-target-input">
<property name="text">
<string>Capture directly from target</string>
</property>
<property name="toolTip">
<string>Captures the memory directly from the target, as opposed to what's currently loaded in the memory inspection pane.</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontal-spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="stale-data-warning">
<layout class="QHBoxLayout">
<property name="spacing">
<number>1</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<spacer name="horizontal-spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="SvgWidget" name="icon">
<property name="containerHeight">
<number>15</number>
</property>
<property name="containerWidth">
<number>14</number>
</property>
<property name="svgFilePath">
<string>:/compiled/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/stale-data-icon.svg</string>
</property>
</widget>
</item>
<item>
<widget class="Label">
<property name="text">
<string>Captured data may be stale</string>
</property>
<property name="toolTip">
<string>Use the 'Capture directly from target' option to avoid capturing stale data.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="vertical-spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="actions">
<property name="minimumHeight">
<number>25</number>
</property>
<property name="maximumHeight">
<number>25</number>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"/>
</property>
<layout class="QHBoxLayout">
<property name="spacing">
<number>15</number>
</property>
<item>
<spacer name="horizontal-spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="PushButton" name="close-btn">
<property name="text">
<string>Cancel</string>
</property>
</widget>
</item>
<item>
<widget class="PushButton" name="capture-btn">
<property name="styleName">
<string>primary</string>
</property>
<property name="text">
<string>Capture</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</ui>

View File

@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
viewBox="0 0 4.2333334 4.2333333"
version="1.1"
id="svg974"
sodipodi:docname="new-manage-snapshot-icon.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs968" />
<sodipodi:namedview
id="base"
pagecolor="#343532"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="29.367338"
inkscape:cx="1.8217518"
inkscape:cy="4.7672009"
inkscape:document-units="px"
inkscape:current-layer="layer2"
inkscape:document-rotation="0"
showgrid="false"
units="px"
inkscape:snap-page="true"
inkscape:window-width="3440"
inkscape:window-height="1353"
inkscape:window-x="2560"
inkscape:window-y="34"
inkscape:window-maximized="1"
inkscape:snap-smooth-nodes="true"
inkscape:snap-bbox="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:pagecheckerboard="0"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0" />
<metadata
id="metadata971">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Main"
style="display:inline"
transform="translate(-0.26458334,-0.26458334)">
<path
id="rect6137"
style="fill:#838382;fill-opacity:0.5;stroke:none;stroke-width:1.327;stroke-miterlimit:4;stroke-dasharray:none"
d="m 1,1 v 13 h 3 v -4 h 7 v 4 h 3 V 1 Z m 2,2 h 9 V 8 H 3 Z"
transform="scale(0.26458334)" />
<rect
style="fill:#838382;fill-opacity:0.5;stroke:none;stroke-width:0.568665;stroke-miterlimit:4;stroke-dasharray:none"
id="rect22557"
width="1.3229166"
height="0.5291667"
x="1.3229166"
y="2.9104166" />
<rect
style="display:inline;fill:#838382;fill-opacity:1;stroke-width:0.0520833"
id="rect1537-3-6-3-7"
width="0.5291667"
height="2.1166666"
x="3.175"
y="2.3812501" />
<rect
style="display:inline;fill:#838382;fill-opacity:1;stroke-width:0.0823502"
id="rect1537-3-6-3-6-5"
width="2.1166666"
height="0.5291667"
x="2.3812501"
y="3.1750002" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,69 @@
#include "MemorySnapshotItem.hpp"
#include "src/Helpers/DateTime.hpp"
namespace Bloom::Widgets
{
MemorySnapshotItem::MemorySnapshotItem(
const MemorySnapshot& memorySnapshot,
QWidget *parent
)
: memorySnapshot(memorySnapshot)
, ClickableWidget(parent)
{
this->setObjectName("snapshot-item");
this->setFixedHeight(50);
this->layout->setContentsMargins(5, 5, 5, 0);
this->nameLabel->setText(memorySnapshot.name);
this->nameLabel->setObjectName("name-label");
this->programCounterLabel->setText("0x" + QString::number(this->memorySnapshot.programCounter, 16).toUpper());
this->programCounterLabel->setObjectName("program-counter-label");
this->createdDateLabel->setText(
memorySnapshot.createdDate.toString(
memorySnapshot.createdDate.date() == DateTime::currentDate()
? "hh:mm"
: "dd/MM/yyyy hh:mm"
)
);
this->createdDateLabel->setObjectName("created-date-label");
auto* topLabelLayout = new QHBoxLayout();
topLabelLayout->setSpacing(0);
topLabelLayout->setContentsMargins(0, 0, 0, 0);
topLabelLayout->addWidget(this->nameLabel, 0, Qt::AlignmentFlag::AlignLeft);
topLabelLayout->addStretch(1);
topLabelLayout->addWidget(this->programCounterLabel, 0, Qt::AlignmentFlag::AlignRight);
auto* bottomLabelLayout = new QHBoxLayout();
bottomLabelLayout->setSpacing(0);
bottomLabelLayout->setContentsMargins(0, 0, 0, 0);
bottomLabelLayout->addWidget(this->createdDateLabel, 0, Qt::AlignmentFlag::AlignLeft);
this->layout->setSpacing(5);
this->layout->addLayout(topLabelLayout);
this->layout->addLayout(bottomLabelLayout);
this->layout->addStretch(1);
auto onClick = [this] {
this->setSelected(true);
};
QObject::connect(this, &ClickableWidget::clicked, this, onClick);
QObject::connect(this, &ClickableWidget::rightClicked, this, onClick);
this->setSelected(false);
}
void MemorySnapshotItem::setSelected(bool selected) {
this->setProperty("selected", selected);
this->style()->unpolish(this);
this->style()->polish(this);
if (selected) {
emit this->selected(this);
}
}
}

View File

@@ -0,0 +1,40 @@
#pragma once
#include <QVBoxLayout>
#include <QComboBox>
#include <map>
#include <QString>
#include <QStringList>
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ClickableWidget.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemorySnapshot.hpp"
#include "src/Targets/TargetMemory.hpp"
namespace Bloom::Widgets
{
class MemorySnapshotItem: public ClickableWidget
{
Q_OBJECT
public:
const MemorySnapshot& memorySnapshot;
MemorySnapshotItem(
const MemorySnapshot& memorySnapshot,
QWidget *parent
);
void setSelected(bool selected);
signals:
void selected(MemorySnapshotItem*);
private:
QVBoxLayout* layout = new QVBoxLayout(this);
Label* nameLabel = new Label(this);
Label* programCounterLabel = new Label(this);
Label* createdDateLabel = new Label(this);
};
}

View File

@@ -0,0 +1,205 @@
#include "SnapshotManager.hpp"
#include <QDesktopServices>
#include "src/Insight/UserInterfaces/InsightWindow/UiLoader.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ErrorDialogue/ErrorDialogue.hpp"
#include "src/Insight/InsightWorker/Tasks/RetrieveMemorySnapshots.hpp"
#include "src/Insight/InsightWorker/Tasks/CaptureMemorySnapshot.hpp"
#include "src/Insight/InsightWorker/InsightWorker.hpp"
#include "src/Helpers/Paths.hpp"
#include "src/Helpers/EnumToStringMappings.hpp"
#include "src/Exceptions/Exception.hpp"
#include "src/Logger/Logger.hpp"
namespace Bloom::Widgets
{
using Bloom::Exceptions::Exception;
SnapshotManager::SnapshotManager(
const Targets::TargetMemoryDescriptor& memoryDescriptor,
const std::optional<Targets::TargetMemoryBuffer>& data,
const bool& staleData,
PaneState& state,
PanelWidget* parent
)
: PaneWidget(state, parent)
, memoryDescriptor(memoryDescriptor)
, data(data)
, staleData(staleData)
{
this->setObjectName("snapshot-manager");
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
auto widgetUiFile = QFile(
QString::fromStdString(Paths::compiledResourcesPath()
+ "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane"
+ "/SnapshotManager/UiFiles/SnapshotManager.ui"
)
);
if (!widgetUiFile.open(QFile::ReadOnly)) {
throw Exception("Failed to open SnapshotManager UI file");
}
auto uiLoader = UiLoader(this);
this->container = uiLoader.load(&widgetUiFile, this);
this->container->setFixedSize(this->size());
this->container->setContentsMargins(0, 0, 0, 0);
this->toolBar = this->container->findChild<QWidget*>("tool-bar");
this->createSnapshotButton = this->toolBar->findChild<SvgToolButton*>("create-snapshot-btn");
this->deleteSnapshotButton = this->toolBar->findChild<SvgToolButton*>("delete-snapshot-btn");
this->itemScrollArea = this->container->findChild<QScrollArea*>("snapshot-item-scroll-area");
this->itemScrollAreaViewport = this->itemScrollArea->findChild<QWidget*>("item-container");
this->itemLayout = this->itemScrollAreaViewport->findChild<QVBoxLayout*>(
"item-container-layout"
);
this->itemScrollArea->setContentsMargins(0, 0, 0, 0);
this->itemScrollAreaViewport->setContentsMargins(0, 0, 0, 0);
this->itemLayout->setContentsMargins(0, 0, 0, 0);
this->createSnapshotWindow = new CreateSnapshotWindow(
this->memoryDescriptor.type,
this->data,
this->staleData,
this
);
QObject::connect(
this->createSnapshotWindow,
&CreateSnapshotWindow::snapshotCaptureRequested,
this,
&SnapshotManager::createSnapshot
);
QObject::connect(
this->createSnapshotButton,
&QToolButton::clicked,
this,
[this] {
if (!this->createSnapshotWindow->isVisible()) {
this->createSnapshotWindow->show();
return;
}
this->createSnapshotWindow->activateWindow();
}
);
auto* retrieveSnapshotsTask = new RetrieveMemorySnapshots(this->memoryDescriptor.type);
QObject::connect(
retrieveSnapshotsTask,
&RetrieveMemorySnapshots::memorySnapshotsRetrieved,
this,
[this] (std::vector<MemorySnapshot> snapshots) {
for (auto& snapshot : snapshots) {
if (!snapshot.isCompatible(this->memoryDescriptor)) {
Logger::warning(
"Ignoring snapshot " + snapshot.id.toStdString()
+ " - snapshot incompatible with current memory descriptor"
);
continue;
}
this->addSnapshot(std::move(snapshot));
}
this->sortSnapshotItems();
}
);
InsightWorker::queueTask(retrieveSnapshotsTask);
this->show();
}
void SnapshotManager::resizeEvent(QResizeEvent* event) {
const auto size = this->size();
this->container->setFixedSize(size.width(), size.height());
PaneWidget::resizeEvent(event);
}
void SnapshotManager::showEvent(QShowEvent* event) {
PaneWidget::showEvent(event);
}
void SnapshotManager::createSnapshot(
const QString& name,
const QString& description,
bool captureFocusedRegions,
bool captureDirectlyFromTarget
) {
auto* captureTask = new CaptureMemorySnapshot(
std::move(name),
std::move(description),
this->memoryDescriptor.type,
{},
{},
captureDirectlyFromTarget ? std::nullopt : this->data
);
QObject::connect(
captureTask,
&CaptureMemorySnapshot::memorySnapshotCaptured,
this,
[this] (MemorySnapshot snapshot) {
this->addSnapshot(std::move(snapshot));
this->sortSnapshotItems();
}
);
InsightWorker::queueTask(captureTask);
}
void SnapshotManager::addSnapshot(MemorySnapshot&& snapshotTmp) {
const auto snapshotIt = this->snapshotsById.insert(std::pair(snapshotTmp.id, std::move(snapshotTmp)));
const auto& snapshot = snapshotIt.first->second;
auto* snapshotItem = new MemorySnapshotItem(snapshot, this);
this->itemLayout->addWidget(snapshotItem);
QObject::connect(
snapshotItem,
&MemorySnapshotItem::selected,
this,
&SnapshotManager::onSnapshotItemSelected
);
}
void SnapshotManager::sortSnapshotItems() {
const auto snapshotItemCompare = [] (MemorySnapshotItem* itemA, MemorySnapshotItem* itemB) {
return itemA->memorySnapshot.createdDate > itemB->memorySnapshot.createdDate;
};
auto sortedSnapshotItems = std::set<MemorySnapshotItem*, decltype(snapshotItemCompare)>(snapshotItemCompare);
QLayoutItem* layoutItem = nullptr;
while ((layoutItem = this->itemLayout->takeAt(0)) != nullptr) {
auto* snapshotItem = qobject_cast<MemorySnapshotItem*>(layoutItem->widget());
if (snapshotItem != nullptr) {
sortedSnapshotItems.insert(snapshotItem);
}
delete layoutItem;
}
for (auto* regionItem : sortedSnapshotItems) {
this->itemLayout->addWidget(regionItem);
}
}
void SnapshotManager::onSnapshotItemSelected(MemorySnapshotItem* item) {
if (this->selectedItem != nullptr && this->selectedItem != item) {
this->selectedItem->setSelected(false);
}
this->selectedItem = item;
}
}

View File

@@ -0,0 +1,69 @@
#pragma once
#include <QWidget>
#include <optional>
#include <QResizeEvent>
#include <QShowEvent>
#include <QScrollArea>
#include <QVBoxLayout>
#include <map>
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/PaneWidget.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/SvgToolButton.hpp"
#include "src/Targets/TargetMemory.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemorySnapshot.hpp"
#include "./CreateSnapshotWindow/CreateSnapshotWindow.hpp"
#include "MemorySnapshotItem.hpp"
namespace Bloom::Widgets
{
class SnapshotManager: public PaneWidget
{
Q_OBJECT
public:
CreateSnapshotWindow* createSnapshotWindow = nullptr;
MemorySnapshotItem* selectedItem = nullptr;
explicit SnapshotManager(
const Targets::TargetMemoryDescriptor& memoryDescriptor,
const std::optional<Targets::TargetMemoryBuffer>& data,
const bool& staleData,
PaneState& state,
PanelWidget* parent = nullptr
);
protected:
void resizeEvent(QResizeEvent* event) override;
void showEvent(QShowEvent* event) override;
private:
const Targets::TargetMemoryDescriptor& memoryDescriptor;
const std::optional<Targets::TargetMemoryBuffer>& data;
const bool& staleData;
std::map<QString, MemorySnapshot> snapshotsById;
QWidget* container = nullptr;
QWidget* toolBar = nullptr;
SvgToolButton* createSnapshotButton = nullptr;
SvgToolButton* deleteSnapshotButton = nullptr;
QScrollArea* itemScrollArea = nullptr;
QWidget* itemScrollAreaViewport = nullptr;
QVBoxLayout* itemLayout = nullptr;
void createSnapshot(
const QString& name,
const QString& description,
bool captureFocusedRegions,
bool captureDirectlyFromTarget
);
void addSnapshot(MemorySnapshot&& snapshotTmp);
void sortSnapshotItems();
void onSnapshotItemSelected(MemorySnapshotItem* item);
};
}

View File

@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<widget class="QWidget" name="container">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"/>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="title-bar">
<property name="minimumHeight">
<number>28</number>
</property>
<property name="maximumHeight">
<number>28</number>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"/>
</property>
<layout class="QHBoxLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<spacer name="horizontal-spacer">
<property name="sizeHint">
<size>
<width>5</width>
</size>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
</spacer>
</item>
<item>
<widget class="Label" name="snapshot-title-label">
<property name="text">
<string>Memory Snapshots</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignLeft">
<spacer name="horizontal-spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QWidget" name="tool-bar">
<property name="minimumHeight">
<number>28</number>
</property>
<property name="maximumHeight">
<number>28</number>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"/>
</property>
<layout class="QHBoxLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<spacer name="horizontal-spacer">
<property name="sizeHint">
<size>
<width>5</width>
</size>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
</spacer>
</item>
<item>
<widget class="SvgToolButton" name="create-snapshot-btn">
<property name="checkable">
<bool>false</bool>
</property>
<property name="svgFilePath">
<string>:/compiled/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/Images/new-snapshot-icon.svg</string>
</property>
<property name="toolTip">
<string>Create Snapshot</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignLeft">
<spacer name="horizontal-spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QScrollArea" name="snapshot-item-scroll-area">
<property name="widgetResizable"><bool>true</bool></property>
<property name="verticalScrollBarPolicy"><enum>Qt::ScrollBarAsNeeded</enum></property>
<property name="sizeAdjustPolicy"><enum>QAbstractScrollArea::AdjustToContents</enum></property>
<property name="horizontalScrollBarPolicy"><enum>Qt::ScrollBarAlwaysOff</enum></property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"/>
</property>
<widget class="QWidget" name="item-container">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"/>
</property>
<layout class="QVBoxLayout" name="item-container-layout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="sizeConstraint">
<enum>QLayout::SetMinAndMaxSize</enum>
</property>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</ui>

View File

@@ -208,3 +208,83 @@
padding-left: 10px;
min-width: 150px;
}
/* Snapshot Manager */
#target-memory-inspection-pane #rh-side-bar #manage-memory-snapshots-btn {
border-bottom: 1px solid #41423f;
qproperty-buttonWidth: 31;
qproperty-buttonHeight: 28;
}
#target-memory-inspection-pane #rh-side-bar #manage-memory-snapshots-btn:hover {
background-color: #2F2F2D;
}
#target-memory-inspection-pane #rh-side-bar #manage-memory-snapshots-btn:checked {
background-color: #2F2F2D;
}
#snapshot-manager #container {
background-color: #373835;
border-left: 1px solid #41423f;
}
#snapshot-manager #title-bar {
background-color: transparent;
border-bottom: 1px solid #41423f;
}
#snapshot-manager #title-bar #snapshot-title-label {
color: #8a8a8d;
}
#snapshot-manager #tool-bar {
border-bottom: 1px solid #41423f;
}
#snapshot-manager #tool-bar QToolButton {
background-color: transparent;
border: none;
padding: 0;
qproperty-buttonWidth: 20;
qproperty-buttonHeight: 18;
}
#snapshot-manager #snapshot-item-scroll-area {
border: none;
}
#snapshot-manager #snapshot-item {
border: none;
border-bottom: 1px solid #41423f;
background-color: transparent;
}
#snapshot-manager #snapshot-item:hover {
border-left: none;
background-color: rgba(142, 139, 131, 0.1);
}
#snapshot-manager #snapshot-item[selected=true] {
background-color: #355A80;
}
#snapshot-manager #snapshot-item #name-label {
font-size: 14px
}
#snapshot-manager #snapshot-item #program-counter-label,
#snapshot-manager #snapshot-item #created-date-label {
font-size: 13px;
color: #8a8a8d;
}
/* Create Snapshot Window */
#create-snapshot-window #stale-data-warning QLabel {
margin-top: 1px;
color: #9a9a9d;
}
#create-snapshot-window #stale-data-warning #icon {
margin-top: 1px;
}

View File

@@ -14,6 +14,7 @@
#include "src/Helpers/Paths.hpp"
#include "src/Exceptions/Exception.hpp"
#include "src/Logger/Logger.hpp"
namespace Bloom::Widgets
{
@@ -78,8 +79,9 @@ namespace Bloom::Widgets
// Quick sanity check to ensure the validity of persisted settings.
this->sanitiseSettings();
auto* containerLayout = this->container->findChild<QHBoxLayout*>("container-sub-layout");
this->subContainerLayout = this->container->findChild<QHBoxLayout*>("container-sub-layout");
this->manageMemoryRegionsButton = this->container->findChild<SvgToolButton*>("manage-memory-regions-btn");
this->manageMemorySnapshotsButton = this->container->findChild<SvgToolButton*>("manage-memory-snapshots-btn");
this->refreshButton = this->container->findChild<SvgToolButton*>("refresh-memory-btn");
this->refreshOnTargetStopAction = this->refreshButton->findChild<QAction*>("refresh-target-stopped");
@@ -102,7 +104,7 @@ namespace Bloom::Widgets
);
this->hexViewerWidget->setDisabled(true);
containerLayout->insertWidget(1, this->hexViewerWidget);
this->subContainerLayout->insertWidget(1, this->hexViewerWidget);
QObject::connect(
this->hexViewerWidget,
@@ -117,6 +119,21 @@ namespace Bloom::Widgets
this->hexViewerWidget->init();
this->rightPanel = new PanelWidget(PanelWidgetType::RIGHT, this->settings.rightPanelState, this);
this->rightPanel->setObjectName("right-panel");
this->rightPanel->setMinimumResize(200);
this->rightPanel->setHandleSize(5);
this->subContainerLayout->insertWidget(2, this->rightPanel);
this->snapshotManager = new SnapshotManager(
this->targetMemoryDescriptor,
this->data,
this->staleData,
this->settings.snapshotManagerState,
this->rightPanel
);
this->rightPanel->layout()->addWidget(this->snapshotManager);
this->setRefreshOnTargetStopEnabled(this->settings.refreshOnTargetStop);
this->setRefreshOnActivationEnabled(this->settings.refreshOnActivation);
@@ -155,6 +172,31 @@ namespace Bloom::Widgets
&TargetMemoryInspectionPane::openMemoryRegionManagerWindow
);
QObject::connect(
this->manageMemorySnapshotsButton,
&QToolButton::clicked,
this,
&TargetMemoryInspectionPane::toggleMemorySnapshotManagerPane
);
QObject::connect(
this->snapshotManager,
&PaneWidget::paneActivated,
this,
[this] {
this->manageMemorySnapshotsButton->setChecked(true);
}
);
QObject::connect(
this->snapshotManager,
&PaneWidget::paneDeactivated,
this,
[this] {
this->manageMemorySnapshotsButton->setChecked(false);
}
);
QObject::connect(
this->refreshButton,
&QToolButton::clicked,
@@ -247,6 +289,8 @@ namespace Bloom::Widgets
} else {
this->deactivate();
}
this->snapshotManager->deactivate();
}
void TargetMemoryInspectionPane::refreshMemoryValues(std::optional<std::function<void(void)>> callback) {
@@ -362,6 +406,8 @@ namespace Bloom::Widgets
const auto size = this->size();
this->container->setFixedSize(size.width(), size.height());
this->rightPanel->setMaximumResize(static_cast<int>(size.width() * 0.4));
PaneWidget::resizeEvent(event);
}
@@ -486,6 +532,8 @@ namespace Bloom::Widgets
this->data = data;
this->hexViewerWidget->updateValues(this->data.value());
this->setStaleData(false);
this->snapshotManager->createSnapshotWindow->refreshForm();
}
void TargetMemoryInspectionPane::openMemoryRegionManagerWindow() {
@@ -515,6 +563,15 @@ namespace Bloom::Widgets
}
}
void TargetMemoryInspectionPane::toggleMemorySnapshotManagerPane() {
if (!this->snapshotManager->state.activated) {
this->snapshotManager->activate();
return;
}
this->snapshotManager->deactivate();
}
void TargetMemoryInspectionPane::onMemoryRegionsChange() {
this->hexViewerWidget->refreshRegions();
}
@@ -546,6 +603,7 @@ namespace Bloom::Widgets
) {
if (memoryType == this->targetMemoryDescriptor.type && this->data.has_value()) {
this->setStaleData(true);
this->snapshotManager->createSnapshotWindow->refreshForm();
}
}

View File

@@ -1,8 +1,10 @@
#pragma once
#include <QWidget>
#include <QResizeEvent>
#include <optional>
#include <vector>
#include <QResizeEvent>
#include <QHBoxLayout>
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/PaneWidget.hpp"
@@ -15,6 +17,7 @@
#include "HexViewerWidget/HexViewerWidget.hpp"
#include "MemoryRegionManager/MemoryRegionManagerWindow.hpp"
#include "SnapshotManager/SnapshotManager.hpp"
#include "TargetMemoryInspectionPaneSettings.hpp"
@@ -50,9 +53,11 @@ namespace Bloom::Widgets
std::optional<Targets::TargetMemoryBuffer> data;
QWidget* container = nullptr;
QHBoxLayout* subContainerLayout = nullptr;
QWidget* titleBar = nullptr;
SvgToolButton* manageMemoryRegionsButton = nullptr;
SvgToolButton* manageMemorySnapshotsButton = nullptr;
SvgToolButton* refreshButton = nullptr;
QAction* refreshOnTargetStopAction = nullptr;
@@ -62,6 +67,9 @@ namespace Bloom::Widgets
SvgToolButton* attachPaneButton = nullptr;
HexViewerWidget* hexViewerWidget = nullptr;
PanelWidget* rightPanel = nullptr;
SnapshotManager* snapshotManager = nullptr;
QWidget* staleDataLabelContainer = nullptr;
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
@@ -76,6 +84,7 @@ namespace Bloom::Widgets
void setRefreshOnActivationEnabled(bool enabled);
void onMemoryRead(const Targets::TargetMemoryBuffer& data);
void openMemoryRegionManagerWindow();
void toggleMemorySnapshotManagerPane();
void onMemoryRegionsChange();
void onTargetReset();
void onProgrammingModeEnabled();

View File

@@ -7,6 +7,9 @@
#include "HexViewerWidget/HexViewerWidgetSettings.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/PanelState.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/PaneState.hpp"
namespace Bloom::Widgets
{
struct TargetMemoryInspectionPaneSettings
@@ -18,5 +21,8 @@ namespace Bloom::Widgets
std::vector<FocusedMemoryRegion> focusedMemoryRegions;
std::vector<ExcludedMemoryRegion> excludedMemoryRegions;
PanelState rightPanelState = PanelState(300, true);
PaneState snapshotManagerState = PaneState(true, true, std::nullopt);
};
}

View File

@@ -236,16 +236,20 @@
<property name="margin">
<number>0</number>
</property>
<item>
<spacer name="vertical-spacer">
<property name="sizeHint">
<size>
<height>3</height>
</size>
</property>
</spacer>
</item>
<!-- Button items here -->
<item alignment="Qt::AlignRight">
<widget class="SvgToolButton" name="manage-memory-snapshots-btn">
<property name="checkable">
<bool>true</bool>
</property>
<property name="svgFilePath">
<string>:/compiled/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/manage-snapshots-icon.svg</string>
</property>
<property name="toolTip">
<string>Manage Memory Snapshots</string>
</property>
</widget>
</item>
<item>
<spacer name="vertical-spacer">
<property name="orientation">