New target memory inspection pane and hex viewer widget

This commit is contained in:
Nav
2021-10-17 20:44:40 +01:00
parent 52a9b27519
commit 7c65e182af
18 changed files with 1435 additions and 17 deletions

View File

@@ -0,0 +1,114 @@
#include "ByteWidget.hpp"
#include <QPainter>
#include <QStyle>
using namespace Bloom::Widgets;
ByteWidget::ByteWidget(
std::size_t byteIndex,
std::uint32_t address,
std::optional<ByteWidget*>& hoveredByteWidget,
QWidget* parent
): ClickableWidget(parent), byteIndex(byteIndex), address(address), hoveredByteWidget(hoveredByteWidget) {
this->setObjectName("byte");
auto onClick = [this] {
this->setSelected(true);
};
this->addressHex = "0x" + QString::number(this->address, 16).rightJustified(8, '0').toUpper();
this->relativeAddressHex = "0x" + QString::number(this->byteIndex, 16).rightJustified(8, '0').toUpper();
this->connect(this, &ClickableWidget::clicked, this, onClick);
this->connect(this, &ClickableWidget::rightClicked, this, onClick);
this->setSelected(false);
}
void ByteWidget::setValue(unsigned char value) {
this->valueChanged = this->valueInitialised && this->value != value;
this->value = value;
this->hexValue = QString::number(this->value, 16).rightJustified(2, '0').toUpper();
this->asciiValue = (this->value >= 32 && this->value <= 126)
? std::optional(QString(QChar(this->value))) : std::nullopt;
this->valueInitialised = true;
}
void ByteWidget::setSelected(bool selected) {
this->setProperty("selected", selected);
this->style()->unpolish(this);
this->style()->polish(this);
if (selected) {
emit this->selected(this);
}
this->postSetSelected(selected);
}
bool ByteWidget::event(QEvent* event) {
if (this->isEnabled()) {
switch (event->type()) {
case QEvent::Enter: {
this->hoverActive = true;
emit this->enter(this);
this->update();
break;
}
case QEvent::Leave: {
this->hoverActive = false;
emit this->leave(this);
this->update();
break;
}
default: {
break;
}
}
}
return QWidget::event(event);
}
void ByteWidget::paintEvent(QPaintEvent* event) {
auto painter = QPainter(this);
this->drawWidget(painter);
}
void ByteWidget::drawWidget(QPainter& painter) {
painter.setRenderHints(QPainter::RenderHint::Antialiasing | QPainter::RenderHint::SmoothPixmapTransform, true);
painter.setPen(Qt::PenStyle::NoPen);
static const auto widgetRect = QRect(0, 0, ByteWidget::WIDTH, ByteWidget::HEIGHT);
if (this->hoveredByteWidget.has_value()
&& (
this->hoveredByteWidget.value()->currentColumnIndex == this->currentColumnIndex
|| this->hoveredByteWidget.value()->currentRowIndex == this->currentRowIndex
)
) {
painter.setBrush(QColor(0x8E, 0x8B, 0x83, this->hoverActive ? 70 : 30));
painter.drawRect(widgetRect);
}
auto textColor = QColor(this->valueChanged ? "#547fba" : "#afb1b3");
if (this->valueInitialised) {
if (!this->isEnabled()) {
textColor.setAlpha(100);
}
painter.setPen(textColor);
painter.drawText(widgetRect, Qt::AlignCenter, this->hexValue);
} else {
textColor.setAlpha(100);
painter.setPen(textColor);
static const auto placeholderString = QString("??");
painter.drawText(widgetRect, Qt::AlignCenter, placeholderString);
}
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include <cstdint>
#include <QEvent>
#include <optional>
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ClickableWidget.hpp"
namespace Bloom::Widgets
{
class ByteWidget: public ClickableWidget
{
Q_OBJECT
public:
static constexpr int WIDTH = 25;
static constexpr int HEIGHT = 20;
static constexpr int RIGHT_MARGIN = 5;
static constexpr int BOTTOM_MARGIN = 5;
std::size_t byteIndex;
unsigned char value = 0x00;
std::uint32_t address = 0x00;
QString addressHex;
QString relativeAddressHex;
bool valueInitialised = false;
std::size_t currentRowIndex = 0;
std::size_t currentColumnIndex = 0;
ByteWidget(
std::size_t byteNumber,
std::uint32_t address,
std::optional<ByteWidget*>& hoveredByteWidget,
QWidget* parent
);
void setValue(unsigned char value);
public slots:
void setSelected(bool selected);
signals:
void selected(ByteWidget*);
void enter(ByteWidget*);
void leave(ByteWidget*);
protected:
virtual void postSetSelected(bool selected) {};
bool event(QEvent* event) override;
void paintEvent(QPaintEvent* event) override;
void drawWidget(QPainter& painter);
private:
bool hoverActive = false;
bool valueChanged = false;
QString hexValue;
std::optional<QString> asciiValue;
std::optional<ByteWidget*>& hoveredByteWidget;
};
}

View File

@@ -0,0 +1,152 @@
#include "ByteWidgetContainer.hpp"
#include <QVBoxLayout>
#include <QTableWidget>
#include <QScrollBar>
#include <QPainter>
#include <cmath>
using namespace Bloom::Widgets;
using namespace Bloom::Exceptions;
using Bloom::Targets::TargetMemoryDescriptor;
ByteWidgetContainer::ByteWidgetContainer(
const TargetMemoryDescriptor& targetMemoryDescriptor,
InsightWorker& insightWorker,
QWidget* parent
): QWidget(parent), targetMemoryDescriptor(targetMemoryDescriptor), insightWorker(insightWorker), parent(parent) {
this->setObjectName("byte-widget-container");
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Ignored);
this->setContentsMargins(
10,
10,
10,
10
);
/*
* Construct ByteWidget objects
*
* No need to position them here - the subsequent call to resizeEvent() will do that.
*/
const auto memorySize = this->targetMemoryDescriptor.size();
const auto startAddress = this->targetMemoryDescriptor.addressRange.startAddress;
for (std::size_t i = 0; i < memorySize; i++) {
const auto address = static_cast<std::uint32_t>(startAddress + i);
auto byteWidget = new ByteWidget(i, address, this->hoveredByteWidget, this);
this->byteWidgetsByAddress.insert(std::pair(
address,
byteWidget
));
this->connect(byteWidget, &ByteWidget::enter, this, &ByteWidgetContainer::onByteWidgetEnter);
this->connect(byteWidget, &ByteWidget::leave, this, &ByteWidgetContainer::onByteWidgetLeave);
}
this->connect(
&insightWorker,
&InsightWorker::targetStateUpdated,
this,
&ByteWidgetContainer::onTargetStateChanged
);
this->show();
}
void ByteWidgetContainer::updateValues(const Targets::TargetMemoryBuffer& buffer) {
for (auto& [address, byteWidget] : this->byteWidgetsByAddress) {
byteWidget->setValue(buffer.at(byteWidget->byteIndex));
byteWidget->update();
}
}
void ByteWidgetContainer::resizeEvent(QResizeEvent* event) {
this->adjustByteWidgets();
}
void ByteWidgetContainer::adjustByteWidgets() {
std::map<std::size_t, std::vector<ByteWidget*>> byteWidgetsByRowIndex;
std::map<std::size_t, std::vector<ByteWidget*>> byteWidgetsByColumnIndex;
const auto margins = this->contentsMargins();
const auto width = this->width();
constexpr auto byteWidgetWidth = ByteWidget::WIDTH + ByteWidget::RIGHT_MARGIN;
constexpr auto byteWidgetHeight = ByteWidget::HEIGHT + ByteWidget::BOTTOM_MARGIN;
const auto rowCapacity = static_cast<std::size_t>(
std::floor((width - margins.left() - margins.right()) / byteWidgetWidth)
);
const auto rowCount = static_cast<int>(
std::ceil(static_cast<double>(this->byteWidgetsByAddress.size()) / static_cast<double>(rowCapacity))
);
for (auto& [address, byteWidget] : this->byteWidgetsByAddress) {
const auto rowIndex = static_cast<std::size_t>(
std::ceil(static_cast<double>(byteWidget->byteIndex + 1) / static_cast<double>(rowCapacity)) - 1
);
const auto columnIndex = static_cast<std::size_t>(
static_cast<double>(byteWidget->byteIndex)
- (std::floor(byteWidget->byteIndex / rowCapacity) * static_cast<double>(rowCapacity))
);
byteWidget->setGeometry(QRect(
static_cast<int>(columnIndex * byteWidgetWidth + static_cast<std::size_t>(margins.left())),
static_cast<int>(rowIndex * byteWidgetHeight + static_cast<std::size_t>(margins.top())),
ByteWidget::WIDTH,
ByteWidget::HEIGHT
));
byteWidget->currentRowIndex = static_cast<std::size_t>(rowIndex);
byteWidget->currentColumnIndex = static_cast<std::size_t>(columnIndex);
byteWidgetsByRowIndex[byteWidget->currentRowIndex].emplace_back(byteWidget);
byteWidgetsByColumnIndex[byteWidget->currentColumnIndex].emplace_back(byteWidget);
byteWidget->update();
}
const auto minHeight = (rowCount * byteWidgetHeight) + margins.top() + margins.bottom();
this->setMinimumHeight(minHeight);
this->parent->setMinimumHeight(minHeight);
this->byteWidgetsByRowIndex.swap(byteWidgetsByRowIndex);
this->byteWidgetsByColumnIndex.swap(byteWidgetsByColumnIndex);
emit this->byteWidgetsAdjusted();
}
void ByteWidgetContainer::onTargetStateChanged(Targets::TargetState newState) {
using Targets::TargetState;
this->targetState = newState;
}
void ByteWidgetContainer::onByteWidgetEnter(ByteWidget* widget) {
this->hoveredByteWidget = widget;
if (!this->byteWidgetsByRowIndex.empty()) {
for (auto& byteWidget : this->byteWidgetsByColumnIndex.at(widget->currentColumnIndex)) {
byteWidget->update();
}
for (auto& byteWidget : this->byteWidgetsByRowIndex.at(widget->currentRowIndex)) {
byteWidget->update();
}
}
}
void ByteWidgetContainer::onByteWidgetLeave(ByteWidget* widget) {
this->hoveredByteWidget = std::nullopt;
if (!this->byteWidgetsByRowIndex.empty()) {
for (auto& byteWidget : this->byteWidgetsByColumnIndex.at(widget->currentColumnIndex)) {
byteWidget->update();
}
for (auto& byteWidget : this->byteWidgetsByRowIndex.at(widget->currentRowIndex)) {
byteWidget->update();
}
}
}

View File

@@ -0,0 +1,63 @@
#pragma once
#include <QWidget>
#include <QLabel>
#include <QToolButton>
#include <QVBoxLayout>
#include <map>
#include <vector>
#include <QSize>
#include <QString>
#include <QEvent>
#include <optional>
#include "src/Targets/TargetMemory.hpp"
#include "src/Targets/TargetState.hpp"
#include "src/Insight/InsightWorker/InsightWorker.hpp"
#include "ByteWidget.hpp"
namespace Bloom::Widgets
{
class ByteWidgetContainer: public QWidget
{
Q_OBJECT
public:
std::optional<ByteWidget*> hoveredByteWidget;
std::map<std::uint32_t, ByteWidget*> byteWidgetsByAddress;
std::map<std::size_t, std::vector<ByteWidget*>> byteWidgetsByRowIndex;
std::map<std::size_t, std::vector<ByteWidget*>> byteWidgetsByColumnIndex;
ByteWidgetContainer(
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
InsightWorker& insightWorker,
QWidget* parent
);
void updateValues(const Targets::TargetMemoryBuffer& buffer);
signals:
void byteWidgetsAdjusted();
protected:
void resizeEvent(QResizeEvent* event) override;
private:
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor;
InsightWorker& insightWorker;
QWidget* parent = nullptr;
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
void adjustByteWidgets();
private slots:
void onTargetStateChanged(Targets::TargetState newState);
void onByteWidgetEnter(ByteWidget* widget);
void onByteWidgetLeave(ByteWidget* widget);
};
}

View File

@@ -0,0 +1,137 @@
#include "HexViewerWidget.hpp"
#include <QVBoxLayout>
#include <QTableWidget>
#include <QScrollBar>
#include <QScrollArea>
#include <QPainter>
#include <cmath>
#include "src/Insight/UserInterfaces/InsightWindow/UiLoader.hpp"
#include "src/Helpers/Paths.hpp"
#include "src/Exceptions/Exception.hpp"
using namespace Bloom::Widgets;
using namespace Bloom::Exceptions;
using Bloom::Targets::TargetMemoryDescriptor;
HexViewerWidget::HexViewerWidget(
const TargetMemoryDescriptor& targetMemoryDescriptor,
InsightWorker& insightWorker,
QWidget* parent
): QWidget(parent), targetMemoryDescriptor(targetMemoryDescriptor), insightWorker(insightWorker) {
this->setObjectName("hex-viewer-widget");
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
auto widgetUiFile = QFile(
QString::fromStdString(Paths::compiledResourcesPath()
+ "/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget"
+ "/UiFiles/HexViewerWidget.ui"
)
);
if (!widgetUiFile.open(QFile::ReadOnly)) {
throw Exception("Failed to open HexViewerWidget 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->refreshButton = this->container->findChild<QToolButton*>("refresh-memory-btn");
this->toolBar->setContentsMargins(0, 0, 0, 0);
this->toolBar->layout()->setContentsMargins(5, 0, 5, 0);
this->byteWidgetScrollArea = this->container->findChild<QScrollArea*>("byte-widget-scroll-area");
auto byteWidgetScrollAreaWidgetContainer = this->byteWidgetScrollArea->findChild<QWidget*>(
"byte-widget-scroll-area-container"
);
auto byteWidgetScrollAreaHorizontalLayout = byteWidgetScrollAreaWidgetContainer->findChild<QHBoxLayout*>(
"byte-widget-scroll-area-horizontal-layout"
);
this->byteWidgetContainer = new ByteWidgetContainer(
targetMemoryDescriptor,
insightWorker,
byteWidgetScrollAreaHorizontalLayout->parentWidget()
);
byteWidgetScrollAreaHorizontalLayout->addWidget(this->byteWidgetContainer);
this->byteWidgetAddressContainer = byteWidgetScrollAreaWidgetContainer->findChild<QWidget*>(
"address-container"
);
this->byteWidgetAddressLayout = this->byteWidgetAddressContainer->findChild<QVBoxLayout*>();
this->byteWidgetAddressLayout->setContentsMargins(5, 10, 0, 5);
this->connect(
this->byteWidgetContainer,
&ByteWidgetContainer::byteWidgetsAdjusted,
this,
&HexViewerWidget::onByteWidgetsAdjusted
);
this->connect(
&insightWorker,
&InsightWorker::targetStateUpdated,
this,
&HexViewerWidget::onTargetStateChanged
);
this->show();
}
void HexViewerWidget::updateValues(const Targets::TargetMemoryBuffer& buffer) {
this->byteWidgetContainer->updateValues(buffer);
}
void HexViewerWidget::resizeEvent(QResizeEvent* event) {
this->container->setFixedSize(
this->width(),
this->height()
);
}
void HexViewerWidget::onTargetStateChanged(Targets::TargetState newState) {
using Targets::TargetState;
this->targetState = newState;
}
void HexViewerWidget::onByteWidgetsAdjusted() {
const auto& byteWidgetsByRowIndex = this->byteWidgetContainer->byteWidgetsByRowIndex;
int layoutItemMaxIndex = this->byteWidgetAddressLayout->count() - 1;
for (const auto& mappingPair : byteWidgetsByRowIndex) {
const auto rowIndex = static_cast<int>(mappingPair.first);
const auto& byteWidgets = mappingPair.second;
if (byteWidgets.empty()) {
continue;
}
QLabel* labelWidget;
if (rowIndex > layoutItemMaxIndex) {
labelWidget = new QLabel(this->byteWidgetAddressContainer);
labelWidget->setFixedSize(75, 20);
labelWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
this->byteWidgetAddressLayout->addWidget(labelWidget);
layoutItemMaxIndex++;
} else {
labelWidget = qobject_cast<QLabel*>(this->byteWidgetAddressLayout->itemAt(rowIndex)->widget());
}
labelWidget->setText(byteWidgets.front()->relativeAddressHex);
}
const auto rowCount = static_cast<int>(byteWidgetsByRowIndex.size());
QLayoutItem* labelItem;
while ((labelItem = this->byteWidgetAddressLayout->takeAt(rowCount)) != nullptr) {
labelItem->widget()->deleteLater();
}
}

View File

@@ -0,0 +1,59 @@
#pragma once
#include <QWidget>
#include <QLabel>
#include <QToolButton>
#include <QVBoxLayout>
#include <set>
#include <map>
#include <QSize>
#include <QString>
#include <QEvent>
#include <optional>
#include "src/Targets/TargetMemory.hpp"
#include "src/Targets/TargetState.hpp"
#include "src/Insight/InsightWorker/InsightWorker.hpp"
#include "ByteWidgetContainer.hpp"
namespace Bloom::Widgets
{
class HexViewerWidget: public QWidget
{
Q_OBJECT
public:
QToolButton* refreshButton = nullptr;
HexViewerWidget(
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
InsightWorker& insightWorker,
QWidget* parent
);
void updateValues(const Targets::TargetMemoryBuffer& buffer);
protected:
void resizeEvent(QResizeEvent* event) override;
private:
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor;
InsightWorker& insightWorker;
QWidget* container = nullptr;
QWidget* toolBar = nullptr;
ByteWidgetContainer* byteWidgetContainer = nullptr;
QWidget* byteWidgetScrollArea = nullptr;
QWidget* byteWidgetAddressContainer = nullptr;
QVBoxLayout* byteWidgetAddressLayout = nullptr;
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
private slots:
void onTargetStateChanged(Targets::TargetState newState);
void onByteWidgetsAdjusted();
};
}

View File

@@ -0,0 +1,125 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<widget class="QWidget" name="hex-viewer-container">
<layout class="QVBoxLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"/>
</property>
<item alignment="Qt::AlignTop">
<widget class="QWidget" name="tool-bar">
<property name="minimumHeight">
<number>27</number>
</property>
<property name="maximumHeight">
<number>27</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>
<widget class="SvgToolButton" name="refresh-memory-btn">
<property name="svgFilePath">
<string>:/compiled/src/Insight/UserInterfaces/InsightWindow/Images/refresh.svg</string>
</property>
<property name="disabledSvgFilePath">
<string>:/compiled/src/Insight/UserInterfaces/InsightWindow/Images/refresh-disabled.svg</string>
</property>
<property name="toolTip">
<string>Refresh Memory</string>
</property>
</widget>
</item>
<item alignment="Qt::AlignTop">
<spacer name="horizontal-spacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="ExpandingHeightScrollAreaWidget" name="byte-widget-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="byte-widget-scroll-area-container">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Ignored"/>
</property>
<layout class="QVBoxLayout" name="byte-widget-scroll-area-vertical-layout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="byte-widget-scroll-area-horizontal-layout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="address-container">
<property name="minimumWidth">
<number>80</number>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Ignored"/>
</property>
<layout class="QVBoxLayout">
<property name="spacing">
<number>5</number>
</property>
<property name="margin">
<number>0</number>
</property>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="vertical-spacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<!-- <item>-->
<!-- <spacer name="vertical-spacer">-->
<!-- <property name="orientation">-->
<!-- <enum>Qt::Vertical</enum>-->
<!-- </property>-->
<!-- </spacer>-->
<!-- </item>-->
</layout>
</widget>
</ui>