diff --git a/src/Insight/CMakeLists.txt b/src/Insight/CMakeLists.txt index a9f4f991..fa87c7bf 100755 --- a/src/Insight/CMakeLists.txt +++ b/src/Insight/CMakeLists.txt @@ -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 diff --git a/src/Insight/InsightWorker/Tasks/ConstructHexViewerByteItems.cpp b/src/Insight/InsightWorker/Tasks/ConstructHexViewerByteItems.cpp deleted file mode 100644 index 3515d99e..00000000 --- a/src/Insight/InsightWorker/Tasks/ConstructHexViewerByteItems.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include "ConstructHexViewerByteItems.hpp" - -#include - -namespace Bloom -{ - ConstructHexViewerByteItems::ConstructHexViewerByteItems( - const Targets::TargetMemoryDescriptor& memoryDescriptor, - std::optional& currentStackPointer, - Widgets::ByteItem** hoveredByteItem, - std::set& highlightedByteItems, - Widgets::HexViewerWidgetSettings& settings - ) - : memoryDescriptor(memoryDescriptor) - , currentStackPointer(currentStackPointer) - , hoveredByteItem(hoveredByteItem) - , highlightedByteItems(highlightedByteItems) - , settings(settings) - { - qRegisterMetaType>(); - } - - 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)); - } -} diff --git a/src/Insight/InsightWorker/Tasks/ConstructHexViewerByteItems.hpp b/src/Insight/InsightWorker/Tasks/ConstructHexViewerByteItems.hpp deleted file mode 100644 index 79455e19..00000000 --- a/src/Insight/InsightWorker/Tasks/ConstructHexViewerByteItems.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -#include -#include - -#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& currentStackPointer, - Widgets::ByteItem** hoveredByteItem, - std::set& highlightedByteItems, - Widgets::HexViewerWidgetSettings& settings - ); - - TaskGroups getTaskGroups() const override { - return TaskGroups(); - }; - - signals: - void sceneCreated(Widgets::ByteItemGraphicsScene* scene); - void byteItems(std::map byteItemsByAddress); - - protected: - void run(Services::TargetControllerService&) override; - - private: - std::map byteItemsByAddress; - - const Targets::TargetMemoryDescriptor& memoryDescriptor; - std::optional& currentStackPointer; - Widgets::ByteItem** hoveredByteItem; - std::set& highlightedByteItems; - Widgets::HexViewerWidgetSettings& settings; - }; -} diff --git a/src/Insight/InsightWorker/Tasks/ConstructHexViewerTopLevelGroupItem.cpp b/src/Insight/InsightWorker/Tasks/ConstructHexViewerTopLevelGroupItem.cpp new file mode 100644 index 00000000..0a91d79c --- /dev/null +++ b/src/Insight/InsightWorker/Tasks/ConstructHexViewerTopLevelGroupItem.cpp @@ -0,0 +1,19 @@ +#include "ConstructHexViewerTopLevelGroupItem.hpp" + +namespace Bloom +{ + ConstructHexViewerTopLevelGroupItem::ConstructHexViewerTopLevelGroupItem( + const std::vector& 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); + } +} diff --git a/src/Insight/InsightWorker/Tasks/ConstructHexViewerTopLevelGroupItem.hpp b/src/Insight/InsightWorker/Tasks/ConstructHexViewerTopLevelGroupItem.hpp new file mode 100644 index 00000000..26a3c6f6 --- /dev/null +++ b/src/Insight/InsightWorker/Tasks/ConstructHexViewerTopLevelGroupItem.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +#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& 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& focusedMemoryRegions; + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/AnnotationItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/AnnotationItem.cpp deleted file mode 100644 index e221973a..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/AnnotationItem.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#include "AnnotationItem.hpp" - -#include - -#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(startAddress + size - 1)) - , labelText(std::move(labelText)) - , position(position) - , width(static_cast((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); - } -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/AnnotationItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/AnnotationItem.hpp deleted file mode 100644 index 12d7a79b..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/AnnotationItem.hpp +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#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(); - - if (!labelFont.has_value()) { - labelFont = QFont("'Ubuntu', sans-serif"); - labelFont->setPixelSize(12); - } - - return labelFont.value(); - } - }; -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.cpp index 7f9198e1..3faf2bee 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.cpp @@ -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>& byteItemsByRowIndex - ) { + void ByteAddressContainer::adjustAddressLabels(const std::vector& firstByteItemByLine) { static constexpr int leftMargin = 10; - const auto newRowCount = byteItemsByRowIndex.size(); - const auto layoutItemMaxIndex = static_cast(this->addressItemsByRowIndex.size() - 1); - for (const auto& mappingPair : byteItemsByRowIndex) { - const auto rowIndex = static_cast(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(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(); } } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.hpp index a00eb3b3..ed07d3cc 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressContainer.hpp @@ -1,15 +1,12 @@ #pragma once #include -#include -#include #include -#include +#include #include -#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>& byteItemsByRowIndex); + void adjustAddressLabels(const std::vector& firstByteItemByLine); void invalidateChildItemCaches(); void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override; private: - const HexViewerWidgetSettings& settings; - std::map addressItemsByRowIndex; + const HexViewerSharedState& hexViewerState; + std::vector addressItems; }; } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.cpp index 3df48cb4..e29b7970 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.cpp @@ -1,46 +1,37 @@ #include "ByteAddressItem.hpp" -#include - namespace Bloom::Widgets { - ByteAddressItem::ByteAddressItem( - std::size_t rowIndex, - const std::map>& 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() ); } } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.hpp index 721eebfc..c3826767 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteAddressItem.hpp @@ -1,12 +1,10 @@ #pragma once -#include #include -#include +#include #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>& 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>& byteItemsByRowIndex; - const AddressType& addressType; + const HexViewerSharedState& hexViewerState; }; } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.cpp index b8abc502..2d8c54db 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.cpp @@ -1,131 +1,105 @@ #include "ByteItem.hpp" -#include +#include +#include namespace Bloom::Widgets { - ByteItem::ByteItem( - std::size_t byteIndex, - Targets::TargetMemoryAddress address, - std::optional& currentStackPointer, - ByteItem** hoveredByteItem, - std::set& 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); } } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.hpp index 4a246067..8fab22e4 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItem.hpp @@ -1,79 +1,53 @@ #pragma once -#include -#include #include -#include -#include -#include +#include +#include -#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& currentStackPointer, - ByteItem** hoveredByteItem, - std::set& 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 standardPixmapsByValue = {}; + static inline std::vector selectedPixmapsByValue = {}; + static inline std::vector groupedPixmapsByValue = {}; + static inline std::vector standardAsciiPixmapsByValue = {}; + static inline std::vector selectedAsciiPixmapsByValue = {}; + static inline std::vector groupedAsciiPixmapsByValue = {}; + static inline std::vector hoveredPrimaryPixmapsByValue = {}; + static inline std::vector hoveredSecondaryPixmapsByValue = {}; + static inline std::vector hoveredPrimaryAsciiPixmapsByValue = {}; + static inline std::vector hoveredSecondaryAsciiPixmapsByValue = {}; + static inline std::optional missingDataPixmap = {}; - const HexViewerWidgetSettings& settings; - std::optional asciiValue; - - ByteItem** hoveredByteItem; - std::optional& currentStackPointer; - std::set& highlightedByteItems; - - const QColor* getBackgroundColor(); - const QColor* getTextColor(); + static void generatePixmapCaches(); }; +#pragma pack(pop) } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemGraphicsScene.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemGraphicsScene.cpp deleted file mode 100644 index 6ab362f4..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemGraphicsScene.cpp +++ /dev/null @@ -1,831 +0,0 @@ -#include "ByteItemGraphicsScene.hpp" - -#include -#include -#include -#include -#include - -#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& focusedMemoryRegions, - std::vector& 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 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& 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::floor( - (width - this->margins.left() - this->margins.right() - ByteAddressContainer::WIDTH - + ByteItem::RIGHT_MARGIN) / (ByteItem::WIDTH + ByteItem::RIGHT_MARGIN) - ) - ); - const auto rowCount = static_cast( - std::ceil(static_cast(this->byteItemsByAddress.size()) / static_cast(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(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( - 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(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(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(item); - - if (byteItem != nullptr && !byteItem->selected) { - this->selectByteItem(byteItem); - } - } - } - - ByteItem* hoveredByteItem = nullptr; - AnnotationItem* hoveredAnnotationItem = nullptr; - - if (!hoveredItems.empty()) { - hoveredByteItem = dynamic_cast(hoveredItems.last()); - hoveredAnnotationItem = dynamic_cast(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::floor( - (this->getSceneWidth() - this->margins.left() - this->margins.right() - ByteAddressContainer::WIDTH - + ByteItem::RIGHT_MARGIN) / (ByteItem::WIDTH + ByteItem::RIGHT_MARGIN) - ) - ); - - std::map> byteWidgetsByRowIndex; - std::map> byteWidgetsByColumnIndex; - - auto rowIndicesWithTopAnnotations = std::set(); - auto rowIndicesWithBottomAnnotations = std::set(); - const auto& memoryAddressRange = this->targetMemoryDescriptor.addressRange; - - for (auto* annotationItem : this->annotationItems) { - const auto firstByteRowIndex = static_cast( - std::ceil(static_cast((annotationItem->startAddress - memoryAddressRange.startAddress) + 1) - / static_cast(columnCount)) - 1 - ); - - const auto lastByteRowIndex = static_cast( - std::ceil(static_cast((annotationItem->endAddress - memoryAddressRange.startAddress) + 1) - / static_cast(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::ceil(static_cast(byteWidget->byteIndex + 1) / static_cast(columnCount)) - 1 - ); - const auto columnIndex = static_cast( - static_cast(byteWidget->byteIndex) - - (std::floor(byteWidget->byteIndex / columnCount) * static_cast(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( - 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)); - } -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/FocusedRegionGroupItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/FocusedRegionGroupItem.cpp new file mode 100644 index 00000000..d63c9e65 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/FocusedRegionGroupItem.cpp @@ -0,0 +1,358 @@ +#include "FocusedRegionGroupItem.hpp" + +#include +#include + +namespace Bloom::Widgets +{ + FocusedRegionGroupItem::FocusedRegionGroupItem( + const FocusedMemoryRegion& focusedRegion, + std::unordered_map& 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(value[0])); + break; + } + + if (valueSize == 2) { + this->valueLabel = QString::number(static_cast((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(asciiData.data()), + static_cast(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(this->focusedMemoryRegion.name.size() * averageSymbolWidth); + const auto valueLabelWidth = static_cast( + 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( + byteItemSize * (ByteItem::WIDTH + (ByteItem::RIGHT_MARGIN / 2)) + ); + + const auto annotationMargin = static_cast( + 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(heightOffset); + const auto verticalLineYEnd = static_cast(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(heightOffset - 5); + const auto verticalLineYEnd = static_cast(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); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/FocusedRegionGroupItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/FocusedRegionGroupItem.hpp new file mode 100644 index 00000000..141aaa05 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/FocusedRegionGroupItem.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +#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& 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 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; + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GraphicsItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GraphicsItem.hpp new file mode 100644 index 00000000..d565f5e9 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GraphicsItem.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +#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; + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GroupItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GroupItem.cpp new file mode 100644 index 00000000..b62a0001 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GroupItem.cpp @@ -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(); + const ByteItem* lastByteItem = nullptr; + + for (const auto& item : this->items) { + auto* const groupItem = dynamic_cast(item); + if (groupItem != nullptr) { + groupItem->adjustItemPositions(maximumWidth, hexViewerState); + } + + const ByteItem* byteItem = groupItem != nullptr + ? groupItem->firstByteItem() + : dynamic_cast(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(position.y() + itemSize.height()), height); + width = std::max(static_cast(position.x() + itemSize.width()), width); + + position.setX(static_cast(position.x() + itemSize.width() + HexViewerItem::RIGHT_MARGIN)); + + currentLineItems.push_back(item); + lastByteItem = byteItem; + } + + this->groupSize = QSize(width + margins.right(), height + margins.bottom()); + } + + std::vector GroupItem::flattenedItems() const { + auto flattenedItems = std::vector(); + + for (const auto& item : this->items) { + flattenedItems.push_back(item); + + const auto* groupItem = dynamic_cast(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(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; + } + ); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GroupItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GroupItem.hpp new file mode 100644 index 00000000..b98f6a3b --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/GroupItem.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +#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 flattenedItems() const; + + void paint( + QPainter* painter, + const HexViewerSharedState* hexViewerState, + const QGraphicsItem* graphicsItem + ) const override { + return; + } + + protected: + std::vector 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(); + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerItem.hpp new file mode 100644 index 00000000..59736811 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerItem.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +#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) +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerSharedState.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerSharedState.hpp new file mode 100644 index 00000000..de3d966e --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerSharedState.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include "src/Targets/TargetMemory.hpp" +#include "HexViewerWidgetSettings.hpp" + +namespace Bloom::Widgets +{ + class ByteItem; + + struct HexViewerSharedState + { + public: + const Targets::TargetMemoryDescriptor& memoryDescriptor; + const std::optional& data; + + HexViewerWidgetSettings& settings; + + ByteItem* hoveredByteItem = nullptr; + std::optional currentStackPointer; + + HexViewerSharedState( + const Targets::TargetMemoryDescriptor& memoryDescriptor, + const std::optional& data, + HexViewerWidgetSettings& settings + ) + : memoryDescriptor(memoryDescriptor) + , data(data) + , settings(settings) + {} + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.cpp index af6d50ad..9515797e 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.cpp @@ -1,6 +1,7 @@ #include "HexViewerWidget.hpp" #include +#include #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& data, HexViewerWidgetSettings& settings, std::vector& focusedMemoryRegions, std::vector& 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("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({}); } } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.hpp index 73447106..9a29be85 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/HexViewerWidget.hpp @@ -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& data, HexViewerWidgetSettings& settings, std::vector& focusedMemoryRegions, std::vector& 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& data; + std::vector& focusedMemoryRegions; std::vector& 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; diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsScene.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsScene.cpp new file mode 100644 index 00000000..a02109a2 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsScene.cpp @@ -0,0 +1,786 @@ +#include "ItemGraphicsScene.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#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& data, + std::vector& focusedMemoryRegions, + std::vector& 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& 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(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( + (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(item); + + if (clickedGraphicsItem == nullptr) { + continue; + } + + auto* byteItem = dynamic_cast(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(item); + + if (graphicsItem == nullptr) { + continue; + } + + auto* byteItem = dynamic_cast(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(item); + + if (graphicsItem == nullptr) { + continue; + } + + auto* byteItem = dynamic_cast(graphicsItem->hexViewerItem); + + if (byteItem != nullptr && !byteItem->selected) { + this->selectByteItem(*byteItem); + } + } + } + + for (const auto& item : hoveredItems) { + auto* hoveredGraphicsItem = dynamic_cast(item); + + if (hoveredGraphicsItem == nullptr) { + continue; + } + + auto* hoveredByteItem = dynamic_cast(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_castgridPoints)::size_type>(std::max( + static_cast( + std::floor( + static_cast(verticalScrollBarValue) / static_cast(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(totalGraphicsItems) + ); + + const auto excessAvailableGraphicItems = static_cast(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(); + 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( + 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(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)); + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemGraphicsScene.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsScene.hpp similarity index 67% rename from src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemGraphicsScene.hpp rename to src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsScene.hpp index 6698b8d1..fc19126e 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemGraphicsScene.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsScene.hpp @@ -3,33 +3,32 @@ #include #include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include -#include -#include #include #include #include #include -#include #include #include #include +#include #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& data, std::vector& focusedMemoryRegions, std::vector& 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& highlightedAddresses); + void selectByteItems(const std::set& 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& focusedMemoryRegions; - std::vector& excludedMemoryRegions; + static constexpr auto GRID_SIZE = 100; bool enabled = true; - ByteItem* hoveredByteWidget = nullptr; - AnnotationItem* hoveredAnnotationItem = nullptr; + HexViewerSharedState state; - std::optional currentStackPointer; + std::vector& focusedMemoryRegions; + std::vector& excludedMemoryRegions; - Targets::TargetMemoryBuffer lastValueBuffer; + std::unique_ptr topLevelGroup = nullptr; - std::map byteItemsByAddress; - std::vector annotationItems; - std::vector valueAnnotationItems; - std::map> byteItemsByRowIndex; - std::map> byteItemsByColumnIndex; + std::vector flattenedItems; + std::vector gridPoints; + std::vector firstByteItemByLine; - Targets::TargetState targetState = Targets::TargetState::UNKNOWN; + std::vector 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 highlightedByteItems; - std::map selectedByteItemsByAddress; + std::unordered_map selectedByteItemsByAddress; QGraphicsRectItem* rubberBandRectItem = nullptr; std::optional 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); diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemContainerGraphicsView.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsView.cpp similarity index 76% rename from src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemContainerGraphicsView.cpp rename to src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsView.cpp index 14467625..82c4083c 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemContainerGraphicsView.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsView.cpp @@ -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& data, std::vector& focusedMemoryRegions, std::vector& 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) { diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemContainerGraphicsView.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsView.hpp similarity index 77% rename from src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemContainerGraphicsView.hpp rename to src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsView.hpp index 4299a0e0..e4f8f2d7 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ByteItemContainerGraphicsView.hpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ItemGraphicsView.hpp @@ -1,25 +1,25 @@ #pragma once #include -#include #include #include -#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& data, std::vector& focusedMemoryRegions, std::vector& 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; }; } diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/TopLevelGroupItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/TopLevelGroupItem.cpp new file mode 100644 index 00000000..f632756b --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/TopLevelGroupItem.cpp @@ -0,0 +1,51 @@ +#include "TopLevelGroupItem.hpp" + +namespace Bloom::Widgets +{ + TopLevelGroupItem::TopLevelGroupItem( + const std::vector& 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); + } + } +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/TopLevelGroupItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/TopLevelGroupItem.hpp new file mode 100644 index 00000000..b3d6ba54 --- /dev/null +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/TopLevelGroupItem.hpp @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include + +#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 byteItemsByAddress; + + TopLevelGroupItem( + const std::vector& 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& focusedMemoryRegions; + const HexViewerSharedState& hexViewerState; + + std::list focusedRegionGroupItems; + }; +} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ValueAnnotationItem.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ValueAnnotationItem.cpp deleted file mode 100644 index 19064295..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ValueAnnotationItem.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "ValueAnnotationItem.hpp" - -#include - -#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(this->value[0])); - break; - } - - if (valueSize == 2) { - this->labelText = QString::number(static_cast((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(asciiData.data()), - static_cast(asciiData.size()) - ) + "'"; - break; - } - default: { - this->labelText = QString(ValueAnnotationItem::DEFAULT_LABEL_TEXT); - } - } - } -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ValueAnnotationItem.hpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ValueAnnotationItem.hpp deleted file mode 100644 index 9487f687..00000000 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/HexViewerWidget/ValueAnnotationItem.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#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(); - - 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(); - }; -} diff --git a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp index b3c7a5f6..a3c50ce3 100644 --- a/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp +++ b/src/Insight/UserInterfaces/InsightWindow/Widgets/TargetMemoryInspectionPane/TargetMemoryInspectionPane.cpp @@ -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();