diff --git a/src/Helpers/DateTime.hpp b/src/Helpers/DateTime.hpp index 760c7743..5b69afde 100644 --- a/src/Helpers/DateTime.hpp +++ b/src/Helpers/DateTime.hpp @@ -2,6 +2,7 @@ #include #include +#include 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(), diff --git a/src/Insight/CMakeLists.txt b/src/Insight/CMakeLists.txt index ed32ee29..26d9f0b7 100755 --- a/src/Insight/CMakeLists.txt +++ b/src/Insight/CMakeLists.txt @@ -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" diff --git a/src/Insight/UserInterfaces/InsightWindow/Stylesheets/Global.qss b/src/Insight/UserInterfaces/InsightWindow/Stylesheets/Global.qss index 281ae8c6..fcc55e8e 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Stylesheets/Global.qss +++ b/src/Insight/UserInterfaces/InsightWindow/Stylesheets/Global.qss @@ -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"); } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/manage-snapshots-icon.svg b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/manage-snapshots-icon.svg new file mode 100644 index 00000000..a5877bb7 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/manage-snapshots-icon.svg @@ -0,0 +1,74 @@ + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/CreateSnapshotWindow.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/CreateSnapshotWindow.cpp new file mode 100644 index 00000000..e91743de --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/CreateSnapshotWindow.cpp @@ -0,0 +1,140 @@ +#include "CreateSnapshotWindow.hpp" + +#include +#include +#include + +#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& 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("form-container"); + + this->nameInput = formContainer->findChild("name-input"); + this->descriptionInput = formContainer->findChild("description-input"); + this->includeFocusedRegionsInput = formContainer->findChild("include-focus-regions-input"); + this->captureDirectlyFromTargetInput = formContainer->findChild("capture-directly-from-target-input"); + + this->staleDataWarning = formContainer->findChild("stale-data-warning"); + + this->captureButton = this->container->findChild("capture-btn"); + this->closeButton = this->container->findChild("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(); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/CreateSnapshotWindow.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/CreateSnapshotWindow.hpp new file mode 100644 index 00000000..2747d79a --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/CreateSnapshotWindow.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include + +#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& 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& data; + const bool& staleData; + Targets::TargetState targetState = Targets::TargetState::UNKNOWN; + + bool captureEnabled(); + void resetForm(); + + void issueCaptureRequest(); + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/UiFiles/CreateSnapshotWindow.ui b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/UiFiles/CreateSnapshotWindow.ui new file mode 100644 index 00000000..753f1383 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/CreateSnapshotWindow/UiFiles/CreateSnapshotWindow.ui @@ -0,0 +1,218 @@ + + + + + + 0 + + + 0 + + + + + + + + + 10 + + + 0 + + + + + + + + + + 100 + + + Name: + + + + + + + + + + + + + + + + + + 100 + + + Description: + + + + + + + 100 + + + + + + + + + + 10 + + + + QSizePolicy::Fixed + + + + + + + + + Include focused regions + + + Captures any focused regions currently defined in the memory inspection pane. + + + + + + + Qt::Horizontal + + + + + + + + + + + Capture directly from target + + + Captures the memory directly from the target, as opposed to what's currently loaded in the memory inspection pane. + + + + + + + Qt::Horizontal + + + + + + + + + + 1 + + + 0 + + + + + Qt::Horizontal + + + + + + + 15 + + + 14 + + + :/compiled/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/stale-data-icon.svg + + + + + + + Captured data may be stale + + + Use the 'Capture directly from target' option to avoid capturing stale data. + + + + + + + + + + Qt::Vertical + + + + + + + + + + 25 + + + 25 + + + + + + + 15 + + + + + Qt::Horizontal + + + + + + + Cancel + + + + + + + primary + + + Capture + + + + + + + + + diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/Images/new-snapshot-icon.svg b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/Images/new-snapshot-icon.svg new file mode 100644 index 00000000..1965ad94 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/Images/new-snapshot-icon.svg @@ -0,0 +1,93 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/MemorySnapshotItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/MemorySnapshotItem.cpp new file mode 100644 index 00000000..7114ab00 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/MemorySnapshotItem.cpp @@ -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); + } + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/MemorySnapshotItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/MemorySnapshotItem.hpp new file mode 100644 index 00000000..bf0a7bad --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/MemorySnapshotItem.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include + +#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); + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.cpp new file mode 100644 index 00000000..2e60e2e9 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.cpp @@ -0,0 +1,205 @@ +#include "SnapshotManager.hpp" + +#include + +#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& 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("tool-bar"); + this->createSnapshotButton = this->toolBar->findChild("create-snapshot-btn"); + this->deleteSnapshotButton = this->toolBar->findChild("delete-snapshot-btn"); + + this->itemScrollArea = this->container->findChild("snapshot-item-scroll-area"); + this->itemScrollAreaViewport = this->itemScrollArea->findChild("item-container"); + this->itemLayout = this->itemScrollAreaViewport->findChild( + "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 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(snapshotItemCompare); + + QLayoutItem* layoutItem = nullptr; + while ((layoutItem = this->itemLayout->takeAt(0)) != nullptr) { + auto* snapshotItem = qobject_cast(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; + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.hpp new file mode 100644 index 00000000..b006dfc9 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotManager.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#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& 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& data; + const bool& staleData; + + std::map 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); + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/UiFiles/SnapshotManager.ui b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/UiFiles/SnapshotManager.ui new file mode 100644 index 00000000..580f332c --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/UiFiles/SnapshotManager.ui @@ -0,0 +1,144 @@ + + + + + + + + + 0 + + + 0 + + + + + 28 + + + 28 + + + + + + + 3 + + + 0 + + + + + + 5 + + + + QSizePolicy::Fixed + + + + + + + Memory Snapshots + + + + + + + Qt::Horizontal + + + + + + + + + + 28 + + + 28 + + + + + + + 3 + + + 0 + + + + + + 5 + + + + QSizePolicy::Fixed + + + + + + + false + + + :/compiled/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/Images/new-snapshot-icon.svg + + + Create Snapshot + + + + + + + Qt::Horizontal + + + + + + + + + true + Qt::ScrollBarAsNeeded + QAbstractScrollArea::AdjustToContents + Qt::ScrollBarAlwaysOff + + + + + + + + + + 0 + + + 0 + + + QLayout::SetMinAndMaxSize + + + + + + + + + diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Stylesheets/TargetMemoryInspectionPane.qss b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Stylesheets/TargetMemoryInspectionPane.qss index f06a5c77..46747a51 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Stylesheets/TargetMemoryInspectionPane.qss +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Stylesheets/TargetMemoryInspectionPane.qss @@ -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; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp index 43f608dc..679e0b50 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp @@ -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("container-sub-layout"); + this->subContainerLayout = this->container->findChild("container-sub-layout"); this->manageMemoryRegionsButton = this->container->findChild("manage-memory-regions-btn"); + this->manageMemorySnapshotsButton = this->container->findChild("manage-memory-snapshots-btn"); this->refreshButton = this->container->findChild("refresh-memory-btn"); this->refreshOnTargetStopAction = this->refreshButton->findChild("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> 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(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(); } } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.hpp index 02fdc9f6..5c2958cc 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.hpp @@ -1,8 +1,10 @@ #pragma once #include -#include +#include #include +#include +#include #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 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(); diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPaneSettings.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPaneSettings.hpp index fbaafbd6..8d8a820e 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPaneSettings.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPaneSettings.hpp @@ -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 focusedMemoryRegions; std::vector excludedMemoryRegions; + + PanelState rightPanelState = PanelState(300, true); + PaneState snapshotManagerState = PaneState(true, true, std::nullopt); }; } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/UiFiles/TargetMemoryInspectionPane.ui b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/UiFiles/TargetMemoryInspectionPane.ui index 2c2c9909..cb2aa9bb 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/UiFiles/TargetMemoryInspectionPane.ui +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/UiFiles/TargetMemoryInspectionPane.ui @@ -236,16 +236,20 @@ 0 - - - - - 3 - - - - + + + + true + + + :/compiled/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/Images/manage-snapshots-icon.svg + + + Manage Memory Snapshots + + +