Refactored hex viewer in preparation for snapshot viewer and diffs.
Before this refactor, the hex viewer was consuming far too much RAM and wasn't scaling very well. It can now handle data inspection up to 5MB (way more than we need), comfortably
This commit is contained in:
@@ -31,7 +31,7 @@ target_sources(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/ReadProgramCounter.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/GetTargetState.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/GetTargetDescriptor.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/ConstructHexViewerByteItems.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/ConstructHexViewerTopLevelGroupItem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/CaptureMemorySnapshot.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/InsightWorker/Tasks/RetrieveMemorySnapshots.cpp
|
||||
|
||||
@@ -71,13 +71,14 @@ target_sources(
|
||||
# Target memory inspection pane
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemContainerGraphicsView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemGraphicsScene.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsView.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsScene.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GroupItem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/TopLevelGroupItem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/FocusedRegionGroupItem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/AnnotationItem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ValueAnnotationItem.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegion.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/ExcludedMemoryRegion.cpp
|
||||
@@ -85,7 +86,6 @@ target_sources(
|
||||
${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
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/SnapshotManager/SnapshotViewer/SnapshotViewer.cpp
|
||||
|
||||
# Memory region manager window
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegionManager/MemoryRegionManagerWindow.cpp
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
#include "ConstructHexViewerByteItems.hpp"
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
namespace Bloom
|
||||
{
|
||||
ConstructHexViewerByteItems::ConstructHexViewerByteItems(
|
||||
const Targets::TargetMemoryDescriptor& memoryDescriptor,
|
||||
std::optional<Targets::TargetStackPointer>& currentStackPointer,
|
||||
Widgets::ByteItem** hoveredByteItem,
|
||||
std::set<Widgets::ByteItem*>& highlightedByteItems,
|
||||
Widgets::HexViewerWidgetSettings& settings
|
||||
)
|
||||
: memoryDescriptor(memoryDescriptor)
|
||||
, currentStackPointer(currentStackPointer)
|
||||
, hoveredByteItem(hoveredByteItem)
|
||||
, highlightedByteItems(highlightedByteItems)
|
||||
, settings(settings)
|
||||
{
|
||||
qRegisterMetaType<std::map<Targets::TargetMemoryAddress, Widgets::ByteItem*>>();
|
||||
}
|
||||
|
||||
void ConstructHexViewerByteItems::run(Services::TargetControllerService&) {
|
||||
const auto memorySize = this->memoryDescriptor.size();
|
||||
const auto startAddress = this->memoryDescriptor.addressRange.startAddress;
|
||||
|
||||
for (Targets::TargetMemorySize i = 0; i < memorySize; i++) {
|
||||
const auto address = startAddress + i;
|
||||
|
||||
this->byteItemsByAddress.emplace(
|
||||
address,
|
||||
new Widgets::ByteItem(
|
||||
i,
|
||||
address,
|
||||
this->currentStackPointer,
|
||||
this->hoveredByteItem,
|
||||
this->highlightedByteItems,
|
||||
settings
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
emit this->byteItems(std::move(this->byteItemsByAddress));
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <set>
|
||||
|
||||
#include "InsightWorkerTask.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidgetSettings.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/ExcludedMemoryRegion.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemGraphicsScene.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp"
|
||||
|
||||
namespace Bloom
|
||||
{
|
||||
class ConstructHexViewerByteItems: public InsightWorkerTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ConstructHexViewerByteItems(
|
||||
const Targets::TargetMemoryDescriptor& memoryDescriptor,
|
||||
std::optional<Targets::TargetStackPointer>& currentStackPointer,
|
||||
Widgets::ByteItem** hoveredByteItem,
|
||||
std::set<Widgets::ByteItem*>& highlightedByteItems,
|
||||
Widgets::HexViewerWidgetSettings& settings
|
||||
);
|
||||
|
||||
TaskGroups getTaskGroups() const override {
|
||||
return TaskGroups();
|
||||
};
|
||||
|
||||
signals:
|
||||
void sceneCreated(Widgets::ByteItemGraphicsScene* scene);
|
||||
void byteItems(std::map<Targets::TargetMemoryAddress, Widgets::ByteItem*> byteItemsByAddress);
|
||||
|
||||
protected:
|
||||
void run(Services::TargetControllerService&) override;
|
||||
|
||||
private:
|
||||
std::map<Targets::TargetMemoryAddress, Widgets::ByteItem*> byteItemsByAddress;
|
||||
|
||||
const Targets::TargetMemoryDescriptor& memoryDescriptor;
|
||||
std::optional<Targets::TargetStackPointer>& currentStackPointer;
|
||||
Widgets::ByteItem** hoveredByteItem;
|
||||
std::set<Widgets::ByteItem*>& highlightedByteItems;
|
||||
Widgets::HexViewerWidgetSettings& settings;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "ConstructHexViewerTopLevelGroupItem.hpp"
|
||||
|
||||
namespace Bloom
|
||||
{
|
||||
ConstructHexViewerTopLevelGroupItem::ConstructHexViewerTopLevelGroupItem(
|
||||
const std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
const Widgets::HexViewerSharedState& hexViewerState
|
||||
)
|
||||
: focusedMemoryRegions(focusedMemoryRegions)
|
||||
, hexViewerState(hexViewerState)
|
||||
{}
|
||||
|
||||
void ConstructHexViewerTopLevelGroupItem::run(Services::TargetControllerService&) {
|
||||
auto* item = new Widgets::TopLevelGroupItem(this->focusedMemoryRegions, this->hexViewerState);
|
||||
item->rebuildItemHierarchy();
|
||||
|
||||
emit this->topLevelGroupItem(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "InsightWorkerTask.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/TopLevelGroupItem.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerSharedState.hpp"
|
||||
|
||||
namespace Bloom
|
||||
{
|
||||
class ConstructHexViewerTopLevelGroupItem: public InsightWorkerTask
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ConstructHexViewerTopLevelGroupItem(
|
||||
const std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
const Widgets::HexViewerSharedState& hexViewerState
|
||||
);
|
||||
|
||||
TaskGroups getTaskGroups() const override {
|
||||
return TaskGroups();
|
||||
};
|
||||
|
||||
signals:
|
||||
void topLevelGroupItem(Widgets::TopLevelGroupItem* item);
|
||||
|
||||
protected:
|
||||
void run(Services::TargetControllerService&) override;
|
||||
|
||||
private:
|
||||
const Widgets::HexViewerSharedState& hexViewerState;
|
||||
const std::vector<FocusedMemoryRegion>& focusedMemoryRegions;
|
||||
};
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
#include "AnnotationItem.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "ByteItem.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
AnnotationItem::AnnotationItem(
|
||||
Targets::TargetMemoryAddress startAddress,
|
||||
Targets::TargetMemorySize size,
|
||||
QString labelText,
|
||||
AnnotationItemPosition position
|
||||
)
|
||||
: QGraphicsItem(nullptr)
|
||||
, startAddress(startAddress)
|
||||
, size(size)
|
||||
, endAddress(static_cast<Targets::TargetMemoryAddress>(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);
|
||||
}
|
||||
|
||||
AnnotationItem::AnnotationItem(
|
||||
const Targets::TargetMemoryAddressRange& addressRange,
|
||||
const QString& labelText,
|
||||
AnnotationItemPosition position
|
||||
)
|
||||
: AnnotationItem(
|
||||
addressRange.startAddress,
|
||||
addressRange.endAddress - addressRange.startAddress + 1,
|
||||
labelText,
|
||||
position
|
||||
)
|
||||
{}
|
||||
|
||||
AnnotationItem::AnnotationItem(const FocusedMemoryRegion& focusedMemoryRegion, AnnotationItemPosition position)
|
||||
: AnnotationItem(
|
||||
focusedMemoryRegion.addressRange,
|
||||
focusedMemoryRegion.name,
|
||||
position
|
||||
)
|
||||
{}
|
||||
|
||||
void AnnotationItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
|
||||
auto lineColor = this->getLineColor();
|
||||
auto labelFontColor = this->getLabelFontColor();
|
||||
|
||||
painter->setFont(this->getLabelFont());
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
const auto verticalLineYStart = this->position == AnnotationItemPosition::BOTTOM ? 0
|
||||
: AnnotationItem::TOP_HEIGHT;
|
||||
const auto verticalLineYEnd = this->position == AnnotationItemPosition::BOTTOM ?
|
||||
AnnotationItem::VERTICAL_LINE_LENGTH : AnnotationItem::TOP_HEIGHT - AnnotationItem::VERTICAL_LINE_LENGTH;
|
||||
|
||||
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);
|
||||
|
||||
if (this->size > 1) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGraphicsItem>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <QFont>
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
|
||||
|
||||
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;
|
||||
static constexpr int VERTICAL_LINE_LENGTH = 5;
|
||||
|
||||
const int width;
|
||||
const int height;
|
||||
const Targets::TargetMemoryAddress startAddress;
|
||||
const Targets::TargetMemoryAddress endAddress;
|
||||
const Targets::TargetMemorySize size;
|
||||
AnnotationItemPosition position = AnnotationItemPosition::TOP;
|
||||
|
||||
AnnotationItem(
|
||||
Targets::TargetMemoryAddress startAddress,
|
||||
Targets::TargetMemorySize size,
|
||||
QString labelText,
|
||||
AnnotationItemPosition position
|
||||
);
|
||||
|
||||
AnnotationItem(
|
||||
const Targets::TargetMemoryAddressRange& addressRange,
|
||||
const QString& labelText,
|
||||
AnnotationItemPosition position
|
||||
);
|
||||
|
||||
AnnotationItem(const FocusedMemoryRegion& focusedMemoryRegion, 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:
|
||||
QString labelText;
|
||||
|
||||
[[nodiscard]] virtual QColor getLineColor() const {
|
||||
return QColor(0x4F, 0x4F, 0x4F);
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual QColor getLabelFontColor() const {
|
||||
return QColor(0x68, 0x68, 0x68);
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual const QFont& getLabelFont() const {
|
||||
static auto labelFont = std::optional<QFont>();
|
||||
|
||||
if (!labelFont.has_value()) {
|
||||
labelFont = QFont("'Ubuntu', sans-serif");
|
||||
labelFont->setPixelSize(12);
|
||||
}
|
||||
|
||||
return labelFont.value();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -2,45 +2,40 @@
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
ByteAddressContainer::ByteAddressContainer(const HexViewerWidgetSettings& settings)
|
||||
: settings(settings)
|
||||
ByteAddressContainer::ByteAddressContainer(const HexViewerSharedState& hexViewerState)
|
||||
: hexViewerState(hexViewerState)
|
||||
{}
|
||||
|
||||
void ByteAddressContainer::adjustAddressLabels(
|
||||
const std::map<std::size_t, std::vector<ByteItem*>>& byteItemsByRowIndex
|
||||
) {
|
||||
void ByteAddressContainer::adjustAddressLabels(const std::vector<const ByteItem*>& firstByteItemByLine) {
|
||||
static constexpr int leftMargin = 10;
|
||||
const auto newRowCount = byteItemsByRowIndex.size();
|
||||
const auto layoutItemMaxIndex = static_cast<int>(this->addressItemsByRowIndex.size() - 1);
|
||||
|
||||
for (const auto& mappingPair : byteItemsByRowIndex) {
|
||||
const auto rowIndex = static_cast<std::size_t>(mappingPair.first);
|
||||
const auto& byteItems = mappingPair.second;
|
||||
const auto addressItemCount = this->addressItems.size();
|
||||
decltype(this->addressItems)::size_type rowIndex = 0;
|
||||
|
||||
ByteAddressItem* addressLabel = nullptr;
|
||||
if (static_cast<int>(rowIndex) > layoutItemMaxIndex) {
|
||||
addressLabel = new ByteAddressItem(rowIndex, byteItemsByRowIndex, this->settings.addressLabelType, this);
|
||||
this->addressItemsByRowIndex.emplace(rowIndex, addressLabel);
|
||||
|
||||
} else {
|
||||
addressLabel = this->addressItemsByRowIndex.at(rowIndex);
|
||||
addressLabel->update();
|
||||
addressLabel->setVisible(true);
|
||||
}
|
||||
|
||||
const auto& firstByteItem = byteItems.front();
|
||||
addressLabel->setPos(
|
||||
leftMargin,
|
||||
firstByteItem->pos().y() + 3 // +3 to have the address item and byte item align vertically, from center
|
||||
for (const auto& byteItem : firstByteItemByLine) {
|
||||
auto addressItem = addressItemCount > 0 && rowIndex < addressItemCount - 1
|
||||
? this->addressItems[rowIndex]
|
||||
: *(this->addressItems.insert(
|
||||
this->addressItems.end(),
|
||||
new ByteAddressItem(this->hexViewerState, this)
|
||||
)
|
||||
);
|
||||
|
||||
addressItem->setPos(
|
||||
leftMargin,
|
||||
byteItem->position().y() + 4 // +4 to have the address item and byte item align vertically, from center
|
||||
);
|
||||
|
||||
addressItem->address = byteItem->startAddress;
|
||||
addressItem->setVisible(true);
|
||||
++rowIndex;
|
||||
}
|
||||
|
||||
// Hide any address items we no longer need
|
||||
const auto addressItemCount = this->addressItemsByRowIndex.size();
|
||||
|
||||
if (newRowCount > 0 && newRowCount < addressItemCount) {
|
||||
for (auto i = (addressItemCount - 1); i >= newRowCount; i--) {
|
||||
this->addressItemsByRowIndex.at(i)->setVisible(false);
|
||||
const auto usedAddressItemCount = rowIndex;
|
||||
if (addressItemCount > 0 && usedAddressItemCount < addressItemCount) {
|
||||
for (auto i = (addressItemCount - 1); i >= usedAddressItemCount; --i) {
|
||||
this->addressItems[i]->setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +43,7 @@ namespace Bloom::Widgets
|
||||
}
|
||||
|
||||
void ByteAddressContainer::invalidateChildItemCaches() {
|
||||
for (auto& [rowIndex, addressItem] : this->addressItemsByRowIndex) {
|
||||
for (auto& addressItem : this->addressItems) {
|
||||
addressItem->update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGraphicsItem>
|
||||
#include <cstdint>
|
||||
#include <QPainter>
|
||||
#include <QGraphicsScene>
|
||||
#include <map>
|
||||
#include <QPainter>
|
||||
#include <vector>
|
||||
|
||||
#include "ByteItem.hpp"
|
||||
#include "HexViewerSharedState.hpp"
|
||||
#include "ByteAddressItem.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidgetSettings.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
@@ -18,7 +15,7 @@ namespace Bloom::Widgets
|
||||
public:
|
||||
static constexpr int WIDTH = 88;
|
||||
|
||||
ByteAddressContainer(const HexViewerWidgetSettings& settings);
|
||||
ByteAddressContainer(const HexViewerSharedState& hexViewerState);
|
||||
|
||||
[[nodiscard]] QRectF boundingRect() const override {
|
||||
return QRectF(
|
||||
@@ -29,12 +26,12 @@ namespace Bloom::Widgets
|
||||
);
|
||||
}
|
||||
|
||||
void adjustAddressLabels(const std::map<std::size_t, std::vector<ByteItem*>>& byteItemsByRowIndex);
|
||||
void adjustAddressLabels(const std::vector<const ByteItem*>& firstByteItemByLine);
|
||||
void invalidateChildItemCaches();
|
||||
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
|
||||
|
||||
private:
|
||||
const HexViewerWidgetSettings& settings;
|
||||
std::map<std::size_t, ByteAddressItem*> addressItemsByRowIndex;
|
||||
const HexViewerSharedState& hexViewerState;
|
||||
std::vector<ByteAddressItem*> addressItems;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,46 +1,37 @@
|
||||
#include "ByteAddressItem.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
ByteAddressItem::ByteAddressItem(
|
||||
std::size_t rowIndex,
|
||||
const std::map<std::size_t, std::vector<ByteItem*>>& byteItemsByRowIndex,
|
||||
const AddressType& addressType,
|
||||
QGraphicsItem* parent
|
||||
)
|
||||
: rowIndex(rowIndex)
|
||||
, byteItemsByRowIndex(byteItemsByRowIndex)
|
||||
, addressType(addressType)
|
||||
ByteAddressItem::ByteAddressItem(const HexViewerSharedState& hexViewerState, QGraphicsItem* parent)
|
||||
: hexViewerState(hexViewerState)
|
||||
, QGraphicsItem(parent)
|
||||
{
|
||||
this->setCacheMode(
|
||||
QGraphicsItem::CacheMode::ItemCoordinateCache,
|
||||
QSize(ByteAddressItem::WIDTH, ByteAddressItem::HEIGHT)
|
||||
);
|
||||
}
|
||||
{}
|
||||
|
||||
void ByteAddressItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
|
||||
static auto fontColor = QColor(0x8F, 0x91, 0x92);
|
||||
static auto font = QFont("'Ubuntu', sans-serif");
|
||||
font.setPixelSize(12);
|
||||
|
||||
painter->setRenderHints(
|
||||
QPainter::RenderHint::Antialiasing | QPainter::RenderHint::SmoothPixmapTransform,
|
||||
true
|
||||
);
|
||||
|
||||
static const auto widgetRect = this->boundingRect();
|
||||
static auto fontColor = QColor(0x8F, 0x91, 0x92);
|
||||
static auto font = QFont("'Ubuntu', sans-serif");
|
||||
font.setPixelSize(12);
|
||||
fontColor.setAlpha(!this->isEnabled() ? 100 : 255);
|
||||
if (!this->isEnabled()) {
|
||||
painter->setOpacity(0.5);
|
||||
}
|
||||
|
||||
painter->setFont(font);
|
||||
painter->setPen(fontColor);
|
||||
painter->drawText(
|
||||
widgetRect,
|
||||
this->boundingRect(),
|
||||
Qt::AlignLeft,
|
||||
this->addressType == AddressType::RELATIVE
|
||||
? this->byteItemsByRowIndex.at(this->rowIndex)[0]->relativeAddressHex
|
||||
: this->byteItemsByRowIndex.at(this->rowIndex)[0]->addressHex
|
||||
this->hexViewerState.settings.addressLabelType == AddressType::RELATIVE
|
||||
? "0x" + QString::number(
|
||||
this->address - this->hexViewerState.memoryDescriptor.addressRange.startAddress,
|
||||
16
|
||||
).rightJustified(8, '0').toUpper()
|
||||
: "0x" + QString::number(this->address, 16).rightJustified(8, '0').toUpper()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <QGraphicsItem>
|
||||
#include <map>
|
||||
#include <QPainter>
|
||||
|
||||
#include "ByteItem.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/AddressType.hpp"
|
||||
#include "HexViewerSharedState.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
@@ -16,14 +14,9 @@ namespace Bloom::Widgets
|
||||
static constexpr int WIDTH = 75;
|
||||
static constexpr int HEIGHT = ByteItem::HEIGHT;
|
||||
|
||||
std::size_t rowIndex = 0;
|
||||
Targets::TargetMemoryAddress address = 0;
|
||||
|
||||
explicit ByteAddressItem(
|
||||
std::size_t rowIndex,
|
||||
const std::map<std::size_t, std::vector<ByteItem*>>& byteItemsByRowIndex,
|
||||
const AddressType& addressType,
|
||||
QGraphicsItem* parent
|
||||
);
|
||||
explicit ByteAddressItem(const HexViewerSharedState& hexViewerState, QGraphicsItem* parent);
|
||||
|
||||
[[nodiscard]] QRectF boundingRect() const override {
|
||||
return {
|
||||
@@ -37,7 +30,6 @@ namespace Bloom::Widgets
|
||||
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
|
||||
|
||||
private:
|
||||
const std::map<std::size_t, std::vector<ByteItem*>>& byteItemsByRowIndex;
|
||||
const AddressType& addressType;
|
||||
const HexViewerSharedState& hexViewerState;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,131 +1,105 @@
|
||||
#include "ByteItem.hpp"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QFont>
|
||||
#include <QColor>
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
ByteItem::ByteItem(
|
||||
std::size_t byteIndex,
|
||||
Targets::TargetMemoryAddress address,
|
||||
std::optional<Targets::TargetStackPointer>& currentStackPointer,
|
||||
ByteItem** hoveredByteItem,
|
||||
std::set<ByteItem*>& highlightedByteItems,
|
||||
const HexViewerWidgetSettings& settings
|
||||
)
|
||||
: QGraphicsItem(nullptr)
|
||||
, byteIndex(byteIndex)
|
||||
, address(address)
|
||||
, currentStackPointer(currentStackPointer)
|
||||
, hoveredByteItem(hoveredByteItem)
|
||||
, highlightedByteItems(highlightedByteItems)
|
||||
, settings(settings)
|
||||
ByteItem::ByteItem(Targets::TargetMemoryAddress address)
|
||||
: HexViewerItem(address)
|
||||
{
|
||||
static const auto cacheResolution = QSize(ByteItem::WIDTH, ByteItem::HEIGHT);
|
||||
|
||||
this->setCacheMode(
|
||||
QGraphicsItem::CacheMode::ItemCoordinateCache,
|
||||
cacheResolution
|
||||
);
|
||||
this->setAcceptHoverEvents(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->setSelected(false);
|
||||
if (ByteItem::standardPixmapsByValue.empty()) {
|
||||
ByteItem::generatePixmapCaches();
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItem::setValue(unsigned char 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;
|
||||
void ByteItem::paint(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const {
|
||||
const auto boundingRect = QRect(0, 0, ByteItem::WIDTH, ByteItem::HEIGHT);
|
||||
|
||||
this->valueInitialised = this->excludedMemoryRegion == nullptr;
|
||||
this->update();
|
||||
}
|
||||
|
||||
void ByteItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
|
||||
painter->setRenderHints(
|
||||
QPainter::RenderHint::Antialiasing | QPainter::RenderHint::SmoothPixmapTransform,
|
||||
true
|
||||
);
|
||||
painter->setPen(Qt::PenStyle::NoPen);
|
||||
|
||||
static const auto widgetRect = this->boundingRect();
|
||||
static const auto font = QFont("'Ubuntu', sans-serif", 8);
|
||||
|
||||
const auto* backgroundColor = this->getBackgroundColor();
|
||||
const auto* textColor = this->getTextColor();
|
||||
|
||||
if (backgroundColor != nullptr) {
|
||||
painter->setBrush(*backgroundColor);
|
||||
painter->drawRect(widgetRect);
|
||||
if (!graphicsItem->isEnabled()) {
|
||||
painter->setOpacity(0.6);
|
||||
}
|
||||
|
||||
painter->setFont(font);
|
||||
painter->setPen(*textColor);
|
||||
if (this->excluded || !hexViewerState->data.has_value()) {
|
||||
painter->drawPixmap(boundingRect, ByteItem::missingDataPixmap.value());
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->valueInitialised && this->excludedMemoryRegion == nullptr) {
|
||||
painter->drawText(
|
||||
widgetRect,
|
||||
Qt::AlignCenter,
|
||||
this->settings.displayAsciiValues && this->asciiValue.has_value()
|
||||
? this->asciiValue.value()
|
||||
: this->hexValue
|
||||
const auto byteIndex = this->startAddress - hexViewerState->memoryDescriptor.addressRange.startAddress;
|
||||
const auto value = (*hexViewerState->data)[byteIndex];
|
||||
|
||||
const auto hoveredPrimary = hexViewerState->hoveredByteItem == this;
|
||||
|
||||
if (hexViewerState->settings.displayAsciiValues) {
|
||||
if (this->selected) {
|
||||
painter->drawPixmap(
|
||||
boundingRect,
|
||||
ByteItem::selectedAsciiPixmapsByValue[value]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->grouped && hexViewerState->settings.highlightFocusedMemory) {
|
||||
painter->drawPixmap(
|
||||
boundingRect,
|
||||
ByteItem::groupedAsciiPixmapsByValue[value]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hoveredPrimary) {
|
||||
painter->drawPixmap(
|
||||
boundingRect,
|
||||
ByteItem::hoveredPrimaryAsciiPixmapsByValue[value]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
painter->drawPixmap(boundingRect, this->standardAsciiPixmapsByValue[value]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->selected) {
|
||||
painter->drawPixmap(
|
||||
boundingRect,
|
||||
ByteItem::selectedPixmapsByValue[value]
|
||||
);
|
||||
|
||||
} else {
|
||||
static const auto placeholderString = QString("??");
|
||||
painter->drawText(widgetRect, Qt::AlignCenter, placeholderString);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->grouped && hexViewerState->settings.highlightFocusedMemory) {
|
||||
painter->drawPixmap(
|
||||
boundingRect,
|
||||
ByteItem::groupedPixmapsByValue[value]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hoveredPrimary) {
|
||||
painter->drawPixmap(
|
||||
boundingRect,
|
||||
ByteItem::hoveredPrimaryPixmapsByValue[value]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
painter->drawPixmap(
|
||||
boundingRect,
|
||||
this->standardPixmapsByValue[value]
|
||||
);
|
||||
}
|
||||
|
||||
const QColor* ByteItem::getBackgroundColor() {
|
||||
/*
|
||||
* Due to the sheer number of byte items, painting them can be quite expensive. This function needs to be fast.
|
||||
*
|
||||
* The background colors vary in alpha value, depending on certain states. We create a static object for each
|
||||
* color variant, so that we don't have to make copies or call QColor::setAlpha() for each ByteItem.
|
||||
*/
|
||||
void ByteItem::generatePixmapCaches() {
|
||||
static const auto standardBackgroundColor = QColor(0x32, 0x33, 0x30, 255);
|
||||
static const auto highlightedBackgroundColor = QColor(0x3C, 0x59, 0x5C, 255);
|
||||
static const auto selectedBackgroundColor = QColor(0x3C, 0x59, 0x5C, 255);
|
||||
static const auto focusedRegionBackgroundColor = QColor(0x44, 0x44, 0x41, 255);
|
||||
static const auto groupedBackgroundColor = QColor(0x44, 0x44, 0x41, 255);
|
||||
static const auto stackMemoryBackgroundColor = QColor(0x67, 0x57, 0x20, 210);
|
||||
|
||||
static const auto disabledHighlightedBackgroundColor = QColor(
|
||||
highlightedBackgroundColor.red(),
|
||||
highlightedBackgroundColor.green(),
|
||||
highlightedBackgroundColor.blue(),
|
||||
100
|
||||
);
|
||||
|
||||
static const auto disabledSelectedBackgroundColor = QColor(
|
||||
selectedBackgroundColor.red(),
|
||||
selectedBackgroundColor.green(),
|
||||
selectedBackgroundColor.blue(),
|
||||
100
|
||||
);
|
||||
|
||||
static const auto disabledFocusedRegionBackgroundColor = QColor(
|
||||
focusedRegionBackgroundColor.red(),
|
||||
focusedRegionBackgroundColor.green(),
|
||||
focusedRegionBackgroundColor.blue(),
|
||||
100
|
||||
);
|
||||
|
||||
static const auto disabledStackMemoryBackgroundColor = QColor(
|
||||
stackMemoryBackgroundColor.red(),
|
||||
stackMemoryBackgroundColor.green(),
|
||||
stackMemoryBackgroundColor.blue(),
|
||||
100
|
||||
);
|
||||
|
||||
static const auto hoveredStackMemoryBackgroundColor = QColor(
|
||||
stackMemoryBackgroundColor.red(),
|
||||
stackMemoryBackgroundColor.green(),
|
||||
@@ -134,109 +108,154 @@ namespace Bloom::Widgets
|
||||
);
|
||||
|
||||
static const auto hoveredBackgroundColor = QColor(0x8E, 0x8B, 0x83, 70);
|
||||
static const auto hoveredNeighbourBackgroundColor = QColor(0x8E, 0x8B, 0x83, 30);
|
||||
static const auto hoveredSecondaryBackgroundColor = QColor(0x8E, 0x8B, 0x83, 30);
|
||||
|
||||
if (this->isEnabled()) {
|
||||
if (this->highlighted) {
|
||||
return &(highlightedBackgroundColor);
|
||||
static const auto standardFontColor = QColor(0xAF, 0xB1, 0xB3);
|
||||
static const auto fadedFontColor = QColor(0xAF, 0xB1, 0xB3, 100);
|
||||
static const auto asciiFontColor = QColor(0xA7, 0x77, 0x26);
|
||||
|
||||
const auto byteItemRect = QRect(0, 0, ByteItem::WIDTH, ByteItem::HEIGHT);
|
||||
const auto byteItemSize = byteItemRect.size();
|
||||
|
||||
auto standardTemplatePixmap = QPixmap(byteItemSize);
|
||||
standardTemplatePixmap.fill(standardBackgroundColor);
|
||||
|
||||
auto highlightedTemplatePixmap = QPixmap(byteItemSize);
|
||||
highlightedTemplatePixmap.fill(highlightedBackgroundColor);
|
||||
|
||||
auto selectedTemplatePixmap = QPixmap(byteItemSize);
|
||||
selectedTemplatePixmap.fill(selectedBackgroundColor);
|
||||
|
||||
auto groupedTemplatePixmap = QPixmap(byteItemSize);
|
||||
groupedTemplatePixmap.fill(groupedBackgroundColor);
|
||||
|
||||
auto stackMemoryTemplatePixmap = QPixmap(byteItemSize);
|
||||
stackMemoryTemplatePixmap.fill(stackMemoryBackgroundColor);
|
||||
|
||||
auto hoveredStackMemoryTemplatePixmap = QPixmap(byteItemSize);
|
||||
hoveredStackMemoryTemplatePixmap.fill(hoveredStackMemoryBackgroundColor);
|
||||
|
||||
auto hoveredPrimaryTemplatePixmap = QPixmap(byteItemSize);
|
||||
hoveredPrimaryTemplatePixmap.fill(hoveredBackgroundColor);
|
||||
|
||||
auto hoveredSecondaryTemplatePixmap = QPixmap(byteItemSize);
|
||||
hoveredSecondaryTemplatePixmap.fill(hoveredSecondaryBackgroundColor);
|
||||
|
||||
static auto font = QFont("'Ubuntu', sans-serif", 8);
|
||||
|
||||
for (std::uint16_t value = 0x00; value <= 0xFF; ++value) {
|
||||
const auto hexValue = QString::number(value, 16).rightJustified(2, '0').toUpper();
|
||||
const auto asciiValue = value >= 32 && value <= 126
|
||||
? std::optional("'" + QString(QChar(value)) + "'")
|
||||
: std::nullopt;
|
||||
|
||||
{
|
||||
auto standardPixmap = standardTemplatePixmap;
|
||||
auto painter = QPainter(&standardPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(standardFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
|
||||
|
||||
ByteItem::standardPixmapsByValue.emplace_back(std::move(standardPixmap));
|
||||
}
|
||||
|
||||
if (this->selected) {
|
||||
return &(selectedBackgroundColor);
|
||||
{
|
||||
auto selectedPixmap = selectedTemplatePixmap;
|
||||
auto painter = QPainter(&selectedPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(standardFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
|
||||
|
||||
ByteItem::selectedPixmapsByValue.emplace_back(std::move(selectedPixmap));
|
||||
}
|
||||
|
||||
const auto* hoveredByteItem = *(this->hoveredByteItem);
|
||||
const auto hovered = hoveredByteItem == this;
|
||||
const auto hoveredNeighbour =
|
||||
!hovered
|
||||
&& hoveredByteItem != nullptr
|
||||
&& this->settings.highlightHoveredRowAndCol
|
||||
&& (
|
||||
hoveredByteItem->currentColumnIndex == this->currentColumnIndex
|
||||
|| hoveredByteItem->currentRowIndex == this->currentRowIndex
|
||||
);
|
||||
{
|
||||
auto groupedPixmap = groupedTemplatePixmap;
|
||||
auto painter = QPainter(&groupedPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(standardFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
|
||||
|
||||
if (
|
||||
this->settings.highlightStackMemory
|
||||
&& this->currentStackPointer.has_value()
|
||||
&& this->address > this->currentStackPointer
|
||||
) {
|
||||
return hovered ? &(hoveredStackMemoryBackgroundColor) : &(stackMemoryBackgroundColor);
|
||||
ByteItem::groupedPixmapsByValue.emplace_back(std::move(groupedPixmap));
|
||||
}
|
||||
|
||||
if (this->settings.highlightFocusedMemory && this->focusedMemoryRegion != nullptr) {
|
||||
return &(focusedRegionBackgroundColor);
|
||||
{
|
||||
auto hoveredPrimaryPixmap = hoveredPrimaryTemplatePixmap;
|
||||
auto painter = QPainter(&hoveredPrimaryPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(standardFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
|
||||
|
||||
ByteItem::hoveredPrimaryPixmapsByValue.emplace_back(std::move(hoveredPrimaryPixmap));
|
||||
}
|
||||
|
||||
if (hoveredNeighbour) {
|
||||
return &(hoveredNeighbourBackgroundColor);
|
||||
{
|
||||
auto hoveredSecondaryPixmap = hoveredSecondaryTemplatePixmap;
|
||||
auto painter = QPainter(&hoveredSecondaryPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(standardFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, hexValue);
|
||||
|
||||
ByteItem::hoveredSecondaryPixmapsByValue.emplace_back(std::move(hoveredSecondaryPixmap));
|
||||
}
|
||||
|
||||
if (hovered) {
|
||||
return &(hoveredBackgroundColor);
|
||||
{
|
||||
auto standardAsciiPixmap = standardTemplatePixmap;
|
||||
auto painter = QPainter(&standardAsciiPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
|
||||
|
||||
ByteItem::standardAsciiPixmapsByValue.emplace_back(std::move(standardAsciiPixmap));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (this->highlighted) {
|
||||
return &(disabledHighlightedBackgroundColor);
|
||||
{
|
||||
auto selectedAsciiPixmap = selectedTemplatePixmap;
|
||||
auto painter = QPainter(&selectedAsciiPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
|
||||
|
||||
ByteItem::selectedAsciiPixmapsByValue.emplace_back(std::move(selectedAsciiPixmap));
|
||||
}
|
||||
|
||||
if (this->selected) {
|
||||
return &(disabledSelectedBackgroundColor);
|
||||
{
|
||||
auto groupedAsciiPixmap = groupedTemplatePixmap;
|
||||
auto painter = QPainter(&groupedAsciiPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
|
||||
|
||||
ByteItem::groupedAsciiPixmapsByValue.emplace_back(std::move(groupedAsciiPixmap));
|
||||
}
|
||||
|
||||
if (
|
||||
this->settings.highlightStackMemory
|
||||
&& this->currentStackPointer.has_value()
|
||||
&& this->address > this->currentStackPointer
|
||||
) {
|
||||
return &(disabledStackMemoryBackgroundColor);
|
||||
{
|
||||
auto hoveredPrimaryAsciiPixmap = hoveredPrimaryTemplatePixmap;
|
||||
auto painter = QPainter(&hoveredPrimaryAsciiPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
|
||||
|
||||
ByteItem::hoveredPrimaryAsciiPixmapsByValue.emplace_back(std::move(hoveredPrimaryAsciiPixmap));
|
||||
}
|
||||
|
||||
if (this->settings.highlightFocusedMemory && this->focusedMemoryRegion != nullptr) {
|
||||
return &(disabledFocusedRegionBackgroundColor);
|
||||
{
|
||||
auto hoveredSecondaryAsciiPixmap = hoveredSecondaryTemplatePixmap;
|
||||
auto painter = QPainter(&hoveredSecondaryAsciiPixmap);
|
||||
painter.setFont(font);
|
||||
painter.setPen(asciiValue.has_value() ? asciiFontColor : fadedFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, asciiValue.value_or(hexValue));
|
||||
|
||||
ByteItem::hoveredSecondaryAsciiPixmapsByValue.emplace_back(std::move(hoveredSecondaryAsciiPixmap));
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const QColor* ByteItem::getTextColor() {
|
||||
static const auto standardTextColor = QColor(0xAF, 0xB1, 0xB3);
|
||||
static const auto asciiModeTextColor = QColor(0xA7, 0x77, 0x26);
|
||||
|
||||
static const auto fadedStandardTextColor = QColor(
|
||||
standardTextColor.red(),
|
||||
standardTextColor.green(),
|
||||
standardTextColor.blue(),
|
||||
100
|
||||
);
|
||||
|
||||
static const auto fadedAsciiModeTextColor = QColor(
|
||||
asciiModeTextColor.red(),
|
||||
asciiModeTextColor.green(),
|
||||
asciiModeTextColor.blue(),
|
||||
100
|
||||
);
|
||||
|
||||
const auto displayAsAscii = this->settings.displayAsciiValues && this->asciiValue.has_value();
|
||||
|
||||
if (this->isEnabled() && this->valueInitialised) {
|
||||
if (!displayAsAscii) {
|
||||
if (
|
||||
this->excludedMemoryRegion != nullptr
|
||||
|| this->settings.displayAsciiValues
|
||||
|| (!this->highlightedByteItems.empty() && !this->highlighted)
|
||||
) {
|
||||
return &(fadedStandardTextColor);
|
||||
}
|
||||
|
||||
return &(standardTextColor);
|
||||
}
|
||||
|
||||
return &(asciiModeTextColor);
|
||||
{
|
||||
ByteItem::missingDataPixmap = standardTemplatePixmap;
|
||||
auto painter = QPainter(&ByteItem::missingDataPixmap.value());
|
||||
painter.setFont(font);
|
||||
painter.setPen(standardFontColor);
|
||||
painter.drawText(byteItemRect, Qt::AlignCenter, "??");
|
||||
}
|
||||
|
||||
return displayAsAscii ? &(fadedAsciiModeTextColor) : &(fadedStandardTextColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,79 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <QEvent>
|
||||
#include <QGraphicsItem>
|
||||
#include <QFont>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
|
||||
#include "src/Targets/TargetMemory.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"
|
||||
#include "HexViewerItem.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class ByteItem: public QGraphicsItem
|
||||
#pragma pack(push, 1)
|
||||
class ByteItem: public HexViewerItem
|
||||
{
|
||||
public:
|
||||
static constexpr int WIDTH = 25;
|
||||
static constexpr int HEIGHT = 20;
|
||||
static constexpr int WIDTH = 27;
|
||||
static constexpr int HEIGHT = 21;
|
||||
|
||||
static constexpr int RIGHT_MARGIN = 5;
|
||||
static constexpr int BOTTOM_MARGIN = 5;
|
||||
|
||||
std::size_t byteIndex;
|
||||
Targets::TargetMemoryAddress address = 0x00;
|
||||
QString addressHex;
|
||||
QString relativeAddressHex;
|
||||
|
||||
unsigned char value = 0x00;
|
||||
QString hexValue;
|
||||
|
||||
std::size_t currentRowIndex = 0;
|
||||
std::size_t currentColumnIndex = 0;
|
||||
|
||||
bool highlighted = false;
|
||||
bool selected = false;
|
||||
const FocusedMemoryRegion* focusedMemoryRegion = nullptr;
|
||||
const ExcludedMemoryRegion* excludedMemoryRegion = nullptr;
|
||||
bool excluded = false;
|
||||
bool grouped = false;
|
||||
|
||||
ByteItem(
|
||||
std::size_t byteIndex,
|
||||
Targets::TargetMemoryAddress address,
|
||||
std::optional<Targets::TargetStackPointer>& currentStackPointer,
|
||||
ByteItem** hoveredByteItem,
|
||||
std::set<ByteItem*>& highlightedByteItems,
|
||||
const HexViewerWidgetSettings& settings
|
||||
);
|
||||
explicit ByteItem(Targets::TargetMemoryAddress address);
|
||||
|
||||
void setValue(unsigned char value);
|
||||
|
||||
[[nodiscard]] QRectF boundingRect() const override {
|
||||
return QRectF(
|
||||
0,
|
||||
0,
|
||||
ByteItem::WIDTH,
|
||||
ByteItem::HEIGHT
|
||||
);
|
||||
QSize size() const override {
|
||||
return QSize(ByteItem::WIDTH, ByteItem::HEIGHT);
|
||||
}
|
||||
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
|
||||
|
||||
void paint(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const override;
|
||||
|
||||
private:
|
||||
bool valueInitialised = false;
|
||||
static inline std::vector<QPixmap> standardPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> selectedPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> groupedPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> standardAsciiPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> selectedAsciiPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> groupedAsciiPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> hoveredPrimaryPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> hoveredSecondaryPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> hoveredPrimaryAsciiPixmapsByValue = {};
|
||||
static inline std::vector<QPixmap> hoveredSecondaryAsciiPixmapsByValue = {};
|
||||
static inline std::optional<QPixmap> missingDataPixmap = {};
|
||||
|
||||
const HexViewerWidgetSettings& settings;
|
||||
std::optional<QString> asciiValue;
|
||||
|
||||
ByteItem** hoveredByteItem;
|
||||
std::optional<Targets::TargetStackPointer>& currentStackPointer;
|
||||
std::set<ByteItem*>& highlightedByteItems;
|
||||
|
||||
const QColor* getBackgroundColor();
|
||||
const QColor* getTextColor();
|
||||
static void generatePixmapCaches();
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
||||
|
||||
@@ -1,831 +0,0 @@
|
||||
#include "ByteItemGraphicsScene.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <QMenu>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "src/Insight/InsightWorker/InsightWorker.hpp"
|
||||
#include "src/Insight/InsightSignals.hpp"
|
||||
|
||||
#include "src/Insight/InsightWorker/Tasks/ConstructHexViewerByteItems.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
using Bloom::Targets::TargetMemoryDescriptor;
|
||||
|
||||
ByteItemGraphicsScene::ByteItemGraphicsScene(
|
||||
const TargetMemoryDescriptor& targetMemoryDescriptor,
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
|
||||
HexViewerWidgetSettings& settings,
|
||||
Label* hoveredAddressLabel,
|
||||
QGraphicsView* parent
|
||||
)
|
||||
: targetMemoryDescriptor(targetMemoryDescriptor)
|
||||
, focusedMemoryRegions(focusedMemoryRegions)
|
||||
, excludedMemoryRegions(excludedMemoryRegions)
|
||||
, settings(settings)
|
||||
, hoveredAddressLabel(hoveredAddressLabel)
|
||||
, parent(parent)
|
||||
, QGraphicsScene(parent)
|
||||
{
|
||||
this->setObjectName("byte-widget-container");
|
||||
|
||||
this->byteAddressContainer = new ByteAddressContainer(this->settings);
|
||||
this->addItem(this->byteAddressContainer);
|
||||
|
||||
this->displayRelativeAddressAction->setCheckable(true);
|
||||
this->displayAbsoluteAddressAction->setCheckable(true);
|
||||
|
||||
this->setAddressType(this->settings.addressLabelType);
|
||||
|
||||
QObject::connect(
|
||||
InsightSignals::instance(),
|
||||
&InsightSignals::targetStateUpdated,
|
||||
this,
|
||||
&ByteItemGraphicsScene::onTargetStateChanged
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->displayRelativeAddressAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
[this] {
|
||||
this->setAddressType(AddressType::RELATIVE);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->displayAbsoluteAddressAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
[this] {
|
||||
this->setAddressType(AddressType::ABSOLUTE);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->selectAllByteItemsAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
&ByteItemGraphicsScene::selectAllByteItems
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->deselectByteItemsAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
&ByteItemGraphicsScene::clearByteItemSelection
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->copyAbsoluteAddressAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
[this] {
|
||||
this->copyAddressesToClipboard(AddressType::ABSOLUTE);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->copyRelativeAddressAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
[this] {
|
||||
this->copyAddressesToClipboard(AddressType::RELATIVE);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->copyHexValuesAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
&ByteItemGraphicsScene::copyHexValuesToClipboard
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->copyDecimalValuesAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
&ByteItemGraphicsScene::copyDecimalValuesToClipboard
|
||||
);
|
||||
|
||||
this->setSceneRect(0, 0, this->getSceneWidth(), 0);
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::init() {
|
||||
auto* constructByteItemsTask = new ConstructHexViewerByteItems(
|
||||
this->targetMemoryDescriptor,
|
||||
this->currentStackPointer,
|
||||
&(this->hoveredByteWidget),
|
||||
this->highlightedByteItems,
|
||||
this->settings
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
constructByteItemsTask,
|
||||
&ConstructHexViewerByteItems::byteItems,
|
||||
this,
|
||||
[this] (std::map<Targets::TargetMemoryAddress, ByteItem*> byteItemsByAddress) {
|
||||
this->byteItemsByAddress = std::move(byteItemsByAddress);
|
||||
|
||||
for (const auto& [address, byteItem] : this->byteItemsByAddress) {
|
||||
this->addItem(byteItem);
|
||||
}
|
||||
|
||||
this->refreshRegions();
|
||||
emit this->ready();
|
||||
}
|
||||
);
|
||||
|
||||
InsightWorker::queueTask(constructByteItemsTask);
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::updateValues(const Targets::TargetMemoryBuffer& buffer) {
|
||||
for (auto& [address, byteWidget] : this->byteItemsByAddress) {
|
||||
byteWidget->setValue(buffer.at(byteWidget->byteIndex));
|
||||
}
|
||||
|
||||
this->updateAnnotationValues(buffer);
|
||||
this->lastValueBuffer = buffer;
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::updateStackPointer(std::uint32_t stackPointer) {
|
||||
this->currentStackPointer = stackPointer;
|
||||
this->invalidateChildItemCaches();
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::setHighlightedAddresses(const std::set<std::uint32_t>& highlightedAddresses) {
|
||||
this->highlightedByteItems.clear();
|
||||
|
||||
for (auto& [address, byteItem] : this->byteItemsByAddress) {
|
||||
if (highlightedAddresses.contains(address)) {
|
||||
byteItem->highlighted = true;
|
||||
this->highlightedByteItems.insert(byteItem);
|
||||
|
||||
} else {
|
||||
byteItem->highlighted = false;
|
||||
}
|
||||
|
||||
byteItem->update();
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::refreshRegions() {
|
||||
for (auto& [byteAddress, byteWidget] : this->byteItemsByAddress) {
|
||||
byteWidget->focusedMemoryRegion = nullptr;
|
||||
byteWidget->excludedMemoryRegion = nullptr;
|
||||
|
||||
for (const auto& focusedRegion : this->focusedMemoryRegions) {
|
||||
if (byteAddress >= focusedRegion.addressRange.startAddress
|
||||
&& byteAddress <= focusedRegion.addressRange.endAddress
|
||||
) {
|
||||
byteWidget->focusedMemoryRegion = &focusedRegion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& excludedRegion : this->excludedMemoryRegions) {
|
||||
if (byteAddress >= excludedRegion.addressRange.startAddress
|
||||
&& byteAddress <= excludedRegion.addressRange.endAddress
|
||||
) {
|
||||
byteWidget->excludedMemoryRegion = &excludedRegion;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
byteWidget->update();
|
||||
}
|
||||
|
||||
// Refresh annotation items
|
||||
this->hoveredAnnotationItem = nullptr;
|
||||
for (auto* annotationItem : this->annotationItems) {
|
||||
this->removeItem(annotationItem);
|
||||
delete annotationItem;
|
||||
}
|
||||
|
||||
this->annotationItems.clear();
|
||||
this->valueAnnotationItems.clear();
|
||||
|
||||
for (const auto& focusedRegion : this->focusedMemoryRegions) {
|
||||
auto* annotationItem = new AnnotationItem(focusedRegion, AnnotationItemPosition::BOTTOM);
|
||||
annotationItem->setEnabled(this->enabled);
|
||||
this->addItem(annotationItem);
|
||||
this->annotationItems.emplace_back(annotationItem);
|
||||
|
||||
if (focusedRegion.dataType != MemoryRegionDataType::UNKNOWN) {
|
||||
auto* valueAnnotationItem = new ValueAnnotationItem(focusedRegion);
|
||||
valueAnnotationItem->setEnabled(this->enabled);
|
||||
this->addItem(valueAnnotationItem);
|
||||
this->annotationItems.emplace_back(valueAnnotationItem);
|
||||
this->valueAnnotationItems.emplace_back(valueAnnotationItem);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->targetState == Targets::TargetState::STOPPED && this->enabled && !this->lastValueBuffer.empty()) {
|
||||
this->updateAnnotationValues(this->lastValueBuffer);
|
||||
}
|
||||
|
||||
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 & annotation positions if the number of rows & columns haven't 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();
|
||||
|
||||
if (this->settings.displayAnnotations) {
|
||||
this->adjustAnnotationItemPositions();
|
||||
}
|
||||
|
||||
const auto* lastByteItem = (--this->byteItemsByAddress.end())->second;
|
||||
const auto sceneHeight = static_cast<int>(
|
||||
lastByteItem->pos().y() + ByteItem::HEIGHT + AnnotationItem::BOTTOM_HEIGHT + this->margins.bottom()
|
||||
);
|
||||
|
||||
this->setSceneRect(
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
std::max(sceneHeight, this->parent->height())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::setEnabled(bool enabled) {
|
||||
if (this->enabled != enabled) {
|
||||
this->enabled = enabled;
|
||||
|
||||
for (auto& [byteAddress, byteItem] : this->byteItemsByAddress) {
|
||||
byteItem->setEnabled(this->enabled);
|
||||
}
|
||||
|
||||
for (auto* annotationItem : this->annotationItems) {
|
||||
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* annotationItem : this->annotationItems) {
|
||||
annotationItem->update();
|
||||
}
|
||||
}
|
||||
|
||||
QPointF ByteItemGraphicsScene::getByteItemPositionByAddress(std::uint32_t address) {
|
||||
const auto byteItemIt = this->byteItemsByAddress.find(address);
|
||||
if (byteItemIt != this->byteItemsByAddress.end()) {
|
||||
return byteItemIt->second->pos();
|
||||
}
|
||||
|
||||
return QPointF();
|
||||
}
|
||||
|
||||
bool ByteItemGraphicsScene::event(QEvent* event) {
|
||||
if (event->type() == QEvent::Type::GraphicsSceneLeave && this->hoveredByteWidget != nullptr) {
|
||||
this->onByteWidgetLeave();
|
||||
}
|
||||
|
||||
return QGraphicsScene::event(event);
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent) {
|
||||
static const auto rubberBandRectBackgroundColor = QColor(0x3C, 0x59, 0x5C, 0x82);
|
||||
static const auto rubberBandRectBorderColor = QColor(0x3C, 0x59, 0x5C, 255);
|
||||
|
||||
const auto mousePosition = mouseEvent->buttonDownScenePos(Qt::MouseButton::LeftButton);
|
||||
|
||||
if (mousePosition.x() <= this->byteAddressContainer->boundingRect().width()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->clearSelectionRectItem();
|
||||
|
||||
this->rubberBandInitPoint = std::move(mousePosition);
|
||||
this->rubberBandRectItem = new QGraphicsRectItem(
|
||||
this->rubberBandInitPoint->x(),
|
||||
this->rubberBandInitPoint->y(),
|
||||
1,
|
||||
1
|
||||
);
|
||||
this->rubberBandRectItem->setBrush(rubberBandRectBackgroundColor);
|
||||
this->rubberBandRectItem->setPen(rubberBandRectBorderColor);
|
||||
this->addItem(this->rubberBandRectItem);
|
||||
|
||||
const auto modifiers = mouseEvent->modifiers();
|
||||
if ((modifiers & (Qt::ControlModifier | Qt::ShiftModifier)) == 0) {
|
||||
this->clearByteItemSelection();
|
||||
}
|
||||
|
||||
auto clickedItems = this->items(mousePosition);
|
||||
|
||||
if (!clickedItems.empty()) {
|
||||
auto* clickedByteItem = dynamic_cast<ByteItem*>(clickedItems.last());
|
||||
|
||||
if (clickedByteItem != nullptr) {
|
||||
|
||||
if ((modifiers & Qt::ShiftModifier) != 0) {
|
||||
for (
|
||||
auto i = clickedByteItem->address;
|
||||
i >= this->targetMemoryDescriptor.addressRange.startAddress;
|
||||
--i
|
||||
) {
|
||||
auto* byteItem = this->byteItemsByAddress.at(i);
|
||||
|
||||
if (byteItem->selected) {
|
||||
break;
|
||||
}
|
||||
|
||||
this->toggleByteItemSelection(byteItem);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this->toggleByteItemSelection(clickedByteItem);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent) {
|
||||
const auto mousePosition = mouseEvent->scenePos();
|
||||
auto hoveredItems = this->items(mousePosition);
|
||||
|
||||
if (this->rubberBandRectItem != nullptr && this->rubberBandInitPoint.has_value()) {
|
||||
const auto oldRect = this->rubberBandRectItem->rect();
|
||||
|
||||
this->rubberBandRectItem->setRect(
|
||||
qMin(mousePosition.x(), this->rubberBandInitPoint->x()),
|
||||
qMin(mousePosition.y(), this->rubberBandInitPoint->y()),
|
||||
qAbs(mousePosition.x() - this->rubberBandInitPoint->x()),
|
||||
qAbs(mousePosition.y() - this->rubberBandInitPoint->y())
|
||||
);
|
||||
|
||||
if ((mouseEvent->modifiers() & Qt::ControlModifier) == 0) {
|
||||
this->clearByteItemSelection();
|
||||
|
||||
} else {
|
||||
const auto oldItems = this->items(oldRect, Qt::IntersectsItemShape);
|
||||
for (auto* item : oldItems) {
|
||||
auto* byteItem = dynamic_cast<ByteItem*>(item);
|
||||
|
||||
if (byteItem != nullptr && byteItem->selected) {
|
||||
this->deselectByteItem(byteItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto items = this->items(this->rubberBandRectItem->rect(), Qt::IntersectsItemShape);
|
||||
for (auto* item : items) {
|
||||
auto* byteItem = dynamic_cast<ByteItem*>(item);
|
||||
|
||||
if (byteItem != nullptr && !byteItem->selected) {
|
||||
this->selectByteItem(byteItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ByteItem* hoveredByteItem = nullptr;
|
||||
AnnotationItem* hoveredAnnotationItem = nullptr;
|
||||
|
||||
if (!hoveredItems.empty()) {
|
||||
hoveredByteItem = dynamic_cast<ByteItem*>(hoveredItems.last());
|
||||
hoveredAnnotationItem = dynamic_cast<AnnotationItem*>(hoveredItems.last());
|
||||
}
|
||||
|
||||
if (hoveredByteItem != nullptr) {
|
||||
this->onByteWidgetEnter(hoveredByteItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->hoveredByteWidget != nullptr) {
|
||||
this->onByteWidgetLeave();
|
||||
}
|
||||
|
||||
if (hoveredAnnotationItem != nullptr) {
|
||||
this->onAnnotationItemEnter(hoveredAnnotationItem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->hoveredAnnotationItem != nullptr) {
|
||||
this->onAnnotationItemLeave();
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* mouseEvent) {
|
||||
this->clearSelectionRectItem();
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::keyPressEvent(QKeyEvent* keyEvent) {
|
||||
const auto key = keyEvent->key();
|
||||
|
||||
if (key == Qt::Key_Escape) {
|
||||
this->clearByteItemSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto modifiers = keyEvent->modifiers();
|
||||
if ((modifiers & Qt::ControlModifier) != 0 && key == Qt::Key_A) {
|
||||
this->selectAllByteItems();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
|
||||
if (event->scenePos().x() <= ByteAddressContainer::WIDTH) {
|
||||
auto* menu = new QMenu(this->parent);
|
||||
menu->setObjectName("byte-item-address-container-context-menu");
|
||||
|
||||
auto* addressTypeMenu = new QMenu("Address Type", menu);
|
||||
addressTypeMenu->addAction(this->displayAbsoluteAddressAction);
|
||||
addressTypeMenu->addAction(this->displayRelativeAddressAction);
|
||||
menu->addMenu(addressTypeMenu);
|
||||
|
||||
menu->exec(event->screenPos());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto itemsSelected = !this->selectedByteItemsByAddress.empty();
|
||||
|
||||
auto* menu = new QMenu(this->parent);
|
||||
menu->addAction(this->selectAllByteItemsAction);
|
||||
menu->addAction(this->deselectByteItemsAction);
|
||||
menu->addSeparator();
|
||||
|
||||
auto* copyMenu = new QMenu("Copy Selected", menu);
|
||||
copyMenu->addAction(this->copyAbsoluteAddressAction);
|
||||
copyMenu->addAction(this->copyRelativeAddressAction);
|
||||
copyMenu->addSeparator();
|
||||
copyMenu->addAction(this->copyHexValuesAction);
|
||||
copyMenu->addAction(this->copyDecimalValuesAction);
|
||||
|
||||
copyMenu->setEnabled(itemsSelected);
|
||||
this->deselectByteItemsAction->setEnabled(itemsSelected);
|
||||
|
||||
menu->addMenu(copyMenu);
|
||||
menu->exec(event->screenPos());
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::updateAnnotationValues(const Targets::TargetMemoryBuffer& buffer) {
|
||||
const auto memoryStartAddress = this->targetMemoryDescriptor.addressRange.startAddress;
|
||||
for (auto* valueAnnotationItem : this->valueAnnotationItems) {
|
||||
if (valueAnnotationItem->size > buffer.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto relativeStartAddress = valueAnnotationItem->startAddress - memoryStartAddress;
|
||||
const auto relativeEndAddress = valueAnnotationItem->endAddress - memoryStartAddress;
|
||||
|
||||
valueAnnotationItem->setValue(Targets::TargetMemoryBuffer(
|
||||
buffer.begin() + relativeStartAddress,
|
||||
buffer.begin() + relativeEndAddress + 1
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
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* annotationItem : this->annotationItems) {
|
||||
const auto firstByteRowIndex = static_cast<std::size_t>(
|
||||
std::ceil(static_cast<double>((annotationItem->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 (this->settings.displayAnnotations && firstByteRowIndex == lastByteRowIndex) {
|
||||
annotationItem->show();
|
||||
|
||||
if (annotationItem->position == AnnotationItemPosition::TOP) {
|
||||
rowIndicesWithTopAnnotations.insert(firstByteRowIndex);
|
||||
|
||||
} else if (annotationItem->position == AnnotationItemPosition::BOTTOM) {
|
||||
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() || !this->settings.displayAnnotations) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto* annotationItem : this->annotationItems) {
|
||||
const auto firstByteItemIt = this->byteItemsByAddress.find(annotationItem->startAddress);
|
||||
if (firstByteItemIt == this->byteItemsByAddress.end()) {
|
||||
annotationItem->hide();
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto firstByteItemPosition = firstByteItemIt->second->pos();
|
||||
|
||||
if (annotationItem->position == AnnotationItemPosition::TOP) {
|
||||
annotationItem->setPos(
|
||||
firstByteItemPosition.x(),
|
||||
firstByteItemPosition.y() - AnnotationItem::TOP_HEIGHT - 1
|
||||
);
|
||||
|
||||
} else if (annotationItem->position == AnnotationItemPosition::BOTTOM) {
|
||||
annotationItem->setPos(
|
||||
firstByteItemPosition.x(),
|
||||
firstByteItemPosition.y() + ByteItem::HEIGHT
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::onTargetStateChanged(Targets::TargetState newState) {
|
||||
this->targetState = newState;
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::onByteWidgetEnter(ByteItem* widget) {
|
||||
if (this->hoveredByteWidget != nullptr) {
|
||||
if (this->hoveredByteWidget == widget) {
|
||||
// This byte item is already marked as hovered
|
||||
return;
|
||||
}
|
||||
|
||||
this->onByteWidgetLeave();
|
||||
}
|
||||
|
||||
this->hoveredByteWidget = widget;
|
||||
|
||||
this->hoveredAddressLabel->setText(
|
||||
"Relative Address / Absolute Address: " + widget->relativeAddressHex + " / " + widget->addressHex
|
||||
);
|
||||
|
||||
if (this->settings.highlightHoveredRowAndCol && !this->byteItemsByRowIndex.empty()) {
|
||||
for (auto& byteWidget : this->byteItemsByColumnIndex.at(widget->currentColumnIndex)) {
|
||||
byteWidget->update();
|
||||
}
|
||||
|
||||
for (auto& byteWidget : this->byteItemsByRowIndex.at(widget->currentRowIndex)) {
|
||||
byteWidget->update();
|
||||
}
|
||||
|
||||
} else {
|
||||
widget->update();
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::onByteWidgetLeave() {
|
||||
auto* byteItem = this->hoveredByteWidget;
|
||||
this->hoveredByteWidget = nullptr;
|
||||
|
||||
this->hoveredAddressLabel->setText("Relative Address / Absolute Address:");
|
||||
|
||||
if (this->settings.highlightHoveredRowAndCol && !this->byteItemsByRowIndex.empty()) {
|
||||
for (auto& byteWidget : this->byteItemsByColumnIndex.at(byteItem->currentColumnIndex)) {
|
||||
byteWidget->update();
|
||||
}
|
||||
|
||||
for (auto& byteWidget : this->byteItemsByRowIndex.at(byteItem->currentRowIndex)) {
|
||||
byteWidget->update();
|
||||
}
|
||||
|
||||
} else {
|
||||
byteItem->update();
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::onAnnotationItemEnter(AnnotationItem* annotationItem) {
|
||||
if (this->hoveredAnnotationItem != nullptr) {
|
||||
if (this->hoveredAnnotationItem == annotationItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
this->hoveredAnnotationItem = nullptr;
|
||||
|
||||
for (
|
||||
auto byteItemAddress = annotationItem->startAddress;
|
||||
byteItemAddress <= annotationItem->endAddress;
|
||||
byteItemAddress++
|
||||
) {
|
||||
this->byteItemsByAddress.at(byteItemAddress)->update();
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::clearSelectionRectItem() {
|
||||
if (this->rubberBandRectItem != nullptr) {
|
||||
this->removeItem(this->rubberBandRectItem);
|
||||
delete this->rubberBandRectItem;
|
||||
this->rubberBandRectItem = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::selectByteItem(ByteItem* byteItem) {
|
||||
byteItem->selected = true;
|
||||
this->selectedByteItemsByAddress.insert(std::pair(byteItem->address, byteItem));
|
||||
byteItem->update();
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::deselectByteItem(ByteItem* byteItem) {
|
||||
byteItem->selected = false;
|
||||
this->selectedByteItemsByAddress.erase(byteItem->address);
|
||||
byteItem->update();
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::toggleByteItemSelection(ByteItem* byteItem) {
|
||||
if (byteItem->selected) {
|
||||
this->deselectByteItem(byteItem);
|
||||
return;
|
||||
}
|
||||
|
||||
this->selectByteItem(byteItem);
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::clearByteItemSelection() {
|
||||
for (const auto& [address, byteItem] : this->selectedByteItemsByAddress) {
|
||||
byteItem->selected = false;
|
||||
byteItem->update();
|
||||
}
|
||||
|
||||
this->selectedByteItemsByAddress.clear();
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::selectAllByteItems() {
|
||||
for (auto& [address, byteItem] : this->byteItemsByAddress) {
|
||||
this->selectByteItem(byteItem);
|
||||
}
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::setAddressType(AddressType type) {
|
||||
this->settings.addressLabelType = type;
|
||||
|
||||
this->displayRelativeAddressAction->setChecked(this->settings.addressLabelType == AddressType::RELATIVE);
|
||||
this->displayAbsoluteAddressAction->setChecked(this->settings.addressLabelType == AddressType::ABSOLUTE);
|
||||
|
||||
this->byteAddressContainer->invalidateChildItemCaches();
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::copyAddressesToClipboard(AddressType type) {
|
||||
if (this->selectedByteItemsByAddress.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QString();
|
||||
|
||||
for (const auto& [address, byteItem] : this->selectedByteItemsByAddress) {
|
||||
data.append((type == AddressType::ABSOLUTE ? byteItem->addressHex : byteItem->relativeAddressHex) + "\n");
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(std::move(data));
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::copyHexValuesToClipboard() {
|
||||
if (this->selectedByteItemsByAddress.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QString();
|
||||
|
||||
for (const auto& [address, byteItem] : this->selectedByteItemsByAddress) {
|
||||
data.append("0x" + byteItem->hexValue + "\n");
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(std::move(data));
|
||||
}
|
||||
|
||||
void ByteItemGraphicsScene::copyDecimalValuesToClipboard() {
|
||||
if (this->selectedByteItemsByAddress.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QString();
|
||||
|
||||
for (const auto& [address, byteItem] : this->selectedByteItemsByAddress) {
|
||||
data.append(QString::number(byteItem->value, 10) + "\n");
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(std::move(data));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
#include "FocusedRegionGroupItem.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <QColor>
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
FocusedRegionGroupItem::FocusedRegionGroupItem(
|
||||
const FocusedMemoryRegion& focusedRegion,
|
||||
std::unordered_map<Targets::TargetMemoryAddress, ByteItem>& byteItemsByAddress,
|
||||
HexViewerItem* parent
|
||||
)
|
||||
: GroupItem(focusedRegion.addressRange.startAddress, parent)
|
||||
, focusedMemoryRegion(focusedRegion)
|
||||
{
|
||||
const auto& startAddress = this->focusedMemoryRegion.addressRange.startAddress;
|
||||
const auto& endAddress = this->focusedMemoryRegion.addressRange.endAddress;
|
||||
|
||||
// Sanity check
|
||||
assert(byteItemsByAddress.contains(startAddress) && byteItemsByAddress.contains(endAddress));
|
||||
|
||||
for (auto address = startAddress; address <= endAddress; ++address) {
|
||||
auto& byteItem = byteItemsByAddress.at(address);
|
||||
byteItem.parent = this;
|
||||
byteItem.grouped = true;
|
||||
|
||||
this->items.push_back(&byteItem);
|
||||
}
|
||||
}
|
||||
|
||||
void FocusedRegionGroupItem::refreshValue(const HexViewerSharedState& hexViewerState) {
|
||||
if (!hexViewerState.data.has_value()) {
|
||||
this->valueLabel = std::nullopt;
|
||||
return;
|
||||
}
|
||||
|
||||
const auto regionStartAddress = this->focusedMemoryRegion.addressRange.startAddress;
|
||||
const auto regionEndAddress = this->focusedMemoryRegion.addressRange.endAddress;
|
||||
const auto startIndex = regionStartAddress - hexViewerState.memoryDescriptor.addressRange.startAddress;
|
||||
auto value = Targets::TargetMemoryBuffer(
|
||||
hexViewerState.data->begin() + startIndex,
|
||||
hexViewerState.data->begin() + startIndex + (regionEndAddress - regionStartAddress + 1)
|
||||
);
|
||||
|
||||
if (this->focusedMemoryRegion.endianness == Targets::TargetMemoryEndianness::LITTLE) {
|
||||
std::reverse(value.begin(), value.end());
|
||||
}
|
||||
|
||||
switch (this->focusedMemoryRegion.dataType) {
|
||||
case MemoryRegionDataType::UNSIGNED_INTEGER: {
|
||||
std::uint64_t integerValue = 0;
|
||||
for (const auto& byte : value) {
|
||||
integerValue = (integerValue << 8) | byte;
|
||||
}
|
||||
|
||||
this->valueLabel = QString::number(integerValue);
|
||||
break;
|
||||
}
|
||||
case MemoryRegionDataType::SIGNED_INTEGER: {
|
||||
const auto valueSize = value.size();
|
||||
|
||||
if (valueSize == 1) {
|
||||
this->valueLabel = QString::number(static_cast<int8_t>(value[0]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (valueSize == 2) {
|
||||
this->valueLabel = QString::number(static_cast<int16_t>((value[0] << 8) | value[1]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (valueSize <= 4) {
|
||||
std::int32_t integerValue = 0;
|
||||
for (const auto& byte : value) {
|
||||
integerValue = (integerValue << 8) | byte;
|
||||
}
|
||||
|
||||
this->valueLabel = QString::number(integerValue);
|
||||
break;
|
||||
}
|
||||
|
||||
std::int64_t integerValue = 0;
|
||||
for (const auto& byte : value) {
|
||||
integerValue = (integerValue << 8) | byte;
|
||||
}
|
||||
|
||||
this->valueLabel = QString::number(integerValue);
|
||||
break;
|
||||
}
|
||||
case MemoryRegionDataType::ASCII_STRING: {
|
||||
// Replace non-ASCII chars with '?'
|
||||
auto asciiData = value;
|
||||
|
||||
std::replace_if(
|
||||
asciiData.begin(),
|
||||
asciiData.end(),
|
||||
[] (unsigned char value) {
|
||||
/*
|
||||
* We only care about non-control characters (except for the white space character) in
|
||||
* the standard ASCII range.
|
||||
*/
|
||||
return value < 32 || value > 126;
|
||||
},
|
||||
'?'
|
||||
);
|
||||
|
||||
this->valueLabel = "'" + QString::fromLatin1(
|
||||
reinterpret_cast<const char*>(asciiData.data()),
|
||||
static_cast<qsizetype>(asciiData.size())
|
||||
) + "'";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this->valueLabel = std::nullopt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FocusedRegionGroupItem::paint(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const {
|
||||
if (!hexViewerState->settings.displayAnnotations) {
|
||||
return;
|
||||
}
|
||||
|
||||
painter->setRenderHints(QPainter::RenderHint::Antialiasing, false);
|
||||
|
||||
if (!graphicsItem->isEnabled()) {
|
||||
painter->setOpacity(0.5);
|
||||
}
|
||||
|
||||
this->paintRegionNameAnnotation(painter, hexViewerState, graphicsItem);
|
||||
|
||||
if (this->focusedMemoryRegion.dataType != MemoryRegionDataType::UNKNOWN) {
|
||||
// Paint the value annotation
|
||||
this->paintValueAnnotation(painter, hexViewerState, graphicsItem);
|
||||
}
|
||||
}
|
||||
|
||||
QMargins FocusedRegionGroupItem::groupMargins(
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const int maximumWidth
|
||||
) const {
|
||||
if (hexViewerState->settings.displayAnnotations) {
|
||||
constexpr auto averageSymbolWidth = 6;
|
||||
const auto nameLabelWidth = static_cast<int>(this->focusedMemoryRegion.name.size() * averageSymbolWidth);
|
||||
const auto valueLabelWidth = static_cast<int>(
|
||||
this->valueLabel.has_value() ? this->valueLabel->size() * averageSymbolWidth : 0
|
||||
);
|
||||
|
||||
const auto minimumWidth = std::min(std::max(nameLabelWidth, valueLabelWidth), maximumWidth);
|
||||
|
||||
const auto byteItemSize = (this->focusedMemoryRegion.addressRange.endAddress
|
||||
- this->focusedMemoryRegion.addressRange.startAddress + 1);
|
||||
const auto estimatedWidth = static_cast<int>(
|
||||
byteItemSize * (ByteItem::WIDTH + (ByteItem::RIGHT_MARGIN / 2))
|
||||
);
|
||||
|
||||
const auto annotationMargin = static_cast<int>(
|
||||
estimatedWidth < minimumWidth ? minimumWidth - estimatedWidth : 0
|
||||
);
|
||||
|
||||
return QMargins(
|
||||
annotationMargin / 2,
|
||||
this->focusedMemoryRegion.dataType != MemoryRegionDataType::UNKNOWN
|
||||
? FocusedRegionGroupItem::ANNOTATION_HEIGHT
|
||||
: 0,
|
||||
annotationMargin / 2,
|
||||
FocusedRegionGroupItem::ANNOTATION_HEIGHT
|
||||
);
|
||||
}
|
||||
|
||||
return QMargins(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
void FocusedRegionGroupItem::paintRegionNameAnnotation(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const {
|
||||
auto labelText = this->focusedMemoryRegion.name;
|
||||
|
||||
static const auto lineColor = QColor(0x4F, 0x4F, 0x4F);
|
||||
static const auto labelFontColor = QColor(0x68, 0x68, 0x68);
|
||||
|
||||
static auto labelFont = QFont("'Ubuntu', sans-serif");
|
||||
labelFont.setPixelSize(12);
|
||||
|
||||
painter->setFont(labelFont);
|
||||
|
||||
const auto groupWidth = this->groupSize.width();
|
||||
const auto fontMetrics = painter->fontMetrics();
|
||||
auto labelSize = fontMetrics.size(Qt::TextSingleLine, labelText);
|
||||
if (labelSize.width() > groupWidth) {
|
||||
labelSize.setWidth(groupWidth);
|
||||
labelText = fontMetrics.elidedText(
|
||||
labelText,
|
||||
Qt::TextElideMode::ElideRight,
|
||||
groupWidth
|
||||
);
|
||||
}
|
||||
|
||||
const auto heightOffset = this->groupSize.height() - FocusedRegionGroupItem::ANNOTATION_HEIGHT + 4;
|
||||
|
||||
const auto verticalLineYStart = static_cast<int>(heightOffset);
|
||||
const auto verticalLineYEnd = static_cast<int>(heightOffset + 5);
|
||||
|
||||
const auto labelRect = QRect(
|
||||
(groupWidth - labelSize.width()) / 2,
|
||||
verticalLineYEnd + 10,
|
||||
labelSize.width(),
|
||||
labelSize.height()
|
||||
);
|
||||
|
||||
painter->setPen(lineColor);
|
||||
|
||||
if (this->focusedMemoryRegion.addressRange.startAddress != this->focusedMemoryRegion.addressRange.endAddress) {
|
||||
const auto lineStartX = this->items.front()->relativePosition.x() + (ByteItem::WIDTH / 2);
|
||||
const auto lineEndX = this->multiLine
|
||||
? groupWidth - + (ByteItem::WIDTH / 2)
|
||||
: this->items.back()->relativePosition.x() + (ByteItem::WIDTH / 2);
|
||||
|
||||
painter->drawLine(QLine(
|
||||
lineStartX,
|
||||
verticalLineYStart,
|
||||
lineStartX,
|
||||
verticalLineYEnd
|
||||
));
|
||||
|
||||
painter->drawLine(QLine(
|
||||
lineEndX,
|
||||
verticalLineYStart,
|
||||
lineEndX,
|
||||
verticalLineYEnd
|
||||
));
|
||||
|
||||
painter->drawLine(QLine(
|
||||
lineStartX,
|
||||
verticalLineYEnd,
|
||||
lineEndX,
|
||||
verticalLineYEnd
|
||||
));
|
||||
}
|
||||
|
||||
painter->drawLine(QLine(
|
||||
groupWidth / 2,
|
||||
verticalLineYEnd,
|
||||
groupWidth / 2,
|
||||
verticalLineYEnd + 4
|
||||
));
|
||||
|
||||
painter->setPen(labelFontColor);
|
||||
painter->drawText(labelRect, Qt::AlignCenter, labelText);
|
||||
}
|
||||
|
||||
void FocusedRegionGroupItem::paintValueAnnotation(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const {
|
||||
using Targets::TargetMemoryEndianness;
|
||||
|
||||
auto labelText = this->valueLabel.value_or("??");
|
||||
|
||||
static const auto lineColor = QColor(0x4F, 0x4F, 0x4F);
|
||||
static const auto labelFontColor = QColor(0x94, 0x6F, 0x30);
|
||||
|
||||
static auto labelFont = QFont("'Ubuntu', sans-serif");
|
||||
labelFont.setPixelSize(12);
|
||||
labelFont.setItalic(true);
|
||||
|
||||
painter->setFont(labelFont);
|
||||
|
||||
const auto groupWidth = this->groupSize.width();
|
||||
const auto fontMetrics = painter->fontMetrics();
|
||||
auto labelSize = fontMetrics.size(Qt::TextSingleLine, labelText);
|
||||
if (labelSize.width() > groupWidth) {
|
||||
labelSize.setWidth(groupWidth);
|
||||
labelText = fontMetrics.elidedText(
|
||||
labelText,
|
||||
Qt::TextElideMode::ElideRight,
|
||||
groupWidth
|
||||
);
|
||||
}
|
||||
|
||||
const auto heightOffset = FocusedRegionGroupItem::ANNOTATION_HEIGHT - 4;
|
||||
|
||||
const auto verticalLineYStart = static_cast<int>(heightOffset - 5);
|
||||
const auto verticalLineYEnd = static_cast<int>(heightOffset);
|
||||
|
||||
const auto labelRect = QRect(
|
||||
(groupWidth - labelSize.width()) / 2,
|
||||
verticalLineYStart - 10 - labelSize.height(),
|
||||
labelSize.width(),
|
||||
labelSize.height()
|
||||
);
|
||||
|
||||
painter->setPen(lineColor);
|
||||
|
||||
if (this->focusedMemoryRegion.addressRange.startAddress != this->focusedMemoryRegion.addressRange.endAddress) {
|
||||
const auto lineStartX = this->items.front()->relativePosition.x() + (ByteItem::WIDTH / 2);
|
||||
const auto lineEndX = this->multiLine
|
||||
? groupWidth - + (ByteItem::WIDTH / 2)
|
||||
: this->items.back()->relativePosition.x() + (ByteItem::WIDTH / 2);
|
||||
|
||||
painter->drawLine(QLine(
|
||||
lineStartX,
|
||||
verticalLineYStart,
|
||||
lineStartX,
|
||||
verticalLineYEnd
|
||||
));
|
||||
|
||||
painter->drawLine(QLine(
|
||||
lineEndX,
|
||||
verticalLineYStart,
|
||||
lineEndX,
|
||||
verticalLineYEnd
|
||||
));
|
||||
|
||||
painter->drawLine(QLine(
|
||||
lineStartX,
|
||||
verticalLineYStart,
|
||||
lineEndX,
|
||||
verticalLineYStart
|
||||
));
|
||||
|
||||
/*
|
||||
* Draw a circle just above the first byte item of the region, to indicate the first byte used to generate
|
||||
* the value (which will depend on the configured endianness of the region).
|
||||
*/
|
||||
painter->setBrush(lineColor);
|
||||
|
||||
constexpr auto radius = 2;
|
||||
painter->drawEllipse(
|
||||
QPoint(
|
||||
this->focusedMemoryRegion.endianness == TargetMemoryEndianness::BIG ? lineStartX : lineEndX,
|
||||
!this->multiLine || this->focusedMemoryRegion.endianness == TargetMemoryEndianness::BIG
|
||||
? verticalLineYStart
|
||||
: this->groupSize.height() - FocusedRegionGroupItem::ANNOTATION_HEIGHT + 4 + 5
|
||||
),
|
||||
radius,
|
||||
radius
|
||||
);
|
||||
}
|
||||
|
||||
painter->drawLine(QLine(
|
||||
groupWidth / 2,
|
||||
verticalLineYStart - 4,
|
||||
groupWidth / 2,
|
||||
verticalLineYStart
|
||||
));
|
||||
|
||||
painter->setPen(labelFontColor);
|
||||
painter->drawText(labelRect, Qt::AlignCenter, labelText);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <QString>
|
||||
|
||||
#include "GroupItem.hpp"
|
||||
#include "ByteItem.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class FocusedRegionGroupItem: public GroupItem
|
||||
{
|
||||
public:
|
||||
FocusedRegionGroupItem(
|
||||
const FocusedMemoryRegion& focusedRegion,
|
||||
std::unordered_map<Targets::TargetMemoryAddress, ByteItem>& byteItemsByAddress,
|
||||
HexViewerItem* parent
|
||||
);
|
||||
|
||||
void refreshValue(const HexViewerSharedState& hexViewerState);
|
||||
|
||||
void paint(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const override;
|
||||
|
||||
protected:
|
||||
QMargins groupMargins(const HexViewerSharedState* hexViewerState, const int maximumWidth) const override;
|
||||
|
||||
private:
|
||||
static constexpr int ANNOTATION_HEIGHT = 35;
|
||||
|
||||
const FocusedMemoryRegion& focusedMemoryRegion;
|
||||
std::optional<QString> valueLabel;
|
||||
|
||||
__attribute__((always_inline)) inline void paintRegionNameAnnotation(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const;
|
||||
|
||||
__attribute__((always_inline)) inline void paintValueAnnotation(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGraphicsItem>
|
||||
#include <QPainter>
|
||||
|
||||
#include "HexViewerItem.hpp"
|
||||
#include "HexViewerSharedState.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class GraphicsItem: public QGraphicsItem
|
||||
{
|
||||
public:
|
||||
HexViewerItem* hexViewerItem = nullptr;
|
||||
|
||||
GraphicsItem(const HexViewerSharedState* hexViewerState)
|
||||
: hexViewerState(hexViewerState)
|
||||
{
|
||||
this->setAcceptHoverEvents(true);
|
||||
this->setCacheMode(QGraphicsItem::CacheMode::NoCache);
|
||||
}
|
||||
|
||||
void setHexViewerItem(HexViewerItem* item) {
|
||||
if (this->hexViewerItem != nullptr) {
|
||||
this->hexViewerItem->allocatedGraphicsItem = nullptr;
|
||||
}
|
||||
|
||||
this->hexViewerItem = item;
|
||||
|
||||
if (this->hexViewerItem == nullptr) {
|
||||
this->setVisible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this->hexViewerItem->allocatedGraphicsItem = this;
|
||||
this->setPos(this->hexViewerItem->position());
|
||||
this->setVisible(true);
|
||||
this->update();
|
||||
}
|
||||
|
||||
[[nodiscard]] QRectF boundingRect() const override {
|
||||
if (this->hexViewerItem != nullptr) {
|
||||
const auto itemSize = this->hexViewerItem->size();
|
||||
return QRectF(0, 0, itemSize.width(), itemSize.height());
|
||||
}
|
||||
|
||||
return QRectF();
|
||||
}
|
||||
|
||||
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override {
|
||||
if (this->hexViewerItem != nullptr) {
|
||||
return this->hexViewerItem->paint(painter, this->hexViewerState, this);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const HexViewerSharedState* hexViewerState;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
#include "GroupItem.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
GroupItem::~GroupItem() {
|
||||
for (auto& byteItem : this->items) {
|
||||
byteItem->parent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void GroupItem::adjustItemPositions(const int maximumWidth, const HexViewerSharedState* hexViewerState) {
|
||||
const auto margins = this->groupMargins(hexViewerState, maximumWidth);
|
||||
|
||||
int width = margins.left();
|
||||
int height = margins.top();
|
||||
|
||||
this->multiLine = false;
|
||||
auto position = QPoint(margins.left(), margins.top());
|
||||
|
||||
auto currentLineItems = std::vector<HexViewerItem*>();
|
||||
const ByteItem* lastByteItem = nullptr;
|
||||
|
||||
for (const auto& item : this->items) {
|
||||
auto* const groupItem = dynamic_cast<GroupItem*>(item);
|
||||
if (groupItem != nullptr) {
|
||||
groupItem->adjustItemPositions(maximumWidth, hexViewerState);
|
||||
}
|
||||
|
||||
const ByteItem* byteItem = groupItem != nullptr
|
||||
? groupItem->firstByteItem()
|
||||
: dynamic_cast<ByteItem*>(item);
|
||||
|
||||
const auto itemSize = item->size();
|
||||
const auto availableWidth = maximumWidth - margins.right() - position.x();
|
||||
|
||||
if (
|
||||
(groupItem != nullptr && groupItem->positionOnNewLine(maximumWidth))
|
||||
|| itemSize.width() > availableWidth
|
||||
) {
|
||||
position.setX(0);
|
||||
position.setY(height + HexViewerItem::BOTTOM_MARGIN);
|
||||
this->multiLine = true;
|
||||
currentLineItems.clear();
|
||||
}
|
||||
|
||||
item->relativePosition = position;
|
||||
|
||||
if (byteItem != nullptr && lastByteItem != nullptr && !currentLineItems.empty()) {
|
||||
// Align byte items on each row
|
||||
const auto offset = lastByteItem->position().y() - byteItem->position().y();
|
||||
|
||||
if (offset > 0) {
|
||||
item->relativePosition.setY(item->relativePosition.y() + offset);
|
||||
}
|
||||
|
||||
if (offset < 0) {
|
||||
for (auto& item : currentLineItems) {
|
||||
item->relativePosition.setY(item->relativePosition.y() + std::abs(offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
height = std::max(static_cast<int>(position.y() + itemSize.height()), height);
|
||||
width = std::max(static_cast<int>(position.x() + itemSize.width()), width);
|
||||
|
||||
position.setX(static_cast<int>(position.x() + itemSize.width() + HexViewerItem::RIGHT_MARGIN));
|
||||
|
||||
currentLineItems.push_back(item);
|
||||
lastByteItem = byteItem;
|
||||
}
|
||||
|
||||
this->groupSize = QSize(width + margins.right(), height + margins.bottom());
|
||||
}
|
||||
|
||||
std::vector<HexViewerItem*> GroupItem::flattenedItems() const {
|
||||
auto flattenedItems = std::vector<HexViewerItem*>();
|
||||
|
||||
for (const auto& item : this->items) {
|
||||
flattenedItems.push_back(item);
|
||||
|
||||
const auto* groupItem = dynamic_cast<const GroupItem*>(item);
|
||||
if (groupItem != nullptr) {
|
||||
auto groupFlattenedItems = groupItem->flattenedItems();
|
||||
std::move(groupFlattenedItems.begin(), groupFlattenedItems.end(), std::back_inserter(flattenedItems));
|
||||
}
|
||||
}
|
||||
|
||||
return flattenedItems;
|
||||
}
|
||||
|
||||
GroupItem::GroupItem(
|
||||
Targets::TargetMemoryAddress startAddress,
|
||||
HexViewerItem* parent
|
||||
)
|
||||
: HexViewerItem(startAddress, parent)
|
||||
{}
|
||||
|
||||
ByteItem* GroupItem::firstByteItem() const {
|
||||
for (const auto& item : this->items) {
|
||||
auto* byteItem = dynamic_cast<ByteItem*>(item);
|
||||
if (byteItem != nullptr) {
|
||||
return byteItem;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void GroupItem::sortItems() {
|
||||
std::sort(
|
||||
this->items.begin(),
|
||||
this->items.end(),
|
||||
[] (const HexViewerItem* itemA, const HexViewerItem* itemB) {
|
||||
return itemA->startAddress < itemB->startAddress;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPainter>
|
||||
#include <QSize>
|
||||
#include <QMargins>
|
||||
#include <vector>
|
||||
|
||||
#include "HexViewerItem.hpp"
|
||||
#include "ByteItem.hpp"
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
#include "HexViewerSharedState.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class GroupItem: public HexViewerItem
|
||||
{
|
||||
public:
|
||||
~GroupItem();
|
||||
|
||||
QSize size() const override {
|
||||
return this->groupSize;
|
||||
}
|
||||
|
||||
void adjustItemPositions(const int maximumWidth, const HexViewerSharedState* hexViewerState);
|
||||
|
||||
[[nodiscard]] std::vector<HexViewerItem*> flattenedItems() const;
|
||||
|
||||
void paint(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const override {
|
||||
return;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::vector<HexViewerItem*> items;
|
||||
QSize groupSize = {};
|
||||
bool multiLine = false;
|
||||
|
||||
GroupItem(
|
||||
Targets::TargetMemoryAddress startAddress,
|
||||
HexViewerItem* parent = nullptr
|
||||
);
|
||||
|
||||
virtual QMargins groupMargins(const HexViewerSharedState* hexViewerState, const int maximumWidth) const {
|
||||
return QMargins(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
virtual bool positionOnNewLine(const int maximumWidth) {
|
||||
return this->multiLine;
|
||||
}
|
||||
|
||||
ByteItem* firstByteItem() const;
|
||||
|
||||
void sortItems();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <QSize>
|
||||
#include <QPainter>
|
||||
#include <QGraphicsItem>
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
#include "HexViewerSharedState.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class GraphicsItem;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
class HexViewerItem
|
||||
{
|
||||
public:
|
||||
static constexpr int RIGHT_MARGIN = 5;
|
||||
static constexpr int BOTTOM_MARGIN = 5;
|
||||
|
||||
const Targets::TargetMemoryAddress startAddress = 0;
|
||||
|
||||
QPoint relativePosition = {};
|
||||
|
||||
HexViewerItem* parent = nullptr;
|
||||
GraphicsItem* allocatedGraphicsItem = nullptr;
|
||||
|
||||
HexViewerItem(
|
||||
Targets::TargetMemoryAddress startAddress,
|
||||
HexViewerItem* parent = nullptr
|
||||
)
|
||||
: startAddress(startAddress)
|
||||
, parent(parent)
|
||||
{};
|
||||
|
||||
virtual ~HexViewerItem() = default;
|
||||
|
||||
QPoint position() const {
|
||||
if (this->parent != nullptr) {
|
||||
return this->parent->position() + this->relativePosition;
|
||||
}
|
||||
|
||||
return this->relativePosition;
|
||||
}
|
||||
|
||||
virtual QSize size() const = 0;
|
||||
|
||||
virtual void paint(
|
||||
QPainter* painter,
|
||||
const HexViewerSharedState* hexViewerState,
|
||||
const QGraphicsItem* graphicsItem
|
||||
) const = 0;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
#include "HexViewerWidgetSettings.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class ByteItem;
|
||||
|
||||
struct HexViewerSharedState
|
||||
{
|
||||
public:
|
||||
const Targets::TargetMemoryDescriptor& memoryDescriptor;
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data;
|
||||
|
||||
HexViewerWidgetSettings& settings;
|
||||
|
||||
ByteItem* hoveredByteItem = nullptr;
|
||||
std::optional<Targets::TargetStackPointer> currentStackPointer;
|
||||
|
||||
HexViewerSharedState(
|
||||
const Targets::TargetMemoryDescriptor& memoryDescriptor,
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data,
|
||||
HexViewerWidgetSettings& settings
|
||||
)
|
||||
: memoryDescriptor(memoryDescriptor)
|
||||
, data(data)
|
||||
, settings(settings)
|
||||
{}
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "HexViewerWidget.hpp"
|
||||
|
||||
#include <QFile>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/UiLoader.hpp"
|
||||
#include "src/Insight/InsightSignals.hpp"
|
||||
@@ -16,6 +17,7 @@ namespace Bloom::Widgets
|
||||
|
||||
HexViewerWidget::HexViewerWidget(
|
||||
const TargetMemoryDescriptor& targetMemoryDescriptor,
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data,
|
||||
HexViewerWidgetSettings& settings,
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
|
||||
@@ -23,6 +25,7 @@ namespace Bloom::Widgets
|
||||
)
|
||||
: QWidget(parent)
|
||||
, targetMemoryDescriptor(targetMemoryDescriptor)
|
||||
, data(data)
|
||||
, settings(settings)
|
||||
, focusedMemoryRegions(focusedMemoryRegions)
|
||||
, excludedMemoryRegions(excludedMemoryRegions)
|
||||
@@ -75,8 +78,9 @@ namespace Bloom::Widgets
|
||||
|
||||
this->loadingHexViewerLabel = this->container->findChild<Label*>("loading-hex-viewer-label");
|
||||
|
||||
this->byteItemGraphicsView = new ByteItemContainerGraphicsView(
|
||||
this->byteItemGraphicsView = new ItemGraphicsView(
|
||||
this->targetMemoryDescriptor,
|
||||
this->data,
|
||||
this->focusedMemoryRegions,
|
||||
this->excludedMemoryRegions,
|
||||
this->settings,
|
||||
@@ -84,6 +88,7 @@ namespace Bloom::Widgets
|
||||
this->container
|
||||
);
|
||||
|
||||
this->byteItemGraphicsView->hide();
|
||||
containerLayout->insertWidget(2, this->byteItemGraphicsView);
|
||||
|
||||
this->setHoveredRowAndColumnHighlightingEnabled(this->settings.highlightHoveredRowAndCol);
|
||||
@@ -172,7 +177,7 @@ namespace Bloom::Widgets
|
||||
void HexViewerWidget::init() {
|
||||
QObject::connect(
|
||||
this->byteItemGraphicsView,
|
||||
&ByteItemContainerGraphicsView::sceneReady,
|
||||
&ItemGraphicsView::sceneReady,
|
||||
this,
|
||||
[this] {
|
||||
this->byteItemGraphicsScene = this->byteItemGraphicsView->getScene();
|
||||
@@ -186,9 +191,9 @@ namespace Bloom::Widgets
|
||||
this->byteItemGraphicsView->initScene();
|
||||
}
|
||||
|
||||
void HexViewerWidget::updateValues(const Targets::TargetMemoryBuffer& buffer) {
|
||||
void HexViewerWidget::updateValues() {
|
||||
if (this->byteItemGraphicsScene != nullptr) {
|
||||
this->byteItemGraphicsScene->updateValues(buffer);
|
||||
this->byteItemGraphicsScene->refreshValues();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,7 +230,7 @@ namespace Bloom::Widgets
|
||||
this->settings.highlightStackMemory = enabled;
|
||||
|
||||
if (this->byteItemGraphicsScene != nullptr) {
|
||||
this->byteItemGraphicsScene->invalidateChildItemCaches();
|
||||
this->byteItemGraphicsScene->update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,7 +239,7 @@ namespace Bloom::Widgets
|
||||
this->settings.highlightHoveredRowAndCol = enabled;
|
||||
|
||||
if (this->byteItemGraphicsScene != nullptr) {
|
||||
this->byteItemGraphicsScene->invalidateChildItemCaches();
|
||||
this->byteItemGraphicsScene->update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,7 +248,7 @@ namespace Bloom::Widgets
|
||||
this->settings.highlightFocusedMemory = enabled;
|
||||
|
||||
if (this->byteItemGraphicsScene != nullptr) {
|
||||
this->byteItemGraphicsScene->invalidateChildItemCaches();
|
||||
this->byteItemGraphicsScene->update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +257,7 @@ namespace Bloom::Widgets
|
||||
this->settings.displayAnnotations = enabled;
|
||||
|
||||
if (this->byteItemGraphicsScene != nullptr) {
|
||||
this->byteItemGraphicsScene->adjustSize(true);
|
||||
this->byteItemGraphicsScene->adjustSize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +266,7 @@ namespace Bloom::Widgets
|
||||
this->settings.displayAsciiValues = enabled;
|
||||
|
||||
if (this->byteItemGraphicsScene != nullptr) {
|
||||
this->byteItemGraphicsScene->invalidateChildItemCaches();
|
||||
this->byteItemGraphicsScene->update();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,11 +281,11 @@ namespace Bloom::Widgets
|
||||
const auto& memoryAddressRange = this->targetMemoryDescriptor.addressRange;
|
||||
|
||||
if (addressConversionOk && memoryAddressRange.contains(address) && this->goToAddressInput->hasFocus()) {
|
||||
this->byteItemGraphicsScene->setHighlightedAddresses({address});
|
||||
this->byteItemGraphicsScene->selectByteItems({address});
|
||||
this->byteItemGraphicsView->scrollToByteItemAtAddress(address);
|
||||
return;
|
||||
}
|
||||
|
||||
this->byteItemGraphicsScene->setHighlightedAddresses({});
|
||||
this->byteItemGraphicsScene->selectByteItems({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TextInput.hpp"
|
||||
|
||||
#include "HexViewerWidgetSettings.hpp"
|
||||
#include "ByteItemContainerGraphicsView.hpp"
|
||||
#include "ItemGraphicsView.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegion.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
|
||||
@@ -28,6 +28,7 @@ namespace Bloom::Widgets
|
||||
public:
|
||||
HexViewerWidget(
|
||||
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data,
|
||||
HexViewerWidgetSettings& settings,
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
|
||||
@@ -35,7 +36,7 @@ namespace Bloom::Widgets
|
||||
);
|
||||
|
||||
void init();
|
||||
void updateValues(const Targets::TargetMemoryBuffer& buffer);
|
||||
void updateValues();
|
||||
void refreshRegions();
|
||||
void setStackPointer(Targets::TargetStackPointer stackPointer);
|
||||
|
||||
@@ -48,6 +49,8 @@ namespace Bloom::Widgets
|
||||
|
||||
private:
|
||||
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor;
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data;
|
||||
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions;
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions;
|
||||
|
||||
@@ -58,8 +61,8 @@ namespace Bloom::Widgets
|
||||
QWidget* bottomBar = nullptr;
|
||||
|
||||
Label* loadingHexViewerLabel = nullptr;
|
||||
ByteItemContainerGraphicsView* byteItemGraphicsView = nullptr;
|
||||
ByteItemGraphicsScene* byteItemGraphicsScene = nullptr;
|
||||
ItemGraphicsView* byteItemGraphicsView = nullptr;
|
||||
ItemGraphicsScene* byteItemGraphicsScene = nullptr;
|
||||
Label* hoveredAddressLabel = nullptr;
|
||||
|
||||
SvgToolButton* highlightStackMemoryButton = nullptr;
|
||||
|
||||
@@ -0,0 +1,786 @@
|
||||
#include "ItemGraphicsScene.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <QMenu>
|
||||
#include <iterator>
|
||||
#include <unordered_set>
|
||||
#include <algorithm>
|
||||
#include <QApplication>
|
||||
#include <QClipboard>
|
||||
#include <QByteArray>
|
||||
|
||||
#include "src/Insight/InsightWorker/InsightWorker.hpp"
|
||||
#include "src/Insight/InsightSignals.hpp"
|
||||
|
||||
#include "src/Insight/InsightWorker/Tasks/ConstructHexViewerTopLevelGroupItem.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
ItemGraphicsScene::ItemGraphicsScene(
|
||||
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data,
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
|
||||
HexViewerWidgetSettings& settings,
|
||||
Label* hoveredAddressLabel,
|
||||
QGraphicsView* parent
|
||||
)
|
||||
: state(
|
||||
HexViewerSharedState(
|
||||
targetMemoryDescriptor,
|
||||
data,
|
||||
settings
|
||||
)
|
||||
)
|
||||
, focusedMemoryRegions(focusedMemoryRegions)
|
||||
, excludedMemoryRegions(excludedMemoryRegions)
|
||||
, hoveredAddressLabel(hoveredAddressLabel)
|
||||
, parent(parent)
|
||||
, QGraphicsScene(parent)
|
||||
{
|
||||
this->setObjectName("byte-widget-container");
|
||||
|
||||
this->byteAddressContainer = new ByteAddressContainer(this->state);
|
||||
this->addItem(this->byteAddressContainer);
|
||||
|
||||
this->displayRelativeAddressAction->setCheckable(true);
|
||||
this->displayAbsoluteAddressAction->setCheckable(true);
|
||||
|
||||
this->setAddressType(this->state.settings.addressLabelType);
|
||||
|
||||
QObject::connect(
|
||||
InsightSignals::instance(),
|
||||
&InsightSignals::targetStateUpdated,
|
||||
this,
|
||||
&ItemGraphicsScene::onTargetStateChanged
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->displayRelativeAddressAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
[this] {
|
||||
this->setAddressType(AddressType::RELATIVE);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->displayAbsoluteAddressAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
[this] {
|
||||
this->setAddressType(AddressType::ABSOLUTE);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->selectAllByteItemsAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
&ItemGraphicsScene::selectAllByteItems
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->deselectByteItemsAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
&ItemGraphicsScene::clearByteItemSelection
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->copyAbsoluteAddressAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
[this] {
|
||||
this->copyAddressesToClipboard(AddressType::ABSOLUTE);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->copyRelativeAddressAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
[this] {
|
||||
this->copyAddressesToClipboard(AddressType::RELATIVE);
|
||||
}
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->copyHexValuesAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
&ItemGraphicsScene::copyHexValuesToClipboard
|
||||
);
|
||||
|
||||
QObject::connect(
|
||||
this->copyDecimalValuesAction,
|
||||
&QAction::triggered,
|
||||
this,
|
||||
&ItemGraphicsScene::copyDecimalValuesToClipboard
|
||||
);
|
||||
|
||||
this->setSceneRect(0, 0, this->getSceneWidth(), 0);
|
||||
|
||||
static const auto hoverRectBackgroundColor = QColor(0x8E, 0x8B, 0x83, 30);
|
||||
|
||||
this->hoverRectX->setBrush(hoverRectBackgroundColor);
|
||||
this->hoverRectY->setBrush(hoverRectBackgroundColor);
|
||||
this->hoverRectX->setPen(Qt::NoPen);
|
||||
this->hoverRectY->setPen(Qt::NoPen);
|
||||
|
||||
this->hoverRectX->setVisible(false);
|
||||
this->hoverRectY->setVisible(false);
|
||||
|
||||
this->hoverRectX->setZValue(100);
|
||||
this->hoverRectY->setZValue(100);
|
||||
|
||||
this->addItem(this->hoverRectX);
|
||||
this->addItem(this->hoverRectY);
|
||||
|
||||
this->setItemIndexMethod(QGraphicsScene::NoIndex);
|
||||
|
||||
this->allocateGraphicsItemsTimer = new QTimer(this);
|
||||
this->allocateGraphicsItemsTimer->setSingleShot(true);
|
||||
this->allocateGraphicsItemsTimer->setInterval(60);
|
||||
this->allocateGraphicsItemsTimer->callOnTimeout(this, [this] {
|
||||
this->allocateGraphicsItems();
|
||||
});
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::init() {
|
||||
auto* constructHexViewerTopLevelGroupItem = new ConstructHexViewerTopLevelGroupItem(this->focusedMemoryRegions, this->state);
|
||||
|
||||
QObject::connect(
|
||||
constructHexViewerTopLevelGroupItem,
|
||||
&ConstructHexViewerTopLevelGroupItem::topLevelGroupItem,
|
||||
this,
|
||||
[this] (TopLevelGroupItem* item) {
|
||||
this->topLevelGroup.reset(item);
|
||||
this->topLevelGroup->setPosition(QPoint(ByteAddressContainer::WIDTH + this->margins.left(), this->margins.top()));
|
||||
this->flattenedItems = this->topLevelGroup->flattenedItems();
|
||||
emit this->ready();
|
||||
}
|
||||
);
|
||||
|
||||
InsightWorker::queueTask(constructHexViewerTopLevelGroupItem);
|
||||
|
||||
auto* vScrollBar = this->views().first()->verticalScrollBar();
|
||||
vScrollBar->setSingleStep((ByteItem::HEIGHT + (ByteItem::BOTTOM_MARGIN / 2)));
|
||||
|
||||
QObject::connect(
|
||||
vScrollBar,
|
||||
&QScrollBar::valueChanged,
|
||||
this,
|
||||
[this] (int) {
|
||||
this->allocateGraphicsItemsTimer->start();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::updateStackPointer(std::uint32_t stackPointer) {
|
||||
this->state.currentStackPointer = stackPointer;
|
||||
this->update();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::selectByteItems(const std::set<std::uint32_t>& addresses) {
|
||||
for (auto& [address, byteItem] : this->topLevelGroup->byteItemsByAddress) {
|
||||
if (addresses.contains(address)) {
|
||||
byteItem.selected = true;
|
||||
|
||||
} else if (byteItem.selected) {
|
||||
byteItem.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
this->update();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::refreshRegions() {
|
||||
this->topLevelGroup->rebuildItemHierarchy();
|
||||
this->flattenedItems = this->topLevelGroup->flattenedItems();
|
||||
this->adjustSize();
|
||||
return;
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::adjustSize() {
|
||||
const auto width = this->getSceneWidth();
|
||||
const auto availableWidth = width - ByteAddressContainer::WIDTH - this->margins.left() - this->margins.right();
|
||||
|
||||
auto hoverRectX = this->hoverRectX->rect();
|
||||
hoverRectX.setWidth(width);
|
||||
this->hoverRectX->setRect(hoverRectX);
|
||||
|
||||
auto hoverRectY = this->hoverRectY->rect();
|
||||
hoverRectY.setHeight(this->views().first()->viewport()->height() + (ByteItem::HEIGHT * 2));
|
||||
this->hoverRectY->setRect(hoverRectY);
|
||||
|
||||
this->topLevelGroup->adjustItemPositions(availableWidth);
|
||||
|
||||
this->setSceneRect(
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
std::max(
|
||||
static_cast<int>(this->topLevelGroup->size().height())
|
||||
+ this->margins.top() + this->margins.bottom(),
|
||||
this->parent->height()
|
||||
)
|
||||
);
|
||||
|
||||
this->refreshItemPositionIndices();
|
||||
|
||||
this->byteAddressContainer->adjustAddressLabels(this->firstByteItemByLine);
|
||||
|
||||
const auto* view = this->views().first();
|
||||
const auto itemsRequired = static_cast<std::uint32_t>(
|
||||
(availableWidth / (ByteItem::WIDTH + (ByteItem::RIGHT_MARGIN / 2)))
|
||||
* (
|
||||
(view->viewport()->height() + (40 * ItemGraphicsScene::GRID_SIZE))
|
||||
/ (ByteItem::HEIGHT + (ByteItem::BOTTOM_MARGIN / 2))
|
||||
)
|
||||
);
|
||||
|
||||
while (this->graphicsItems.size() < itemsRequired) {
|
||||
auto* item = new GraphicsItem(&(this->state));
|
||||
item->setEnabled(this->enabled);
|
||||
this->graphicsItems.push_back(item);
|
||||
this->addItem(item);
|
||||
}
|
||||
|
||||
this->allocateGraphicsItems();
|
||||
this->update();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::setEnabled(bool enabled) {
|
||||
if (this->enabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->enabled = enabled;
|
||||
|
||||
for (auto& graphicsItem : this->graphicsItems) {
|
||||
graphicsItem->setEnabled(this->enabled);
|
||||
}
|
||||
|
||||
this->byteAddressContainer->setEnabled(enabled);
|
||||
this->byteAddressContainer->update();
|
||||
this->update();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::refreshValues() {
|
||||
this->topLevelGroup->refreshValues();
|
||||
this->update();
|
||||
}
|
||||
|
||||
QPointF ItemGraphicsScene::getByteItemPositionByAddress(std::uint32_t address) {
|
||||
const auto byteItemIt = this->topLevelGroup->byteItemsByAddress.find(address);
|
||||
if (byteItemIt != this->topLevelGroup->byteItemsByAddress.end()) {
|
||||
return byteItemIt->second.position();
|
||||
}
|
||||
|
||||
return QPointF();
|
||||
}
|
||||
|
||||
bool ItemGraphicsScene::event(QEvent* event) {
|
||||
if (event->type() == QEvent::Type::GraphicsSceneLeave && this->state.hoveredByteItem != nullptr) {
|
||||
this->onByteItemLeave();
|
||||
}
|
||||
|
||||
return QGraphicsScene::event(event);
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent* mouseEvent) {
|
||||
static const auto rubberBandRectBackgroundColor = QColor(0x3C, 0x59, 0x5C, 0x82);
|
||||
static const auto rubberBandRectBorderColor = QColor(0x3C, 0x59, 0x5C, 255);
|
||||
|
||||
if (mouseEvent->button() != Qt::MouseButton::LeftButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto mousePosition = mouseEvent->buttonDownScenePos(Qt::MouseButton::LeftButton);
|
||||
|
||||
if (mousePosition.x() <= this->byteAddressContainer->boundingRect().width()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->update();
|
||||
const auto modifiers = mouseEvent->modifiers();
|
||||
if ((modifiers & Qt::ShiftModifier) == 0) {
|
||||
this->clearSelectionRectItem();
|
||||
|
||||
this->rubberBandInitPoint = std::move(mousePosition);
|
||||
this->rubberBandRectItem = new QGraphicsRectItem(
|
||||
this->rubberBandInitPoint->x(),
|
||||
this->rubberBandInitPoint->y(),
|
||||
1,
|
||||
1
|
||||
);
|
||||
this->rubberBandRectItem->setBrush(rubberBandRectBackgroundColor);
|
||||
this->rubberBandRectItem->setPen(rubberBandRectBorderColor);
|
||||
this->addItem(this->rubberBandRectItem);
|
||||
}
|
||||
|
||||
if ((modifiers & (Qt::ControlModifier | Qt::ShiftModifier)) == 0) {
|
||||
this->clearByteItemSelection();
|
||||
}
|
||||
|
||||
for (const auto& item : this->items(mousePosition)) {
|
||||
auto* clickedGraphicsItem = dynamic_cast<GraphicsItem*>(item);
|
||||
|
||||
if (clickedGraphicsItem == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* byteItem = dynamic_cast<ByteItem*>(clickedGraphicsItem->hexViewerItem);
|
||||
|
||||
if (byteItem == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((modifiers & Qt::ShiftModifier) != 0) {
|
||||
for (
|
||||
auto i = byteItem->startAddress;
|
||||
i >= this->state.memoryDescriptor.addressRange.startAddress;
|
||||
--i
|
||||
) {
|
||||
auto& byteItem = this->topLevelGroup->byteItemsByAddress.at(i);
|
||||
|
||||
if (byteItem.selected) {
|
||||
break;
|
||||
}
|
||||
|
||||
this->toggleByteItemSelection(byteItem);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this->toggleByteItemSelection(*byteItem);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::mouseMoveEvent(QGraphicsSceneMouseEvent* mouseEvent) {
|
||||
const auto mousePosition = mouseEvent->scenePos();
|
||||
auto hoveredItems = this->items(mousePosition);
|
||||
|
||||
if (this->rubberBandRectItem != nullptr && this->rubberBandInitPoint.has_value()) {
|
||||
const auto oldRect = this->rubberBandRectItem->rect();
|
||||
|
||||
this->rubberBandRectItem->setRect(
|
||||
qMin(mousePosition.x(), this->rubberBandInitPoint->x()),
|
||||
qMin(mousePosition.y(), this->rubberBandInitPoint->y()),
|
||||
qAbs(mousePosition.x() - this->rubberBandInitPoint->x()),
|
||||
qAbs(mousePosition.y() - this->rubberBandInitPoint->y())
|
||||
);
|
||||
|
||||
if ((mouseEvent->modifiers() & Qt::ControlModifier) == 0) {
|
||||
this->clearByteItemSelection();
|
||||
|
||||
} else {
|
||||
const auto oldItems = this->items(oldRect, Qt::IntersectsItemShape);
|
||||
for (auto* item : oldItems) {
|
||||
auto* graphicsItem = dynamic_cast<GraphicsItem*>(item);
|
||||
|
||||
if (graphicsItem == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* byteItem = dynamic_cast<ByteItem*>(graphicsItem->hexViewerItem);
|
||||
|
||||
if (byteItem != nullptr && byteItem->selected) {
|
||||
this->deselectByteItem(*byteItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto items = this->items(this->rubberBandRectItem->rect(), Qt::IntersectsItemShape);
|
||||
for (auto* item : items) {
|
||||
auto* graphicsItem = dynamic_cast<GraphicsItem*>(item);
|
||||
|
||||
if (graphicsItem == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* byteItem = dynamic_cast<ByteItem*>(graphicsItem->hexViewerItem);
|
||||
|
||||
if (byteItem != nullptr && !byteItem->selected) {
|
||||
this->selectByteItem(*byteItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& item : hoveredItems) {
|
||||
auto* hoveredGraphicsItem = dynamic_cast<GraphicsItem*>(item);
|
||||
|
||||
if (hoveredGraphicsItem == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* hoveredByteItem = dynamic_cast<ByteItem*>(hoveredGraphicsItem->hexViewerItem);
|
||||
|
||||
if (hoveredByteItem != nullptr) {
|
||||
this->onByteItemEnter(*hoveredByteItem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this->state.hoveredByteItem != nullptr) {
|
||||
this->onByteItemLeave();
|
||||
}
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* mouseEvent) {
|
||||
this->clearSelectionRectItem();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::keyPressEvent(QKeyEvent* keyEvent) {
|
||||
const auto key = keyEvent->key();
|
||||
|
||||
if (key == Qt::Key_Escape) {
|
||||
this->clearByteItemSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto modifiers = keyEvent->modifiers();
|
||||
if ((modifiers & Qt::ControlModifier) != 0 && key == Qt::Key_A) {
|
||||
this->selectAllByteItems();
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsScene::keyPressEvent(keyEvent);
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
|
||||
if (event->scenePos().x() <= ByteAddressContainer::WIDTH) {
|
||||
auto* menu = new QMenu(this->parent);
|
||||
menu->setObjectName("byte-item-address-container-context-menu");
|
||||
|
||||
auto* addressTypeMenu = new QMenu("Address Type", menu);
|
||||
addressTypeMenu->addAction(this->displayAbsoluteAddressAction);
|
||||
addressTypeMenu->addAction(this->displayRelativeAddressAction);
|
||||
menu->addMenu(addressTypeMenu);
|
||||
|
||||
menu->exec(event->screenPos());
|
||||
return;
|
||||
}
|
||||
|
||||
const auto itemsSelected = !this->selectedByteItemsByAddress.empty();
|
||||
|
||||
auto* menu = new QMenu(this->parent);
|
||||
menu->addAction(this->selectAllByteItemsAction);
|
||||
menu->addAction(this->deselectByteItemsAction);
|
||||
menu->addSeparator();
|
||||
|
||||
auto* copyMenu = new QMenu("Copy Selected", menu);
|
||||
copyMenu->addAction(this->copyAbsoluteAddressAction);
|
||||
copyMenu->addAction(this->copyRelativeAddressAction);
|
||||
copyMenu->addSeparator();
|
||||
copyMenu->addAction(this->copyHexValuesAction);
|
||||
copyMenu->addAction(this->copyDecimalValuesAction);
|
||||
|
||||
copyMenu->setEnabled(itemsSelected);
|
||||
this->deselectByteItemsAction->setEnabled(itemsSelected);
|
||||
|
||||
menu->addMenu(copyMenu);
|
||||
menu->exec(event->screenPos());
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::allocateGraphicsItems() {
|
||||
const auto* view = this->views().first();
|
||||
const auto verticalScrollBarValue = view->verticalScrollBar()->value();
|
||||
|
||||
constexpr auto bufferPointSize = 20;
|
||||
const auto gridPointIndex = static_cast<decltype(this->gridPoints)::size_type>(std::max(
|
||||
static_cast<int>(
|
||||
std::floor(
|
||||
static_cast<float>(verticalScrollBarValue) / static_cast<float>(ItemGraphicsScene::GRID_SIZE)
|
||||
)
|
||||
) - 1 - bufferPointSize,
|
||||
0
|
||||
));
|
||||
|
||||
// Sanity check
|
||||
assert(this->gridPoints.size() > gridPointIndex);
|
||||
|
||||
const auto totalGraphicsItems = this->graphicsItems.size();
|
||||
|
||||
auto allocateRangeStartItemIt = this->gridPoints[gridPointIndex];
|
||||
const auto allocateRangeEndItemIt = allocateRangeStartItemIt + std::min(
|
||||
std::distance(allocateRangeStartItemIt, this->flattenedItems.end() - 1),
|
||||
static_cast<long>(totalGraphicsItems)
|
||||
);
|
||||
|
||||
const auto excessAvailableGraphicItems = static_cast<int>(totalGraphicsItems)
|
||||
- std::distance(allocateRangeStartItemIt, allocateRangeEndItemIt);
|
||||
|
||||
if (excessAvailableGraphicItems > 0) {
|
||||
allocateRangeStartItemIt -= std::min(
|
||||
std::distance(this->flattenedItems.begin(), allocateRangeStartItemIt),
|
||||
excessAvailableGraphicItems
|
||||
);
|
||||
}
|
||||
|
||||
const auto allocateRangeStartAddress = (*allocateRangeStartItemIt)->startAddress;
|
||||
const auto allocateRangeEndAddress = (*allocateRangeEndItemIt)->startAddress;
|
||||
|
||||
auto allocatableGraphicsItems = std::unordered_set<GraphicsItem*>();
|
||||
std::copy_if(
|
||||
this->graphicsItems.begin(),
|
||||
this->graphicsItems.end(),
|
||||
std::inserter(allocatableGraphicsItems, allocatableGraphicsItems.begin()),
|
||||
[&allocateRangeStartAddress, &allocateRangeEndAddress] (const GraphicsItem* graphicsItem) {
|
||||
return
|
||||
graphicsItem->hexViewerItem == nullptr
|
||||
|| graphicsItem->hexViewerItem->startAddress < allocateRangeStartAddress
|
||||
|| graphicsItem->hexViewerItem->startAddress > allocateRangeEndAddress;
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
* Ensure that a graphics item for each parent, grandparent, etc. is allocated for the first item in the
|
||||
* allocatable range.
|
||||
*/
|
||||
const auto& firstItem = *allocateRangeStartItemIt;
|
||||
auto* parentItem = firstItem->parent;
|
||||
|
||||
while (
|
||||
parentItem != nullptr
|
||||
&& parentItem != this->topLevelGroup.get()
|
||||
&& !allocatableGraphicsItems.empty()
|
||||
) {
|
||||
if (parentItem->allocatedGraphicsItem == nullptr) {
|
||||
(*allocatableGraphicsItems.begin())->setHexViewerItem(parentItem);
|
||||
}
|
||||
|
||||
allocatableGraphicsItems.erase(parentItem->allocatedGraphicsItem);
|
||||
parentItem = parentItem->parent;
|
||||
}
|
||||
|
||||
for (auto itemIt = allocateRangeStartItemIt; itemIt != allocateRangeEndItemIt; ++itemIt) {
|
||||
if (allocatableGraphicsItems.empty()) {
|
||||
// No more graphics items available to allocate
|
||||
break;
|
||||
}
|
||||
|
||||
auto& item = *itemIt;
|
||||
|
||||
if (item->allocatedGraphicsItem != nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
(*allocatableGraphicsItems.begin())->setHexViewerItem(item);
|
||||
allocatableGraphicsItems.erase(item->allocatedGraphicsItem);
|
||||
}
|
||||
|
||||
// If we still have some available graphics items, clear them
|
||||
for (auto& graphicsItem : allocatableGraphicsItems) {
|
||||
graphicsItem->setHexViewerItem(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::refreshItemPositionIndices() {
|
||||
const auto pointsRequired = static_cast<std::uint32_t>(
|
||||
this->sceneRect().height() / ItemGraphicsScene::GRID_SIZE
|
||||
);
|
||||
|
||||
this->gridPoints.clear();
|
||||
this->gridPoints.reserve(pointsRequired);
|
||||
|
||||
this->firstByteItemByLine.clear();
|
||||
|
||||
auto currentPoint = 0;
|
||||
auto currentLineYPosition = 0;
|
||||
for (auto itemIt = this->flattenedItems.begin(); itemIt != this->flattenedItems.end(); ++itemIt) {
|
||||
auto& item = *itemIt;
|
||||
|
||||
if (item->allocatedGraphicsItem != nullptr) {
|
||||
item->allocatedGraphicsItem->setPos(item->position());
|
||||
}
|
||||
|
||||
const auto byteItem = dynamic_cast<const ByteItem*>(item);
|
||||
|
||||
if (byteItem == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto itemYStartPosition = byteItem->position().y();
|
||||
const auto itemYEndPosition = itemYStartPosition + byteItem->size().height();
|
||||
|
||||
if (itemYStartPosition > currentLineYPosition) {
|
||||
this->firstByteItemByLine.push_back(byteItem);
|
||||
currentLineYPosition = itemYStartPosition;
|
||||
}
|
||||
|
||||
if (itemYEndPosition >= currentPoint) {
|
||||
// This byte item is the first to exceed or intersect with the currentPoint
|
||||
this->gridPoints.push_back(itemIt);
|
||||
currentPoint += ItemGraphicsScene::GRID_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::onTargetStateChanged(Targets::TargetState newState) {
|
||||
this->targetState = newState;
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::onByteItemEnter(ByteItem& byteItem) {
|
||||
if (this->state.hoveredByteItem != nullptr) {
|
||||
if (this->state.hoveredByteItem == &byteItem) {
|
||||
// This byteItem is already marked as hovered
|
||||
return;
|
||||
}
|
||||
|
||||
this->onByteItemLeave();
|
||||
}
|
||||
|
||||
this->state.hoveredByteItem = &byteItem;
|
||||
|
||||
const auto addressHex = "0x" + QString::number(
|
||||
byteItem.startAddress,
|
||||
16
|
||||
).rightJustified(8, '0').toUpper();
|
||||
|
||||
const auto relativeAddress = byteItem.startAddress - this->state.memoryDescriptor.addressRange.startAddress;
|
||||
const auto relativeAddressHex = "0x" + QString::number(
|
||||
relativeAddress,
|
||||
16
|
||||
).rightJustified(8, '0').toUpper();
|
||||
|
||||
this->hoveredAddressLabel->setText(
|
||||
"Relative Address / Absolute Address: " + relativeAddressHex + " / " + addressHex
|
||||
);
|
||||
|
||||
if (this->state.settings.highlightHoveredRowAndCol) {
|
||||
const auto byteItemScenePos = byteItem.position();
|
||||
this->hoverRectX->setPos(0, byteItemScenePos.y());
|
||||
this->hoverRectY->setPos(
|
||||
byteItemScenePos.x(),
|
||||
std::max(this->views().first()->verticalScrollBar()->value() - ByteItem::HEIGHT, 0)
|
||||
);
|
||||
|
||||
this->hoverRectX->setVisible(true);
|
||||
this->hoverRectY->setVisible(true);
|
||||
}
|
||||
|
||||
this->update();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::onByteItemLeave() {
|
||||
if (this->state.hoveredByteItem != nullptr) {
|
||||
this->state.hoveredByteItem = nullptr;
|
||||
}
|
||||
|
||||
this->hoveredAddressLabel->setText("Relative Address / Absolute Address:");
|
||||
|
||||
this->hoverRectX->setVisible(false);
|
||||
this->hoverRectY->setVisible(false);
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::clearSelectionRectItem() {
|
||||
if (this->rubberBandRectItem != nullptr) {
|
||||
this->removeItem(this->rubberBandRectItem);
|
||||
delete this->rubberBandRectItem;
|
||||
this->rubberBandRectItem = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::selectByteItem(ByteItem& byteItem) {
|
||||
byteItem.selected = true;
|
||||
this->selectedByteItemsByAddress.insert(std::pair(byteItem.startAddress, &byteItem));
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::deselectByteItem(ByteItem& byteItem) {
|
||||
byteItem.selected = false;
|
||||
this->selectedByteItemsByAddress.erase(byteItem.startAddress);
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::toggleByteItemSelection(ByteItem& byteItem) {
|
||||
if (byteItem.selected) {
|
||||
this->deselectByteItem(byteItem);
|
||||
return;
|
||||
}
|
||||
|
||||
this->selectByteItem(byteItem);
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::clearByteItemSelection() {
|
||||
for (auto& [address, byteItem] : this->selectedByteItemsByAddress) {
|
||||
byteItem->selected = false;
|
||||
}
|
||||
|
||||
this->selectedByteItemsByAddress.clear();
|
||||
this->update();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::selectAllByteItems() {
|
||||
for (auto& [address, byteItem] : this->topLevelGroup->byteItemsByAddress) {
|
||||
byteItem.selected = true;
|
||||
this->selectedByteItemsByAddress.insert(std::pair(byteItem.startAddress, &byteItem));
|
||||
// this->selectByteItem(byteItem);
|
||||
}
|
||||
|
||||
this->update();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::setAddressType(AddressType type) {
|
||||
this->state.settings.addressLabelType = type;
|
||||
|
||||
this->displayRelativeAddressAction->setChecked(this->state.settings.addressLabelType == AddressType::RELATIVE);
|
||||
this->displayAbsoluteAddressAction->setChecked(this->state.settings.addressLabelType == AddressType::ABSOLUTE);
|
||||
|
||||
this->byteAddressContainer->invalidateChildItemCaches();
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::copyAddressesToClipboard(AddressType type) {
|
||||
if (this->selectedByteItemsByAddress.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QString();
|
||||
const auto memoryStartAddress = this->state.memoryDescriptor.addressRange.startAddress;
|
||||
|
||||
for (const auto& [address, byteItem] : this->selectedByteItemsByAddress) {
|
||||
data.append(
|
||||
"0x" + QString::number(
|
||||
type == AddressType::RELATIVE
|
||||
? byteItem->startAddress - memoryStartAddress
|
||||
: byteItem->startAddress,
|
||||
16
|
||||
).rightJustified(8, '0').toUpper() + "\n"
|
||||
);
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(std::move(data));
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::copyHexValuesToClipboard() {
|
||||
if (this->selectedByteItemsByAddress.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QString();
|
||||
|
||||
for (const auto& [address, byteItem] : this->selectedByteItemsByAddress) {
|
||||
const auto byteIndex = byteItem->startAddress - this->state.memoryDescriptor.addressRange.startAddress;
|
||||
data.append("0x" + QString::number((*this->state.data)[byteIndex], 16).rightJustified(2, '0').toUpper() + "\n");
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(std::move(data));
|
||||
}
|
||||
|
||||
void ItemGraphicsScene::copyDecimalValuesToClipboard() {
|
||||
if (this->selectedByteItemsByAddress.empty() || !this->state.data.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = QString();
|
||||
|
||||
for (const auto& [address, byteItem] : this->selectedByteItemsByAddress) {
|
||||
const auto byteIndex = byteItem->startAddress - this->state.memoryDescriptor.addressRange.startAddress;
|
||||
data.append(QString::number((*this->state.data)[byteIndex], 10) + "\n");
|
||||
}
|
||||
|
||||
QApplication::clipboard()->setText(std::move(data));
|
||||
}
|
||||
}
|
||||
@@ -3,33 +3,32 @@
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsView>
|
||||
#include <QScrollBar>
|
||||
#include <QWidget>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsSceneWheelEvent>
|
||||
#include <QGraphicsSceneContextMenuEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <optional>
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QPointF>
|
||||
#include <QAction>
|
||||
#include <QTimer>
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
#include "src/Targets/TargetState.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp"
|
||||
|
||||
#include "GraphicsItem.hpp"
|
||||
#include "TopLevelGroupItem.hpp"
|
||||
#include "GroupItem.hpp"
|
||||
#include "ByteItem.hpp"
|
||||
#include "ByteAddressContainer.hpp"
|
||||
#include "AnnotationItem.hpp"
|
||||
#include "ValueAnnotationItem.hpp"
|
||||
#include "HexViewerWidgetSettings.hpp"
|
||||
|
||||
#include "HexViewerSharedState.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/MemoryRegion.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
|
||||
@@ -38,13 +37,14 @@
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class ByteItemGraphicsScene: public QGraphicsScene
|
||||
class ItemGraphicsScene: public QGraphicsScene
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ByteItemGraphicsScene(
|
||||
ItemGraphicsScene(
|
||||
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data,
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
|
||||
HexViewerWidgetSettings& settings,
|
||||
@@ -53,18 +53,16 @@ namespace Bloom::Widgets
|
||||
);
|
||||
|
||||
void init();
|
||||
void updateValues(const Targets::TargetMemoryBuffer& buffer);
|
||||
void updateStackPointer(Targets::TargetStackPointer stackPointer);
|
||||
void setHighlightedAddresses(const std::set<Targets::TargetMemoryAddress>& highlightedAddresses);
|
||||
void selectByteItems(const std::set<Targets::TargetMemoryAddress>& addresses);
|
||||
void refreshRegions();
|
||||
void adjustSize(bool forced = false);
|
||||
void adjustSize();
|
||||
void setEnabled(bool enabled);
|
||||
void invalidateChildItemCaches();
|
||||
void refreshValues();
|
||||
QPointF getByteItemPositionByAddress(Targets::TargetMemoryAddress address);
|
||||
|
||||
signals:
|
||||
void ready();
|
||||
void byteWidgetsAdjusted();
|
||||
|
||||
protected:
|
||||
bool event(QEvent* event) override;
|
||||
@@ -75,41 +73,40 @@ namespace Bloom::Widgets
|
||||
void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) override;
|
||||
|
||||
private:
|
||||
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor;
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions;
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions;
|
||||
static constexpr auto GRID_SIZE = 100;
|
||||
|
||||
bool enabled = true;
|
||||
|
||||
ByteItem* hoveredByteWidget = nullptr;
|
||||
AnnotationItem* hoveredAnnotationItem = nullptr;
|
||||
HexViewerSharedState state;
|
||||
|
||||
std::optional<Targets::TargetStackPointer> currentStackPointer;
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions;
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions;
|
||||
|
||||
Targets::TargetMemoryBuffer lastValueBuffer;
|
||||
std::unique_ptr<TopLevelGroupItem> topLevelGroup = nullptr;
|
||||
|
||||
std::map<Targets::TargetMemoryAddress, ByteItem*> byteItemsByAddress;
|
||||
std::vector<AnnotationItem*> annotationItems;
|
||||
std::vector<ValueAnnotationItem*> valueAnnotationItems;
|
||||
std::map<std::size_t, std::vector<ByteItem*>> byteItemsByRowIndex;
|
||||
std::map<std::size_t, std::vector<ByteItem*>> byteItemsByColumnIndex;
|
||||
std::vector<HexViewerItem*> flattenedItems;
|
||||
std::vector<decltype(ItemGraphicsScene::flattenedItems)::iterator> gridPoints;
|
||||
std::vector<const ByteItem*> firstByteItemByLine;
|
||||
|
||||
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
|
||||
std::vector<GraphicsItem*> graphicsItems;
|
||||
|
||||
const QMargins margins = QMargins(10, 10, 10, 10);
|
||||
HexViewerWidgetSettings& settings;
|
||||
|
||||
Targets::TargetState targetState = Targets::TargetState::UNKNOWN;
|
||||
|
||||
QGraphicsView* parent = nullptr;
|
||||
Label* hoveredAddressLabel = nullptr;
|
||||
|
||||
ByteAddressContainer* byteAddressContainer = nullptr;
|
||||
|
||||
std::set<ByteItem*> highlightedByteItems;
|
||||
std::map<Targets::TargetMemoryAddress, ByteItem*> selectedByteItemsByAddress;
|
||||
std::unordered_map<Targets::TargetMemoryAddress, ByteItem*> selectedByteItemsByAddress;
|
||||
|
||||
QGraphicsRectItem* rubberBandRectItem = nullptr;
|
||||
std::optional<QPointF> rubberBandInitPoint = std::nullopt;
|
||||
|
||||
QGraphicsRectItem* hoverRectX = new QGraphicsRectItem(QRect(0, 0, 0, ByteItem::HEIGHT));
|
||||
QGraphicsRectItem* hoverRectY = new QGraphicsRectItem(QRect(0, 0, ByteItem::WIDTH, 0));
|
||||
|
||||
// Context menu actions
|
||||
QAction* selectAllByteItemsAction = new QAction("Select All", this);
|
||||
QAction* deselectByteItemsAction = new QAction("Deselect All", this);
|
||||
@@ -122,6 +119,8 @@ namespace Bloom::Widgets
|
||||
QAction* displayRelativeAddressAction = new QAction("Relative", this);
|
||||
QAction* displayAbsoluteAddressAction = new QAction("Absolute", this);
|
||||
|
||||
QTimer* allocateGraphicsItemsTimer = nullptr;
|
||||
|
||||
int getSceneWidth() {
|
||||
/*
|
||||
* Minus 2 for the QSS margin on the vertical scrollbar (which isn't accounted for during viewport
|
||||
@@ -132,18 +131,15 @@ namespace Bloom::Widgets
|
||||
return std::max(this->parent->viewport()->width(), 400) - 2;
|
||||
}
|
||||
|
||||
void updateAnnotationValues(const Targets::TargetMemoryBuffer& buffer);
|
||||
void adjustByteItemPositions();
|
||||
void adjustAnnotationItemPositions();
|
||||
void allocateGraphicsItems();
|
||||
void refreshItemPositionIndices();
|
||||
void onTargetStateChanged(Targets::TargetState newState);
|
||||
void onByteWidgetEnter(Bloom::Widgets::ByteItem* widget);
|
||||
void onByteWidgetLeave();
|
||||
void onAnnotationItemEnter(Bloom::Widgets::AnnotationItem* annotationItem);
|
||||
void onAnnotationItemLeave();
|
||||
void onByteItemEnter(ByteItem& byteItem);
|
||||
void onByteItemLeave();
|
||||
void clearSelectionRectItem();
|
||||
void selectByteItem(ByteItem* byteItem);
|
||||
void deselectByteItem(ByteItem* byteItem);
|
||||
void toggleByteItemSelection(ByteItem* byteItem);
|
||||
void selectByteItem(ByteItem& byteItem);
|
||||
void deselectByteItem(ByteItem& byteItem);
|
||||
void toggleByteItemSelection(ByteItem& byteItem);
|
||||
void clearByteItemSelection();
|
||||
void selectAllByteItems();
|
||||
void setAddressType(AddressType type);
|
||||
@@ -1,14 +1,12 @@
|
||||
#include "ByteItemContainerGraphicsView.hpp"
|
||||
|
||||
#include "src/Insight/InsightWorker/InsightWorker.hpp"
|
||||
#include "src/Insight/InsightWorker/Tasks/ConstructHexViewerByteItems.hpp"
|
||||
#include "ItemGraphicsView.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
using Bloom::Targets::TargetMemoryDescriptor;
|
||||
|
||||
ByteItemContainerGraphicsView::ByteItemContainerGraphicsView(
|
||||
ItemGraphicsView::ItemGraphicsView(
|
||||
const TargetMemoryDescriptor& targetMemoryDescriptor,
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data,
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
|
||||
HexViewerWidgetSettings& settings,
|
||||
@@ -26,9 +24,11 @@ namespace Bloom::Widgets
|
||||
this->setOptimizationFlag(QGraphicsView::DontSavePainterState, true);
|
||||
this->setOptimizationFlag(QGraphicsView::DontAdjustForAntialiasing, true);
|
||||
this->setCacheMode(QGraphicsView::CacheBackground);
|
||||
this->setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
this->scene = new ByteItemGraphicsScene(
|
||||
this->scene = new ItemGraphicsScene(
|
||||
targetMemoryDescriptor,
|
||||
data,
|
||||
focusedMemoryRegions,
|
||||
excludedMemoryRegions,
|
||||
settings,
|
||||
@@ -39,10 +39,10 @@ namespace Bloom::Widgets
|
||||
this->setScene(this->scene);
|
||||
}
|
||||
|
||||
void ByteItemContainerGraphicsView::initScene() {
|
||||
void ItemGraphicsView::initScene() {
|
||||
QObject::connect(
|
||||
this->scene,
|
||||
&ByteItemGraphicsScene::ready,
|
||||
&ItemGraphicsScene::ready,
|
||||
this,
|
||||
[this] {
|
||||
this->scene->setEnabled(this->isEnabled());
|
||||
@@ -53,13 +53,13 @@ namespace Bloom::Widgets
|
||||
this->scene->init();
|
||||
}
|
||||
|
||||
void ByteItemContainerGraphicsView::scrollToByteItemAtAddress(Targets::TargetMemoryAddress address) {
|
||||
void ItemGraphicsView::scrollToByteItemAtAddress(Targets::TargetMemoryAddress address) {
|
||||
if (this->scene != nullptr) {
|
||||
this->centerOn(this->scene->getByteItemPositionByAddress(address));
|
||||
}
|
||||
}
|
||||
|
||||
bool ByteItemContainerGraphicsView::event(QEvent* event) {
|
||||
bool ItemGraphicsView::event(QEvent* event) {
|
||||
const auto eventType = event->type();
|
||||
if (eventType == QEvent::Type::EnabledChange && this->scene != nullptr) {
|
||||
this->scene->setEnabled(this->isEnabled());
|
||||
@@ -68,7 +68,7 @@ namespace Bloom::Widgets
|
||||
return QGraphicsView::event(event);
|
||||
}
|
||||
|
||||
void ByteItemContainerGraphicsView::resizeEvent(QResizeEvent* event) {
|
||||
void ItemGraphicsView::resizeEvent(QResizeEvent* event) {
|
||||
QGraphicsView::resizeEvent(event);
|
||||
|
||||
if (this->scene != nullptr) {
|
||||
@@ -1,25 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGraphicsView>
|
||||
#include <QWidget>
|
||||
#include <vector>
|
||||
#include <QEvent>
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp"
|
||||
#include "ByteItemGraphicsScene.hpp"
|
||||
#include "HexViewerWidgetSettings.hpp"
|
||||
#include "ItemGraphicsScene.hpp"
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/Label.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class ByteItemContainerGraphicsView: public QGraphicsView
|
||||
class ItemGraphicsView: public QGraphicsView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ByteItemContainerGraphicsView(
|
||||
ItemGraphicsView(
|
||||
const Targets::TargetMemoryDescriptor& targetMemoryDescriptor,
|
||||
const std::optional<Targets::TargetMemoryBuffer>& data,
|
||||
std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
std::vector<ExcludedMemoryRegion>& excludedMemoryRegions,
|
||||
HexViewerWidgetSettings& settings,
|
||||
@@ -29,7 +29,7 @@ namespace Bloom::Widgets
|
||||
|
||||
void initScene();
|
||||
|
||||
[[nodiscard]] ByteItemGraphicsScene* getScene() const {
|
||||
[[nodiscard]] ItemGraphicsScene* getScene() const {
|
||||
return this->scene;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,6 @@ namespace Bloom::Widgets
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
private:
|
||||
ByteItemGraphicsScene* scene = nullptr;
|
||||
ItemGraphicsScene* scene = nullptr;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#include "TopLevelGroupItem.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
TopLevelGroupItem::TopLevelGroupItem(
|
||||
const std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
const HexViewerSharedState& hexViewerState
|
||||
)
|
||||
: GroupItem(0, nullptr)
|
||||
, focusedMemoryRegions(focusedMemoryRegions)
|
||||
, hexViewerState(hexViewerState)
|
||||
{
|
||||
const auto memorySize = this->hexViewerState.memoryDescriptor.size();
|
||||
const auto startAddress = this->hexViewerState.memoryDescriptor.addressRange.startAddress;
|
||||
|
||||
for (Targets::TargetMemorySize i = 0; i < memorySize; i++) {
|
||||
const auto address = startAddress + i;
|
||||
this->byteItemsByAddress.emplace(address, ByteItem(address));
|
||||
}
|
||||
}
|
||||
|
||||
void TopLevelGroupItem::rebuildItemHierarchy() {
|
||||
this->items.clear();
|
||||
this->focusedRegionGroupItems.clear();
|
||||
|
||||
for (const auto& focusedRegion : this->focusedMemoryRegions) {
|
||||
this->focusedRegionGroupItems.emplace_back(focusedRegion, this->byteItemsByAddress, this);
|
||||
items.emplace_back(&(this->focusedRegionGroupItems.back()));
|
||||
}
|
||||
|
||||
for (auto& [address, byteItem] : this->byteItemsByAddress) {
|
||||
if (byteItem.parent != nullptr && byteItem.parent != this) {
|
||||
// This ByteItem is managed by another group
|
||||
continue;
|
||||
}
|
||||
|
||||
byteItem.parent = this;
|
||||
byteItem.grouped = false;
|
||||
this->items.push_back(&byteItem);
|
||||
}
|
||||
|
||||
this->sortItems();
|
||||
this->refreshValues();
|
||||
}
|
||||
|
||||
void TopLevelGroupItem::refreshValues() {
|
||||
for (auto& focusedRegionItem : this->focusedRegionGroupItems) {
|
||||
focusedRegionItem.refreshValue(this->hexViewerState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
#include "GroupItem.hpp"
|
||||
#include "FocusedRegionGroupItem.hpp"
|
||||
#include "ByteItem.hpp"
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class TopLevelGroupItem: public GroupItem
|
||||
{
|
||||
public:
|
||||
std::unordered_map<Targets::TargetMemoryAddress, ByteItem> byteItemsByAddress;
|
||||
|
||||
TopLevelGroupItem(
|
||||
const std::vector<FocusedMemoryRegion>& focusedMemoryRegions,
|
||||
const HexViewerSharedState& hexViewerState
|
||||
);
|
||||
|
||||
void rebuildItemHierarchy();
|
||||
|
||||
void refreshValues();
|
||||
|
||||
void adjustItemPositions(const int maximumWidth) {
|
||||
GroupItem::adjustItemPositions(maximumWidth, &(this->hexViewerState));
|
||||
}
|
||||
|
||||
void setPosition(const QPoint& position) {
|
||||
this->relativePosition = position;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::vector<FocusedMemoryRegion>& focusedMemoryRegions;
|
||||
const HexViewerSharedState& hexViewerState;
|
||||
|
||||
std::list<FocusedRegionGroupItem> focusedRegionGroupItems;
|
||||
};
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
#include "ValueAnnotationItem.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "ByteItem.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
ValueAnnotationItem::ValueAnnotationItem(const FocusedMemoryRegion& focusedMemoryRegion)
|
||||
: AnnotationItem(focusedMemoryRegion, AnnotationItemPosition::TOP)
|
||||
, focusedMemoryRegion(focusedMemoryRegion)
|
||||
, endianness(focusedMemoryRegion.endianness)
|
||||
{
|
||||
this->labelText = QString(ValueAnnotationItem::DEFAULT_LABEL_TEXT);
|
||||
}
|
||||
|
||||
void ValueAnnotationItem::setValue(const Targets::TargetMemoryBuffer& value) {
|
||||
this->value = value;
|
||||
|
||||
if (this->endianness == Targets::TargetMemoryEndianness::LITTLE) {
|
||||
std::reverse(this->value.begin(), this->value.end());
|
||||
}
|
||||
|
||||
this->refreshLabelText();
|
||||
this->setToolTip(this->labelText);
|
||||
}
|
||||
|
||||
void ValueAnnotationItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) {
|
||||
AnnotationItem::paint(painter, option, widget);
|
||||
|
||||
if (this->size > 1) {
|
||||
/*
|
||||
* Draw a circle just above the first byte item of the region, to indicate the first byte used to generate
|
||||
* the value (which will depend on the configured endianness of the region).
|
||||
*/
|
||||
using Targets::TargetMemoryEndianness;
|
||||
|
||||
auto fillColor = this->getLineColor();
|
||||
fillColor.setAlpha(this->isEnabled() ? 255 : 100);
|
||||
|
||||
painter->setPen(fillColor);
|
||||
painter->setBrush(fillColor);
|
||||
|
||||
constexpr auto radius = 2;
|
||||
painter->drawEllipse(
|
||||
QPoint(
|
||||
this->endianness == TargetMemoryEndianness::BIG
|
||||
? ByteItem::WIDTH / 2
|
||||
: this->width - (ByteItem::WIDTH / 2),
|
||||
AnnotationItem::TOP_HEIGHT - AnnotationItem::VERTICAL_LINE_LENGTH
|
||||
),
|
||||
radius,
|
||||
radius
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ValueAnnotationItem::refreshLabelText() {
|
||||
this->update();
|
||||
|
||||
if (this->value.empty()) {
|
||||
this->labelText = QString(ValueAnnotationItem::DEFAULT_LABEL_TEXT);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (this->focusedMemoryRegion.dataType) {
|
||||
case MemoryRegionDataType::UNSIGNED_INTEGER: {
|
||||
std::uint64_t integerValue = 0;
|
||||
for (const auto& byte : this->value) {
|
||||
integerValue = (integerValue << 8) | byte;
|
||||
}
|
||||
|
||||
this->labelText = QString::number(integerValue);
|
||||
break;
|
||||
}
|
||||
case MemoryRegionDataType::SIGNED_INTEGER: {
|
||||
const auto valueSize = this->value.size();
|
||||
|
||||
if (valueSize == 1) {
|
||||
this->labelText = QString::number(static_cast<int8_t>(this->value[0]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (valueSize == 2) {
|
||||
this->labelText = QString::number(static_cast<int16_t>((this->value[0] << 8) | this->value[1]));
|
||||
break;
|
||||
}
|
||||
|
||||
if (valueSize <= 4) {
|
||||
std::int32_t integerValue = 0;
|
||||
for (const auto& byte : this->value) {
|
||||
integerValue = (integerValue << 8) | byte;
|
||||
}
|
||||
|
||||
this->labelText = QString::number(integerValue);
|
||||
break;
|
||||
}
|
||||
|
||||
std::int64_t integerValue = 0;
|
||||
for (const auto& byte : this->value) {
|
||||
integerValue = (integerValue << 8) | byte;
|
||||
}
|
||||
|
||||
this->labelText = QString::number(integerValue);
|
||||
break;
|
||||
}
|
||||
case MemoryRegionDataType::ASCII_STRING: {
|
||||
// Replace non-ASCII chars with '?'
|
||||
auto asciiData = this->value;
|
||||
|
||||
std::replace_if(
|
||||
asciiData.begin(),
|
||||
asciiData.end(),
|
||||
[] (unsigned char value) {
|
||||
/*
|
||||
* We only care about non-control characters (with the exception of the white space character) in
|
||||
* the standard ASCII range.
|
||||
*/
|
||||
constexpr auto asciiRangeStart = 32;
|
||||
constexpr auto asciiRangeEnd = 126;
|
||||
return value < asciiRangeStart || value > asciiRangeEnd;
|
||||
},
|
||||
'?'
|
||||
);
|
||||
|
||||
this->labelText = "'" + QString::fromLatin1(
|
||||
reinterpret_cast<const char*>(asciiData.data()),
|
||||
static_cast<qsizetype>(asciiData.size())
|
||||
) + "'";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this->labelText = QString(ValueAnnotationItem::DEFAULT_LABEL_TEXT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QPainter>
|
||||
#include <QWidget>
|
||||
#include <QStyleOptionGraphicsItem>
|
||||
#include <optional>
|
||||
|
||||
#include "AnnotationItem.hpp"
|
||||
|
||||
#include "src/Targets/TargetMemory.hpp"
|
||||
|
||||
#include "src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/FocusedMemoryRegion.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
class ValueAnnotationItem: public AnnotationItem
|
||||
{
|
||||
public:
|
||||
explicit ValueAnnotationItem(const FocusedMemoryRegion& focusedMemoryRegion);
|
||||
void setValue(const Targets::TargetMemoryBuffer& value);
|
||||
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override;
|
||||
|
||||
protected:
|
||||
[[nodiscard]] QColor getLabelFontColor() const override {
|
||||
return QColor(0x94, 0x6F, 0x30);
|
||||
}
|
||||
|
||||
[[nodiscard]] const QFont& getLabelFont() const override {
|
||||
static auto labelFont = std::optional<QFont>();
|
||||
|
||||
if (!labelFont.has_value()) {
|
||||
labelFont = QFont("'Ubuntu', sans-serif");
|
||||
labelFont->setPixelSize(11);
|
||||
labelFont->setItalic(true);
|
||||
}
|
||||
|
||||
return labelFont.value();
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr auto DEFAULT_LABEL_TEXT = "??";
|
||||
|
||||
FocusedMemoryRegion focusedMemoryRegion;
|
||||
Targets::TargetMemoryBuffer value;
|
||||
Targets::TargetMemoryEndianness endianness = Targets::TargetMemoryEndianness::LITTLE;
|
||||
|
||||
void refreshLabelText();
|
||||
};
|
||||
}
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
#include "src/Services/PathService.hpp"
|
||||
#include "src/Exceptions/Exception.hpp"
|
||||
#include "src/Logger/Logger.hpp"
|
||||
|
||||
namespace Bloom::Widgets
|
||||
{
|
||||
@@ -97,6 +96,7 @@ namespace Bloom::Widgets
|
||||
|
||||
this->hexViewerWidget = new HexViewerWidget(
|
||||
this->targetMemoryDescriptor,
|
||||
this->data,
|
||||
this->settings.hexViewerWidgetSettings,
|
||||
this->settings.focusedMemoryRegions,
|
||||
this->settings.excludedMemoryRegions,
|
||||
@@ -106,17 +106,6 @@ namespace Bloom::Widgets
|
||||
|
||||
this->subContainerLayout->insertWidget(1, this->hexViewerWidget);
|
||||
|
||||
QObject::connect(
|
||||
this->hexViewerWidget,
|
||||
&HexViewerWidget::ready,
|
||||
this,
|
||||
[this] {
|
||||
if (this->data.has_value()) {
|
||||
this->hexViewerWidget->updateValues(this->data.value());
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this->hexViewerWidget->init();
|
||||
|
||||
this->rightPanel = new PanelWidget(PanelWidgetType::RIGHT, this->settings.rightPanelState, this);
|
||||
@@ -530,7 +519,7 @@ namespace Bloom::Widgets
|
||||
assert(data.size() == this->targetMemoryDescriptor.size());
|
||||
|
||||
this->data = data;
|
||||
this->hexViewerWidget->updateValues(this->data.value());
|
||||
this->hexViewerWidget->updateValues();
|
||||
this->setStaleData(false);
|
||||
|
||||
this->snapshotManager->createSnapshotWindow->refreshForm();
|
||||
|
||||
Reference in New Issue
Block a user