Added annotation items in the hex viewer, for focused regions

Corrected issue with unexpected horizontal scrolling in hex viewer
Added highlighting of focused byte items, in the hex viewer widget
This commit is contained in:
Nav
2021-12-24 02:53:34 +00:00
parent a5460fd9ae
commit 7c2a8705e7
15 changed files with 595 additions and 158 deletions

View File

@@ -198,6 +198,7 @@ add_executable(Bloom
src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.cpp
src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.cpp
src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.cpp
src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/AnnotationItem.cpp
# Memory region manager window
src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegionManager/MemoryRegionManagerWindow.cpp

View File

@@ -0,0 +1,81 @@
#include "AnnotationItem.hpp"
#include <QPainter>
#include "ByteItem.hpp"
using namespace Bloom::Widgets;
AnnotationItem::AnnotationItem(std::uint32_t startAddress, std::size_t size, QString labelText, AnnotationItemPosition position)
: QGraphicsItem(nullptr),
startAddress(startAddress),
size(size),
endAddress(static_cast<std::uint32_t>(startAddress + size - 1)),
labelText(std::move(labelText)),
position(position),
width(static_cast<int>((ByteItem::WIDTH + ByteItem::RIGHT_MARGIN) * size - ByteItem::RIGHT_MARGIN)),
height(position == AnnotationItemPosition::TOP ? AnnotationItem::TOP_HEIGHT : AnnotationItem::BOTTOM_HEIGHT) {
this->setAcceptHoverEvents(true);
this->setToolTip(this->labelText);
}
void AnnotationItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
static auto lineColor = this->getLineColor();
static auto labelFontColor = this->getLabelFontColor();
const auto isEnabled = this->isEnabled();
lineColor.setAlpha(isEnabled ? 255 : 100);
labelFontColor.setAlpha(isEnabled ? 255 : 100);
const auto fontMetrics = painter->fontMetrics();
auto labelSize = fontMetrics.size(Qt::TextSingleLine, this->labelText);
if (labelSize.width() > this->width) {
labelSize.setWidth(this->width);
this->labelText = fontMetrics.elidedText(this->labelText, Qt::TextElideMode::ElideRight, this->width);
}
constexpr auto verticalLineLength = 5;
const auto verticalLineYStart = this->position == AnnotationItemPosition::BOTTOM ? 0 : AnnotationItem::TOP_HEIGHT;
const auto verticalLineYEnd = this->position == AnnotationItemPosition::BOTTOM ?
verticalLineLength : AnnotationItem::TOP_HEIGHT - verticalLineLength;
const auto labelRect = QRect(
(this->width - labelSize.width()) / 2,
verticalLineYEnd - (this->position == AnnotationItemPosition::BOTTOM ? -6: labelSize.height() + 6),
labelSize.width(),
labelSize.height()
);
painter->setPen(lineColor);
painter->drawLine(QLine(
ByteItem::WIDTH / 2,
verticalLineYStart,
ByteItem::WIDTH / 2,
verticalLineYEnd
));
painter->drawLine(QLine(
this->width - (ByteItem::WIDTH / 2),
verticalLineYStart,
this->width - (ByteItem::WIDTH / 2),
verticalLineYEnd
));
painter->drawLine(QLine(
ByteItem::WIDTH / 2,
verticalLineYEnd,
(ByteItem::WIDTH / 2) + (this->width - ByteItem::WIDTH),
verticalLineYEnd
));
painter->drawLine(QLine(
this->width / 2,
verticalLineYEnd,
this->width / 2,
(this->position == AnnotationItemPosition::BOTTOM ? verticalLineYEnd + 4 : verticalLineYEnd - 4)
));
painter->setPen(labelFontColor);
painter->drawText(labelRect, Qt::AlignCenter, this->labelText);
}

View File

@@ -0,0 +1,51 @@
#pragma once
#include <QGraphicsItem>
#include <cstdint>
namespace Bloom::Widgets
{
enum class AnnotationItemPosition: std::uint8_t
{
TOP,
BOTTOM,
};
class AnnotationItem: public QGraphicsItem
{
public:
static constexpr int TOP_HEIGHT = 26;
static constexpr int BOTTOM_HEIGHT = 26;
const int width;
const int height;
const std::uint32_t startAddress;
const std::uint32_t endAddress;
AnnotationItemPosition position = AnnotationItemPosition::TOP;
AnnotationItem(
std::uint32_t startAddress,
std::size_t size,
QString labelText,
AnnotationItemPosition position
);
[[nodiscard]] QRectF boundingRect() const override {
return QRectF(0, 0, this->width, this->height);
}
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
protected:
[[nodiscard]] QColor getLineColor() const {
return QColor(0x4F, 0x4F, 0x4F);
}
[[nodiscard]] QColor getLabelFontColor() const {
return QColor(0x8A, 0x8A, 0x8D);
}
private:
std::size_t size = 0;
QString labelText;
};
}

View File

@@ -3,20 +3,20 @@
#include <QPainter>
#include <QStyle>
#include "src/Logger/Logger.hpp"
using namespace Bloom::Widgets;
ByteItem::ByteItem(
std::size_t byteIndex,
std::uint32_t address,
std::optional<ByteItem*>& hoveredByteItem,
std::optional<AnnotationItem*>& hoveredAnnotationItem,
const HexViewerWidgetSettings& settings
):
QGraphicsItem(nullptr),
byteIndex(byteIndex),
address(address),
hoveredByteItem(hoveredByteItem),
hoveredAnnotationItem(hoveredAnnotationItem),
settings(settings)
{
this->setCacheMode(
@@ -51,25 +51,40 @@ void ByteItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
static const auto valueChangedTextColor = QColor(0x54, 0x7F, 0xBA);
static auto font = QFont("'Ubuntu', sans-serif");
static const auto stackMemoryBackgroundColor = QColor(0x5E, 0x50, 0x27, 255);
static const auto focusedRegionBackgroundColor = QColor(0x25, 0x5A, 0x49, 210);
static const auto stackMemoryBackgroundColor = QColor(0x67, 0x57, 0x20, 210);
static const auto hoveredBackgroundColor = QColor(0x8E, 0x8B, 0x83, 70);
static const auto hoveredNeighbourBackgroundColor = QColor(0x8E, 0x8B, 0x83, 30);
static const auto hoveredAnnotationBackgroundColor = QColor(0x8E, 0x8B, 0x83, 50);
const auto isEnabled = this->isEnabled();
auto textColor = this->valueChanged ? valueChangedTextColor : standardTextColor;
auto backgroundColor = std::optional<QColor>();
font.setPixelSize(11);
painter->setFont(font);
if (this->settings.highlightStackMemory && this->settings.stackPointerAddress.has_value()
if (this->settings.highlightFocusedMemory && this->focusedMemoryRegion != nullptr) {
// This byte is within a focused region
backgroundColor = focusedRegionBackgroundColor;
} else if (this->settings.highlightStackMemory && this->settings.stackPointerAddress.has_value()
&& this->address > this->settings.stackPointerAddress
) {
// This byte is within the stack memory
painter->setBrush(stackMemoryBackgroundColor);
painter->drawRect(widgetRect);
backgroundColor = stackMemoryBackgroundColor;
}
const auto* hoveredByteItem = this->hoveredByteItem.value_or(nullptr);
const auto* hoveredAnnotationItem = this->hoveredAnnotationItem.value_or(nullptr);
if (hoveredByteItem != nullptr) {
if (hoveredByteItem == this) {
painter->setBrush(hoveredBackgroundColor);
if (backgroundColor.has_value()) {
backgroundColor->setAlpha(255);
} else {
backgroundColor = hoveredBackgroundColor;
}
} else if (this->settings.highlightHoveredRowAndCol
&& (
@@ -77,16 +92,34 @@ void ByteItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option,
|| hoveredByteItem->currentRowIndex == this->currentRowIndex
)
) {
painter->setBrush(hoveredNeighbourBackgroundColor);
if (backgroundColor.has_value()) {
backgroundColor->setAlpha(220);
} else {
backgroundColor = hoveredNeighbourBackgroundColor;
}
}
} else if (
!this->settings.highlightFocusedMemory
&& hoveredAnnotationItem != nullptr
&& this->address >= hoveredAnnotationItem->startAddress
&& this->address <= hoveredAnnotationItem->endAddress
) {
backgroundColor = hoveredAnnotationBackgroundColor;
}
if (backgroundColor.has_value()) {
if (!isEnabled) {
backgroundColor->setAlpha(100);
}
painter->setBrush(backgroundColor.value());
painter->drawRect(widgetRect);
}
auto textColor = this->valueChanged ? valueChangedTextColor : standardTextColor;
if (this->valueInitialised) {
if (!this->isEnabled()) {
if (this->valueInitialised && this->excludedMemoryRegion == nullptr) {
if (!isEnabled) {
textColor.setAlpha(100);
}

View File

@@ -5,8 +5,11 @@
#include <QGraphicsItem>
#include <optional>
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/ClickableWidget.hpp"
#include "HexViewerWidgetSettings.hpp"
#include "AnnotationItem.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/ExcludedMemoryRegion.hpp"
namespace Bloom::Widgets
{
@@ -28,10 +31,14 @@ namespace Bloom::Widgets
std::size_t currentRowIndex = 0;
std::size_t currentColumnIndex = 0;
const FocusedMemoryRegion* focusedMemoryRegion = nullptr;
const ExcludedMemoryRegion* excludedMemoryRegion = nullptr;
ByteItem(
std::size_t byteIndex,
std::uint32_t address,
std::optional<ByteItem*>& hoveredByteItem,
std::optional<AnnotationItem*>& hoveredAnnotationItem,
const HexViewerWidgetSettings& settings
);
@@ -58,5 +65,6 @@ namespace Bloom::Widgets
std::optional<QString> asciiValue;
std::optional<ByteItem*>& hoveredByteItem;
std::optional<AnnotationItem*>& hoveredAnnotationItem;
};
}

View File

@@ -1,20 +1,13 @@
#include "ByteItemContainerGraphicsView.hpp"
#include <QVBoxLayout>
#include <QTableWidget>
#include <QScrollBar>
#include <QPainter>
#include <cmath>
#include "src/Logger/Logger.hpp"
using namespace Bloom::Widgets;
using namespace Bloom::Exceptions;
using Bloom::Targets::TargetMemoryDescriptor;
ByteItemContainerGraphicsView::ByteItemContainerGraphicsView(
const TargetMemoryDescriptor& targetMemoryDescriptor,
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
InsightWorker& insightWorker,
const HexViewerWidgetSettings& settings,
QLabel* hoveredAddressLabel,
@@ -29,6 +22,8 @@ ByteItemContainerGraphicsView::ByteItemContainerGraphicsView(
this->scene = new ByteItemGraphicsScene(
targetMemoryDescriptor,
focusedMemoryRegions,
excludedMemoryRegions,
insightWorker,
settings,
hoveredAddressLabel,
@@ -49,6 +44,6 @@ bool ByteItemContainerGraphicsView::event(QEvent* event) {
}
void ByteItemContainerGraphicsView::resizeEvent(QResizeEvent* event) {
this->scene->adjustByteWidgets();
QGraphicsView::resizeEvent(event);
this->scene->adjustSize();
}

View File

@@ -3,18 +3,10 @@
#include <QGraphicsView>
#include <QWidget>
#include <QLabel>
#include <QToolButton>
#include <QVBoxLayout>
#include <map>
#include <vector>
#include <QSize>
#include <QString>
#include <QEvent>
#include <QGraphicsSceneMouseEvent>
#include <optional>
#include "src/Targets/TargetMemory.hpp"
#include "src/Targets/TargetState.hpp"
#include "src/Insight/InsightWorker/InsightWorker.hpp"
@@ -30,6 +22,8 @@ namespace Bloom::Widgets
public:
ByteItemContainerGraphicsView(
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
InsightWorker& insightWorker,
const HexViewerWidgetSettings& settings,
QLabel* hoveredAddressLabel,

View File

@@ -1,13 +1,7 @@
#include "ByteItemGraphicsScene.hpp"
#include <QVBoxLayout>
#include <QTableWidget>
#include <QScrollBar>
#include <QPainter>
#include <cmath>
#include "src/Logger/Logger.hpp"
using namespace Bloom::Widgets;
using namespace Bloom::Exceptions;
@@ -15,33 +9,34 @@ using Bloom::Targets::TargetMemoryDescriptor;
ByteItemGraphicsScene::ByteItemGraphicsScene(
const TargetMemoryDescriptor& targetMemoryDescriptor,
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
InsightWorker& insightWorker,
const HexViewerWidgetSettings& settings,
QLabel* hoveredAddressLabel,
QWidget* parent
): QGraphicsScene(parent),
targetMemoryDescriptor(targetMemoryDescriptor),
insightWorker(insightWorker),
settings(settings),
hoveredAddressLabel(hoveredAddressLabel),
parent(parent) {
QGraphicsView* parent
):
QGraphicsScene(parent),
targetMemoryDescriptor(targetMemoryDescriptor),
focusedMemoryRegions(focusedMemoryRegions),
excludedMemoryRegions(excludedMemoryRegions),
insightWorker(insightWorker),
settings(settings),
hoveredAddressLabel(hoveredAddressLabel),
parent(parent)
{
this->setObjectName("byte-widget-container");
this->byteAddressContainer = new ByteAddressContainer();
this->addItem(this->byteAddressContainer);
/*
* Construct ByteWidget objects
*
* No need to position them here - the subsequent call to resizeEvent() will do that.
*/
// Construct ByteWidget objects
const auto memorySize = this->targetMemoryDescriptor.size();
const auto startAddress = this->targetMemoryDescriptor.addressRange.startAddress;
Logger::error("Constructing bytes begin");
for (std::uint32_t i = 0; i < memorySize; i++) {
const auto address = startAddress + i;
auto* byteWidget = new ByteItem(i, address, this->hoveredByteWidget, settings);
auto* byteWidget = new ByteItem(i, address, this->hoveredByteWidget, this->hoveredAnnotationItem, settings);
this->byteItemsByAddress.insert(std::pair(
address,
byteWidget
@@ -49,8 +44,6 @@ parent(parent) {
this->addItem(byteWidget);
}
Logger::error("Constructing bytes end");
this->adjustByteWidgets();
QObject::connect(
&insightWorker,
@@ -58,6 +51,9 @@ parent(parent) {
this,
&ByteItemGraphicsScene::onTargetStateChanged
);
this->refreshRegions();
this->adjustSize();
}
void ByteItemGraphicsScene::updateValues(const Targets::TargetMemoryBuffer& buffer) {
@@ -67,61 +63,101 @@ void ByteItemGraphicsScene::updateValues(const Targets::TargetMemoryBuffer& buff
}
}
void ByteItemGraphicsScene::adjustByteWidgets() {
const auto margins = QMargins(10, 10, 10, 10);
const auto width = std::max(600, static_cast<int>(this->parent->width()));
void ByteItemGraphicsScene::refreshRegions() {
for (auto& [byteAddress, byteWidget] : this->byteItemsByAddress) {
byteWidget->focusedMemoryRegion = nullptr;
byteWidget->excludedMemoryRegion = nullptr;
constexpr auto byteWidgetWidth = ByteItem::WIDTH + ByteItem::RIGHT_MARGIN;
constexpr auto byteWidgetHeight = ByteItem::HEIGHT + ByteItem::BOTTOM_MARGIN;
const auto rowCapacity = static_cast<std::size_t>(
std::floor((width - margins.left() - margins.right() - ByteAddressContainer::WIDTH) / byteWidgetWidth)
);
const auto rowCount = static_cast<int>(
std::ceil(static_cast<double>(this->byteItemsByAddress.size()) / static_cast<double>(rowCapacity))
);
for (const auto& focusedRegion : this->focusedMemoryRegions) {
const auto addressRange = focusedRegion.getAbsoluteAddressRange();
if (byteAddress >= addressRange.startAddress && byteAddress <= addressRange.endAddress) {
byteWidget->focusedMemoryRegion = &focusedRegion;
break;
}
}
this->setSceneRect(
0,
0,
width,
std::max(((rowCount * byteWidgetHeight) + margins.top() + margins.bottom()), this->parent->height())
);
// Don't bother recalculating the byte item positions if the number of rows & columns have not changed.
if (rowCount == this->byteItemsByRowIndex.size() && rowCapacity == this->byteItemsByColumnIndex.size()) {
return;
}
std::map<std::size_t, std::vector<ByteItem*>> byteWidgetsByRowIndex;
std::map<std::size_t, std::vector<ByteItem*>> byteWidgetsByColumnIndex;
for (auto& [address, byteWidget] : this->byteItemsByAddress) {
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->setPos(
static_cast<int>(columnIndex * byteWidgetWidth + margins.left() + ByteAddressContainer::WIDTH),
static_cast<int>(rowIndex * byteWidgetHeight + static_cast<std::size_t>(margins.top()))
);
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);
for (const auto& excludedRegion : this->excludedMemoryRegions) {
const auto addressRange = excludedRegion.getAbsoluteAddressRange();
if (byteAddress >= addressRange.startAddress && byteAddress <= addressRange.endAddress) {
byteWidget->excludedMemoryRegion = &excludedRegion;
break;
}
}
byteWidget->update();
}
this->byteItemsByRowIndex = std::move(byteWidgetsByRowIndex);
this->byteItemsByColumnIndex = std::move(byteWidgetsByColumnIndex);
// Refresh annotation items
for (auto [startAddress, annotationItem] : this->annotationItemsByStartAddress) {
this->removeItem(annotationItem);
delete annotationItem;
}
this->byteAddressContainer->adjustAddressLabels(this->byteItemsByRowIndex);
this->annotationItemsByStartAddress.clear();
for (const auto& focusedRegion : this->focusedMemoryRegions) {
const auto addressRange = focusedRegion.getAbsoluteAddressRange();
auto* annotationItem = new AnnotationItem(
addressRange.startAddress,
addressRange.endAddress - addressRange.startAddress + 1,
focusedRegion.name,
AnnotationItemPosition::BOTTOM
);
this->addItem(annotationItem);
this->annotationItemsByStartAddress.insert(std::pair(addressRange.startAddress, annotationItem));
}
this->adjustSize(true);
}
void ByteItemGraphicsScene::adjustSize(bool forced) {
const auto width = this->getSceneWidth();
const auto columnCount = static_cast<std::size_t>(
std::floor(
(width - this->margins.left() - this->margins.right() - ByteAddressContainer::WIDTH + ByteItem::RIGHT_MARGIN)
/ (ByteItem::WIDTH + ByteItem::RIGHT_MARGIN)
)
);
const auto rowCount = static_cast<int>(
std::ceil(static_cast<double>(this->byteItemsByAddress.size()) / static_cast<double>(columnCount))
);
// Don't bother recalculating the byte item & annotation positions if the number of rows & columns have not changed.
if (this->byteItemsByAddress.empty()
|| (
!forced
&& rowCount == this->byteItemsByRowIndex.size()
&& columnCount == this->byteItemsByColumnIndex.size()
)
) {
this->setSceneRect(
0,
0,
width,
std::max(static_cast<int>(this->sceneRect().height()), this->parent->viewport()->height())
);
return;
}
if (!this->byteItemsByAddress.empty()) {
this->adjustByteItemPositions();
this->adjustAnnotationItemPositions();
const auto lastByteItemPosition = (--this->byteItemsByAddress.end())->second->pos();
this->setSceneRect(
0,
0,
width,
std::max(
static_cast<int>(lastByteItemPosition.y() + ByteItem::HEIGHT + this->margins.bottom()),
this->parent->height()
)
);
}
this->update();
}
void ByteItemGraphicsScene::setEnabled(bool enabled) {
@@ -132,11 +168,178 @@ void ByteItemGraphicsScene::setEnabled(bool enabled) {
byteItem->setEnabled(this->enabled);
}
for (auto& [startAddress, annotationItem] : this->annotationItemsByStartAddress) {
annotationItem->setEnabled(this->enabled);
}
this->byteAddressContainer->setEnabled(enabled);
this->byteAddressContainer->update();
this->update();
}
}
void ByteItemGraphicsScene::invalidateChildItemCaches() {
for (auto& [address, byteWidget] : this->byteItemsByAddress) {
byteWidget->update();
}
for (auto& [startAddress, annotationItem] : this->annotationItemsByStartAddress) {
annotationItem->update();
}
}
bool ByteItemGraphicsScene::event(QEvent* event) {
if (event->type() == QEvent::Type::GraphicsSceneLeave && this->hoveredByteWidget.has_value()) {
this->onByteWidgetLeave();
}
return QGraphicsScene::event(event);
}
void ByteItemGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent) {
auto hoveredItems = this->items(mouseEvent->scenePos());
ByteItem* hoveredByteItem = nullptr;
AnnotationItem* hoveredAnnotationItem = nullptr;
if (!hoveredItems.empty()) {
hoveredByteItem = dynamic_cast<ByteItem*>(hoveredItems.at(0));
hoveredAnnotationItem = dynamic_cast<AnnotationItem*>(hoveredItems.at(0));
}
if (hoveredByteItem != nullptr) {
this->onByteWidgetEnter(hoveredByteItem);
return;
}
if (this->hoveredByteWidget.has_value()) {
this->onByteWidgetLeave();
}
if (hoveredAnnotationItem != nullptr) {
this->onAnnotationItemEnter(hoveredAnnotationItem);
return;
}
if (this->hoveredAnnotationItem.has_value()) {
this->onAnnotationItemLeave();
}
}
void ByteItemGraphicsScene::adjustByteItemPositions() {
const auto columnCount = static_cast<std::size_t>(
std::floor(
(this->getSceneWidth() - this->margins.left() - this->margins.right() - ByteAddressContainer::WIDTH + ByteItem::RIGHT_MARGIN)
/ (ByteItem::WIDTH + ByteItem::RIGHT_MARGIN)
)
);
std::map<std::size_t, std::vector<ByteItem*>> byteWidgetsByRowIndex;
std::map<std::size_t, std::vector<ByteItem*>> byteWidgetsByColumnIndex;
auto rowIndicesWithTopAnnotations = std::set<std::size_t>();
auto rowIndicesWithBottomAnnotations = std::set<std::size_t>();
const auto& memoryAddressRange = this->targetMemoryDescriptor.addressRange;
for (auto [startAddress, annotationItem] : this->annotationItemsByStartAddress) {
const auto firstByteRowIndex = static_cast<std::size_t>(
std::ceil(static_cast<double>((startAddress - memoryAddressRange.startAddress) + 1)
/ static_cast<double>(columnCount)) - 1
);
const auto lastByteRowIndex = static_cast<std::size_t>(
std::ceil(static_cast<double>((annotationItem->endAddress - memoryAddressRange.startAddress) + 1)
/ static_cast<double>(columnCount)) - 1
);
// We only display annotations that span a single row.
if (firstByteRowIndex == lastByteRowIndex) {
annotationItem->show();
rowIndicesWithBottomAnnotations.insert(firstByteRowIndex);
} else {
annotationItem->hide();
}
}
constexpr auto annotationTopHeight = AnnotationItem::TOP_HEIGHT;
constexpr auto annotationBottomHeight = AnnotationItem::BOTTOM_HEIGHT;
std::size_t lastRowIndex = 0;
int rowYPosition = margins.top();
auto currentRowAnnotatedTop = false;
for (auto& [address, byteWidget] : this->byteItemsByAddress) {
const auto rowIndex = static_cast<std::size_t>(
std::ceil(static_cast<double>(byteWidget->byteIndex + 1) / static_cast<double>(columnCount)) - 1
);
const auto columnIndex = static_cast<std::size_t>(
static_cast<double>(byteWidget->byteIndex)
- (std::floor(byteWidget->byteIndex / columnCount) * static_cast<double>(columnCount))
);
if (rowIndex != lastRowIndex) {
rowYPosition += ByteItem::HEIGHT + ByteItem::BOTTOM_MARGIN;
currentRowAnnotatedTop = false;
if (rowIndicesWithBottomAnnotations.contains(lastRowIndex)) {
rowYPosition += annotationBottomHeight;
}
}
if (!currentRowAnnotatedTop && rowIndicesWithTopAnnotations.contains(rowIndex)) {
rowYPosition += annotationTopHeight;
currentRowAnnotatedTop = true;
}
byteWidget->setPos(
static_cast<int>(
columnIndex * (ByteItem::WIDTH + ByteItem::RIGHT_MARGIN) + this->margins.left() + ByteAddressContainer::WIDTH),
rowYPosition
);
byteWidget->currentRowIndex = rowIndex;
byteWidget->currentColumnIndex = columnIndex;
byteWidgetsByRowIndex[byteWidget->currentRowIndex].emplace_back(byteWidget);
byteWidgetsByColumnIndex[byteWidget->currentColumnIndex].emplace_back(byteWidget);
lastRowIndex = rowIndex;
}
this->byteItemsByRowIndex = std::move(byteWidgetsByRowIndex);
this->byteItemsByColumnIndex = std::move(byteWidgetsByColumnIndex);
this->byteAddressContainer->adjustAddressLabels(this->byteItemsByRowIndex);
}
void ByteItemGraphicsScene::adjustAnnotationItemPositions() {
if (this->byteItemsByAddress.empty()) {
return;
}
for (auto& [startAddress, annotationItem] : this->annotationItemsByStartAddress) {
if (!this->byteItemsByAddress.contains(startAddress)) {
annotationItem->hide();
continue;
}
const auto firstByteItemPosition = this->byteItemsByAddress.at(startAddress)->pos();
if (annotationItem->position == AnnotationItemPosition::TOP) {
annotationItem->setPos(
firstByteItemPosition.x(),
firstByteItemPosition.y() - AnnotationItem::TOP_HEIGHT
);
} else if (annotationItem->position == AnnotationItemPosition::BOTTOM) {
annotationItem->setPos(
firstByteItemPosition.x(),
firstByteItemPosition.y() + ByteItem::HEIGHT
);
}
}
}
void ByteItemGraphicsScene::onTargetStateChanged(Targets::TargetState newState) {
using Targets::TargetState;
this->targetState = newState;
@@ -147,10 +350,9 @@ void ByteItemGraphicsScene::onByteWidgetEnter(ByteItem* widget) {
if (this->hoveredByteWidget.value() == widget) {
// This byte item is already marked as hovered
return;
} else {
this->onByteWidgetLeave();
}
this->onByteWidgetLeave();
}
this->hoveredByteWidget = widget;
@@ -174,7 +376,7 @@ void ByteItemGraphicsScene::onByteWidgetEnter(ByteItem* widget) {
}
void ByteItemGraphicsScene::onByteWidgetLeave() {
const auto byteItem = this->hoveredByteWidget.value();
auto* byteItem = this->hoveredByteWidget.value();
this->hoveredByteWidget = std::nullopt;
this->hoveredAddressLabel->setText("Relative Address (Absolute Address):");
@@ -193,24 +395,35 @@ void ByteItemGraphicsScene::onByteWidgetLeave() {
}
}
bool ByteItemGraphicsScene::event(QEvent* event) {
if (event->type() == QEvent::Type::GraphicsSceneLeave && this->hoveredByteWidget.has_value()) {
this->onByteWidgetLeave();
}
return QGraphicsScene::event(event);
}
void ByteItemGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent) {
auto hoveredItems = this->items(mouseEvent->scenePos());
if (!hoveredItems.empty()) {
auto hoveredByteWidget = dynamic_cast<ByteItem*>(hoveredItems.at(0));
if (hoveredByteWidget != nullptr) {
this->onByteWidgetEnter(hoveredByteWidget);
void ByteItemGraphicsScene::onAnnotationItemEnter(AnnotationItem* annotationItem) {
if (this->hoveredAnnotationItem.has_value()) {
if (this->hoveredAnnotationItem.value() == annotationItem) {
return;
}
} else if (this->hoveredByteWidget.has_value()) {
this->onByteWidgetLeave();
this->onAnnotationItemLeave();
}
this->hoveredAnnotationItem = annotationItem;
for (
auto byteItemAddress = annotationItem->startAddress;
byteItemAddress <= annotationItem->endAddress;
byteItemAddress++
) {
this->byteItemsByAddress.at(byteItemAddress)->update();
}
}
void ByteItemGraphicsScene::onAnnotationItemLeave() {
auto* annotationItem = this->hoveredAnnotationItem.value();
this->hoveredAnnotationItem = std::nullopt;
for (
auto byteItemAddress = annotationItem->startAddress;
byteItemAddress <= annotationItem->endAddress;
byteItemAddress++
) {
this->byteItemsByAddress.at(byteItemAddress)->update();
}
}

View File

@@ -1,15 +1,19 @@
#pragma once
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QScrollBar>
#include <QWidget>
#include <QLabel>
#include <QToolButton>
#include <QVBoxLayout>
#include <map>
#include <algorithm>
#include <vector>
#include <QSize>
#include <QString>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneWheelEvent>
#include <optional>
#include "src/Targets/TargetMemory.hpp"
@@ -19,8 +23,13 @@
#include "ByteItem.hpp"
#include "ByteAddressContainer.hpp"
#include "AnnotationItem.hpp"
#include "HexViewerWidgetSettings.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegion.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/ExcludedMemoryRegion.hpp"
namespace Bloom::Widgets
{
class ByteItemGraphicsScene: public QGraphicsScene
@@ -29,24 +38,23 @@ namespace Bloom::Widgets
public:
std::optional<ByteItem*> hoveredByteWidget;
std::map<std::uint32_t, ByteItem*> byteItemsByAddress;
std::map<std::size_t, std::vector<ByteItem*>> byteItemsByRowIndex;
std::map<std::size_t, std::vector<ByteItem*>> byteItemsByColumnIndex;
std::optional<AnnotationItem*> hoveredAnnotationItem;
ByteItemGraphicsScene(
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
InsightWorker& insightWorker,
const HexViewerWidgetSettings& settings,
QLabel* hoveredAddressLabel,
QWidget* parent
QGraphicsView* parent
);
void updateValues(const Targets::TargetMemoryBuffer& buffer);
void adjustByteWidgets();
void refreshRegions();
void adjustSize(bool forced = false);
void setEnabled(bool enabled);
void invalidateChildItemCaches();
signals:
void byteWidgetsAdjusted();
@@ -57,21 +65,43 @@ namespace Bloom::Widgets
private:
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor;
std::vector<FocusedMemoryRegion>& focusedMemoryRegions;
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions;
std::map<std::uint32_t, ByteItem*> byteItemsByAddress;
std::map<std::uint32_t, AnnotationItem*> annotationItemsByStartAddress;
std::map<std::size_t, std::vector<ByteItem*>> byteItemsByRowIndex;
std::map<std::size_t, std::vector<ByteItem*>> byteItemsByColumnIndex;
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
InsightWorker& insightWorker;
const QMargins margins = QMargins(10, 10, 10, 10);
const HexViewerWidgetSettings& settings;
QWidget* parent = nullptr;
QGraphicsView* parent = nullptr;
QLabel* hoveredAddressLabel = nullptr;
ByteAddressContainer* byteAddressContainer = nullptr;
bool enabled = true;
private slots:
int getSceneWidth() {
/*
* Minus 2 for the QSS margin on the vertical scrollbar (which isn't accounted for during viewport
* size calculation).
*
* See https://bugreports.qt.io/browse/QTBUG-99189 for more on this.
*/
return std::max(this->parent->viewport()->width(), 400) - 2;
}
void adjustByteItemPositions();
void adjustAnnotationItemPositions();
void onTargetStateChanged(Targets::TargetState newState);
void onByteWidgetEnter(Bloom::Widgets::ByteItem* widget);
void onByteWidgetLeave();
void onAnnotationItemEnter(Bloom::Widgets::AnnotationItem* annotationItem);
void onAnnotationItemLeave();
};
}

View File

@@ -1,16 +1,9 @@
#include "HexViewerWidget.hpp"
#include <QVBoxLayout>
#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"
#include "src/Logger/Logger.hpp"
using namespace Bloom::Widgets;
using namespace Bloom::Exceptions;
@@ -19,9 +12,17 @@ using Bloom::Targets::TargetMemoryDescriptor;
HexViewerWidget::HexViewerWidget(
const TargetMemoryDescriptor& targetMemoryDescriptor,
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
InsightWorker& insightWorker,
QWidget* parent
): QWidget(parent), targetMemoryDescriptor(targetMemoryDescriptor), insightWorker(insightWorker) {
):
QWidget(parent),
targetMemoryDescriptor(targetMemoryDescriptor),
focusedMemoryRegions(focusedMemoryRegions),
excludedMemoryRegions(excludedMemoryRegions),
insightWorker(insightWorker)
{
this->setObjectName("hex-viewer-widget");
this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
@@ -63,8 +64,10 @@ HexViewerWidget::HexViewerWidget(
this->byteItemGraphicsViewContainer = this->container->findChild<QWidget*>("graphics-view-container");
this->byteItemGraphicsView = new ByteItemContainerGraphicsView(
targetMemoryDescriptor,
insightWorker,
this->targetMemoryDescriptor,
this->focusedMemoryRegions,
this->excludedMemoryRegions,
this->insightWorker,
this->settings,
this->hoveredAddressLabel,
this->byteItemGraphicsViewContainer
@@ -116,9 +119,13 @@ void HexViewerWidget::updateValues(const Targets::TargetMemoryBuffer& buffer) {
this->byteItemGraphicsScene->updateValues(buffer);
}
void HexViewerWidget::refreshRegions() {
this->byteItemGraphicsScene->refreshRegions();
}
void HexViewerWidget::setStackPointer(std::uint32_t stackPointer) {
this->settings.stackPointerAddress = stackPointer;
this->byteItemGraphicsScene->update();
this->byteItemGraphicsScene->invalidateChildItemCaches();
}
void HexViewerWidget::resizeEvent(QResizeEvent* event) {
@@ -143,19 +150,19 @@ void HexViewerWidget::setStackMemoryHighlightingEnabled(bool enabled) {
this->highlightStackMemoryButton->setChecked(enabled);
this->settings.highlightStackMemory = enabled;
this->byteItemGraphicsScene->update();
this->byteItemGraphicsScene->invalidateChildItemCaches();
}
void HexViewerWidget::setHoveredRowAndColumnHighlightingEnabled(bool enabled) {
this->highlightHoveredRowAndColumnButton->setChecked(enabled);
this->settings.highlightHoveredRowAndCol = enabled;
this->byteItemGraphicsScene->update();
this->byteItemGraphicsScene->invalidateChildItemCaches();
}
void HexViewerWidget::setFocusedMemoryHighlightingEnabled(bool enabled) {
this->highlightFocusedMemoryButton->setChecked(enabled);
this->settings.highlightFocusedMemory = enabled;
this->byteItemGraphicsScene->update();
this->byteItemGraphicsScene->invalidateChildItemCaches();
}

View File

@@ -2,25 +2,23 @@
#include <QWidget>
#include <QLabel>
#include <QGraphicsView>
#include <QVBoxLayout>
#include <set>
#include <map>
#include <QSize>
#include <QString>
#include <QEvent>
#include <QResizeEvent>
#include <QShowEvent>
#include <optional>
#include <vector>
#include "src/Targets/TargetMemory.hpp"
#include "src/Targets/TargetState.hpp"
#include "src/Insight/InsightWorker/InsightWorker.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/SvgToolButton.hpp"
#include "HexViewerWidgetSettings.hpp"
#include "ByteItemContainerGraphicsView.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/SvgToolButton.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegion.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/ExcludedMemoryRegion.hpp"
namespace Bloom::Widgets
{
@@ -33,12 +31,14 @@ namespace Bloom::Widgets
HexViewerWidget(
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
InsightWorker& insightWorker,
QWidget* parent
);
void updateValues(const Targets::TargetMemoryBuffer& buffer);
void refreshRegions();
void setStackPointer(std::uint32_t stackPointer);
protected:
@@ -47,6 +47,9 @@ namespace Bloom::Widgets
private:
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor;
std::vector<FocusedMemoryRegion>& focusedMemoryRegions;
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions;
InsightWorker& insightWorker;
HexViewerWidgetSettings settings = HexViewerWidgetSettings();
@@ -66,7 +69,6 @@ namespace Bloom::Widgets
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
private slots:
void onTargetStateChanged(Targets::TargetState newState);
void setStackMemoryHighlightingEnabled(bool enabled);
void setHoveredRowAndColumnHighlightingEnabled(bool enabled);

View File

@@ -382,6 +382,7 @@ void MemoryRegionManagerWindow::applyChanges() {
this->focusedMemoryRegions = std::move(processedFocusedMemoryRegions);
this->excludedMemoryRegions = std::move(processedExcludedMemoryRegions);
this->close();
emit this->changesApplied();
}
void MemoryRegionManagerWindow::openHelpPage() {

View File

@@ -37,6 +37,9 @@ namespace Bloom::Widgets
void refreshRegions();
signals:
void changesApplied();
protected:
void showEvent(QShowEvent* event) override;

View File

@@ -49,7 +49,13 @@ TargetMemoryInspectionPane::TargetMemoryInspectionPane(
auto* subContainerLayout = this->container->findChild<QHBoxLayout*>("sub-container-layout");
this->manageMemoryRegionsButton = this->container->findChild<SvgToolButton*>("manage-memory-regions-btn");
this->hexViewerWidget = new HexViewerWidget(this->targetMemoryDescriptor, this->insightWorker, this);
this->hexViewerWidget = new HexViewerWidget(
this->targetMemoryDescriptor,
this->focusedMemoryRegions,
this->excludedMemoryRegions,
this->insightWorker,
this
);
this->hexViewerWidget->setDisabled(true);
subContainerLayout->addWidget(this->hexViewerWidget);
@@ -187,6 +193,13 @@ void TargetMemoryInspectionPane::openMemoryRegionManagerWindow() {
this->excludedMemoryRegions,
this
);
QObject::connect(
this->memoryRegionManagerWindow,
&MemoryRegionManagerWindow::changesApplied,
this,
&TargetMemoryInspectionPane::onMemoryRegionsChange
);
}
if (!this->memoryRegionManagerWindow->isVisible()) {
@@ -197,3 +210,7 @@ void TargetMemoryInspectionPane::openMemoryRegionManagerWindow() {
this->memoryRegionManagerWindow->activateWindow();
}
}
void TargetMemoryInspectionPane::onMemoryRegionsChange() {
this->hexViewerWidget->refreshRegions();
}

View File

@@ -64,5 +64,6 @@ namespace Bloom::Widgets
void onTargetStateChanged(Targets::TargetState newState);
void onMemoryRead(const Targets::TargetMemoryBuffer& buffer);
void openMemoryRegionManagerWindow();
void onMemoryRegionsChange();
};
}