Refactored byte item selection and highlighting in hex viewer

This commit is contained in:
Nav
2023-08-24 17:25:28 +01:00
parent b07be04a3c
commit 586c11157c
16 changed files with 200 additions and 138 deletions

View File

@@ -4,6 +4,7 @@
#include <QWidget> #include <QWidget>
#include <functional> #include <functional>
#include <QString> #include <QString>
#include <set>
#include <optional> #include <optional>
#include "src/Targets/TargetMemory.hpp" #include "src/Targets/TargetMemory.hpp"
@@ -28,7 +29,7 @@ namespace Widgets
* always be enabled. * always be enabled.
*/ */
using IsEnabledCallbackType = std::function< using IsEnabledCallbackType = std::function<
bool(const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>&) bool(const std::set<Targets::TargetMemoryAddress>&)
>; >;
public: public:
@@ -41,6 +42,6 @@ namespace Widgets
); );
signals: signals:
void invoked(const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress); void invoked(const std::set<Targets::TargetMemoryAddress>& selectedByteItemAddresses);
}; };
} }

View File

@@ -20,6 +20,7 @@ namespace Widgets
ByteItem* hoveredByteItem = nullptr; ByteItem* hoveredByteItem = nullptr;
std::optional<Targets::TargetStackPointer> currentStackPointer; std::optional<Targets::TargetStackPointer> currentStackPointer;
bool highlightingEnabled = false; bool highlightingEnabled = false;
std::set<Targets::TargetMemoryAddressRange> highlightedAddressRanges;
HexViewerSharedState( HexViewerSharedState(
const Targets::TargetMemoryDescriptor& memoryDescriptor, const Targets::TargetMemoryDescriptor& memoryDescriptor,

View File

@@ -242,22 +242,17 @@ namespace Widgets
this->byteItemGraphicsScene->addExternalContextMenuAction(action); this->byteItemGraphicsScene->addExternalContextMenuAction(action);
} }
void HexViewerWidget::highlightBytes(const std::set<Targets::TargetMemoryAddress>& addresses) { void HexViewerWidget::highlightByteItemRanges(const std::set<Targets::TargetMemoryAddressRange>& addressRanges) {
this->byteItemGraphicsScene->highlightByteItems(addresses); this->byteItemGraphicsScene->highlightByteItemRanges(addressRanges);
}
void HexViewerWidget::highlightBytes(const std::set<Targets::TargetMemoryAddressRange>& addressRanges) {
this->byteItemGraphicsScene->highlightByteItems(this->addressRangesToAddresses(addressRanges));
} }
void HexViewerWidget::clearHighlighting() { void HexViewerWidget::clearHighlighting() {
this->highlightBytes(std::set<Targets::TargetMemoryAddress>()); this->byteItemGraphicsScene->clearByteItemHighlighting();
} }
void HexViewerWidget::selectAndHighlightBytes(const std::set<Targets::TargetMemoryAddressRange>& addressRanges) { void HexViewerWidget::selectAndHighlightBytes(const std::set<Targets::TargetMemoryAddressRange>& addressRanges) {
const auto addresses = this->addressRangesToAddresses(addressRanges); this->byteItemGraphicsScene->highlightByteItemRanges(addressRanges);
this->byteItemGraphicsScene->highlightByteItems(addresses); this->byteItemGraphicsScene->selectByteItemRanges(addressRanges);
this->byteItemGraphicsScene->selectByteItems(addresses);
} }
void HexViewerWidget::centerOnByte(Targets::TargetMemoryAddress address) { void HexViewerWidget::centerOnByte(Targets::TargetMemoryAddress address) {
@@ -377,9 +372,9 @@ namespace Widgets
} }
void HexViewerWidget::onByteSelectionChanged( void HexViewerWidget::onByteSelectionChanged(
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress const std::set<Targets::TargetMemoryAddress>& selectedByteItemAddresses
) { ) {
const auto selectionCount = selectedByteItemsByAddress.size(); const auto selectionCount = selectedByteItemAddresses.size();
if (selectionCount == 0) { if (selectionCount == 0) {
this->selectionCountLabel->hide(); this->selectionCountLabel->hide();

View File

@@ -42,8 +42,7 @@ namespace Widgets
void refreshRegions(); void refreshRegions();
void setStackPointer(Targets::TargetStackPointer stackPointer); void setStackPointer(Targets::TargetStackPointer stackPointer);
void addExternalContextMenuAction(ContextMenuAction* action); void addExternalContextMenuAction(ContextMenuAction* action);
void highlightBytes(const std::set<Targets::TargetMemoryAddress>& addresses); void highlightByteItemRanges(const std::set<Targets::TargetMemoryAddressRange>& addressRanges);
void highlightBytes(const std::set<Targets::TargetMemoryAddressRange>& addressRanges);
void clearHighlighting(); void clearHighlighting();
void selectAndHighlightBytes(const std::set<Targets::TargetMemoryAddressRange>& addressRanges); void selectAndHighlightBytes(const std::set<Targets::TargetMemoryAddressRange>& addressRanges);
void centerOnByte(Targets::TargetMemoryAddress address); void centerOnByte(Targets::TargetMemoryAddress address);
@@ -91,9 +90,7 @@ namespace Widgets
void setDisplayAsciiEnabled(bool enabled); void setDisplayAsciiEnabled(bool enabled);
void onGoToAddressInputChanged(); void onGoToAddressInputChanged();
void onHoveredAddress(const std::optional<Targets::TargetMemoryAddress>& address); void onHoveredAddress(const std::optional<Targets::TargetMemoryAddress>& address);
void onByteSelectionChanged( void onByteSelectionChanged(const std::set<Targets::TargetMemoryAddress>& selectedByteItemAddresses);
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress
);
std::set<Targets::TargetMemoryAddress> addressRangesToAddresses( std::set<Targets::TargetMemoryAddress> addressRangesToAddresses(
const std::set<Targets::TargetMemoryAddressRange>& addressRanges const std::set<Targets::TargetMemoryAddressRange>& addressRanges
); );

View File

@@ -215,18 +215,18 @@ namespace Widgets
InsightWorker::queueTask(constructHexViewerTopLevelGroupItem); InsightWorker::queueTask(constructHexViewerTopLevelGroupItem);
} }
void ItemGraphicsScene::updateStackPointer(std::uint32_t stackPointer) { void ItemGraphicsScene::updateStackPointer(Targets::TargetStackPointer stackPointer) {
this->state.currentStackPointer = stackPointer; this->state.currentStackPointer = stackPointer;
this->rebuildItemHierarchy(); this->rebuildItemHierarchy();
} }
void ItemGraphicsScene::selectByteItems(const std::set<std::uint32_t>& addresses) { void ItemGraphicsScene::selectByteItems(const std::set<Targets::TargetMemoryAddress>& addresses) {
this->selectedByteItemsByAddress.clear(); this->selectedByteItemAddresses.clear();
for (auto& [address, byteItem] : this->topLevelGroup->byteItemsByAddress) { for (auto& [address, byteItem] : this->topLevelGroup->byteItemsByAddress) {
if (addresses.contains(address)) { if (addresses.contains(address)) {
byteItem.selected = true; byteItem.selected = true;
this->selectedByteItemsByAddress.insert(std::pair(byteItem.startAddress, &byteItem)); this->selectedByteItemAddresses.insert(byteItem.startAddress);
} else if (byteItem.selected) { } else if (byteItem.selected) {
byteItem.selected = false; byteItem.selected = false;
@@ -234,16 +234,37 @@ namespace Widgets
} }
this->update(); this->update();
emit this->selectionChanged(this->selectedByteItemsByAddress); emit this->selectionChanged(addresses);
} }
void ItemGraphicsScene::highlightByteItems(const std::set<std::uint32_t>& addresses) { void ItemGraphicsScene::selectByteItemRanges(const std::set<Targets::TargetMemoryAddressRange>& addressRanges) {
for (auto& [address, byteItem] : this->topLevelGroup->byteItemsByAddress) { return this->selectByteItems(this->addressRangesToAddresses(addressRanges));
byteItem.highlighted = addresses.contains(address); }
void ItemGraphicsScene::highlightByteItemRanges(const std::set<Targets::TargetMemoryAddressRange>& addressRanges) {
/*
* Don't bother updating the byte items if addressRanges is empty - updating the this->state.highlightingEnabled
* flag will prevent the highlighting, and the byte items will be updated the next time we actually want to
* highlight something.
*
* Not pretty but it saves a lot of cycles.
*/
if (!addressRanges.empty()) {
const auto addresses = this->addressRangesToAddresses(addressRanges);
for (auto& [address, byteItem] : this->topLevelGroup->byteItemsByAddress) {
byteItem.highlighted = addresses.contains(address);
}
} }
this->state.highlightingEnabled = !addresses.empty(); this->state.highlightingEnabled = !addressRanges.empty();
this->state.highlightedAddressRanges = std::move(addressRanges);
this->update(); this->update();
emit this->highlightingChanged(addressRanges);
}
void ItemGraphicsScene::clearByteItemHighlighting() {
this->highlightByteItemRanges({});
} }
void ItemGraphicsScene::rebuildItemHierarchy() { void ItemGraphicsScene::rebuildItemHierarchy() {
@@ -323,7 +344,7 @@ namespace Widgets
void ItemGraphicsScene::addExternalContextMenuAction(ContextMenuAction* action) { void ItemGraphicsScene::addExternalContextMenuAction(ContextMenuAction* action) {
QObject::connect(action, &QAction::triggered, this, [this, action] () { QObject::connect(action, &QAction::triggered, this, [this, action] () {
emit action->invoked(this->selectedByteItemsByAddress); emit action->invoked(this->selectedByteItemAddresses);
}); });
this->externalContextMenuActions.push_back(action); this->externalContextMenuActions.push_back(action);
@@ -367,8 +388,7 @@ namespace Widgets
const auto mousePosition = mouseEvent->buttonDownScenePos(button); const auto mousePosition = mouseEvent->buttonDownScenePos(button);
if (this->state.highlightingEnabled) { if (this->state.highlightingEnabled) {
this->state.highlightingEnabled = false; this->clearByteItemHighlighting();
this->update();
} }
if (mousePosition.x() <= this->byteAddressContainer->boundingRect().width()) { if (mousePosition.x() <= this->byteAddressContainer->boundingRect().width()) {
@@ -424,12 +444,12 @@ namespace Widgets
this->toggleByteItemSelection(byteItem); this->toggleByteItemSelection(byteItem);
} }
emit this->selectionChanged(this->selectedByteItemsByAddress); emit this->selectionChanged(this->selectedByteItemAddresses);
return; return;
} }
this->toggleByteItemSelection(*clickedByteItem); this->toggleByteItemSelection(*clickedByteItem);
emit this->selectionChanged(this->selectedByteItemsByAddress); emit this->selectionChanged(this->selectedByteItemAddresses);
} }
} }
@@ -463,7 +483,7 @@ namespace Widgets
for (auto& byteItem : items) { for (auto& byteItem : items) {
this->selectByteItem(*byteItem); this->selectByteItem(*byteItem);
} }
emit this->selectionChanged(this->selectedByteItemsByAddress); emit this->selectionChanged(this->selectedByteItemAddresses);
} }
auto* hoveredByteItem = this->itemIndex->byteItemAt(mousePosition); auto* hoveredByteItem = this->itemIndex->byteItemAt(mousePosition);
@@ -485,7 +505,7 @@ namespace Widgets
void ItemGraphicsScene::keyPressEvent(QKeyEvent* keyEvent) { void ItemGraphicsScene::keyPressEvent(QKeyEvent* keyEvent) {
const auto key = keyEvent->key(); const auto key = keyEvent->key();
if (key == Qt::Key_Escape && !this->selectedByteItemsByAddress.empty()) { if (key == Qt::Key_Escape && !this->selectedByteItemAddresses.empty()) {
this->clearByteItemSelection(); this->clearByteItemSelection();
return; return;
} }
@@ -514,7 +534,7 @@ namespace Widgets
return; return;
} }
const auto itemsSelected = !this->selectedByteItemsByAddress.empty(); const auto itemsSelected = !this->selectedByteItemAddresses.empty();
auto* menu = new QMenu(this->parent); auto* menu = new QMenu(this->parent);
menu->setLayoutDirection(Qt::LayoutDirection::LeftToRight); menu->setLayoutDirection(Qt::LayoutDirection::LeftToRight);
@@ -548,7 +568,7 @@ namespace Widgets
itemsSelected itemsSelected
&& ( && (
!externalAction->isEnabledCallback.has_value() !externalAction->isEnabledCallback.has_value()
|| externalAction->isEnabledCallback.value()(this->selectedByteItemsByAddress) || externalAction->isEnabledCallback.value()(this->selectedByteItemAddresses)
) )
); );
@@ -620,12 +640,12 @@ namespace Widgets
void ItemGraphicsScene::selectByteItem(ByteItem& byteItem) { void ItemGraphicsScene::selectByteItem(ByteItem& byteItem) {
byteItem.selected = true; byteItem.selected = true;
this->selectedByteItemsByAddress.insert(std::pair(byteItem.startAddress, &byteItem)); this->selectedByteItemAddresses.insert(byteItem.startAddress);
} }
void ItemGraphicsScene::deselectByteItem(ByteItem& byteItem) { void ItemGraphicsScene::deselectByteItem(ByteItem& byteItem) {
byteItem.selected = false; byteItem.selected = false;
this->selectedByteItemsByAddress.erase(byteItem.startAddress); this->selectedByteItemAddresses.erase(byteItem.startAddress);
} }
void ItemGraphicsScene::toggleByteItemSelection(ByteItem& byteItem) { void ItemGraphicsScene::toggleByteItemSelection(ByteItem& byteItem) {
@@ -638,23 +658,24 @@ namespace Widgets
} }
void ItemGraphicsScene::clearByteItemSelection() { void ItemGraphicsScene::clearByteItemSelection() {
for (auto& [address, byteItem] : this->selectedByteItemsByAddress) { for (const auto& address : this->selectedByteItemAddresses) {
byteItem->selected = false; auto& byteItem = this->topLevelGroup->byteItemsByAddress.at(address);
byteItem.selected = false;
} }
this->selectedByteItemsByAddress.clear(); this->selectedByteItemAddresses.clear();
this->update(); this->update();
emit this->selectionChanged(this->selectedByteItemsByAddress); emit this->selectionChanged(this->selectedByteItemAddresses);
} }
void ItemGraphicsScene::selectAllByteItems() { void ItemGraphicsScene::selectAllByteItems() {
for (auto& [address, byteItem] : this->topLevelGroup->byteItemsByAddress) { for (auto& [address, byteItem] : this->topLevelGroup->byteItemsByAddress) {
byteItem.selected = true; byteItem.selected = true;
this->selectedByteItemsByAddress.insert(std::pair(byteItem.startAddress, &byteItem)); this->selectedByteItemAddresses.insert(byteItem.startAddress);
} }
this->update(); this->update();
emit this->selectionChanged(this->selectedByteItemsByAddress); emit this->selectionChanged(this->selectedByteItemAddresses);
} }
void ItemGraphicsScene::setAddressType(AddressType type) { void ItemGraphicsScene::setAddressType(AddressType type) {
@@ -666,34 +687,31 @@ namespace Widgets
this->byteAddressContainer->invalidateChildItemCaches(); this->byteAddressContainer->invalidateChildItemCaches();
} }
std::map<Targets::TargetMemoryAddress, ByteItem*> ItemGraphicsScene::sortedByteItemsByAddress() { std::set<Targets::TargetMemoryAddress> ItemGraphicsScene::excludedAddresses() {
auto sortedByteItemsByAddress = std::map<Targets::TargetMemoryAddress, ByteItem*>(); auto output = std::set<Targets::TargetMemoryAddress>();
std::transform(
this->selectedByteItemsByAddress.begin(),
this->selectedByteItemsByAddress.end(),
std::inserter(sortedByteItemsByAddress, sortedByteItemsByAddress.end()),
[] (const decltype(this->selectedByteItemsByAddress)::value_type& pair) {
return pair;
}
);
return sortedByteItemsByAddress; for (const auto& excludedRegion : this->excludedMemoryRegions) {
const auto regionAddresses = excludedRegion.addressRange.addresses();
output.insert(regionAddresses.begin(), regionAddresses.end());
}
return output;
} }
void ItemGraphicsScene::copyAddressesToClipboard(AddressType type) { void ItemGraphicsScene::copyAddressesToClipboard(AddressType type) {
if (this->selectedByteItemsByAddress.empty()) { if (this->selectedByteItemAddresses.empty()) {
return; return;
} }
auto data = QString(); auto data = QString();
const auto memoryStartAddress = this->state.memoryDescriptor.addressRange.startAddress; const auto memoryStartAddress = this->state.memoryDescriptor.addressRange.startAddress;
for (const auto& [address, byteItem] : this->sortedByteItemsByAddress()) { for (const auto& address : this->selectedByteItemAddresses) {
data.append( data.append(
"0x" + QString::number( "0x" + QString::number(
type == AddressType::RELATIVE type == AddressType::RELATIVE
? byteItem->startAddress - memoryStartAddress ? address - memoryStartAddress
: byteItem->startAddress, : address,
16 16
).rightJustified(8, '0').toUpper() + "\n" ).rightJustified(8, '0').toUpper() + "\n"
); );
@@ -703,16 +721,17 @@ namespace Widgets
} }
void ItemGraphicsScene::copyHexValuesToClipboard(bool withDelimiters) { void ItemGraphicsScene::copyHexValuesToClipboard(bool withDelimiters) {
if (this->selectedByteItemsByAddress.empty()) { if (this->selectedByteItemAddresses.empty()) {
return; return;
} }
const auto excludedAddresses = this->excludedAddresses();
auto data = QString(); auto data = QString();
for (const auto& [address, byteItem] : this->sortedByteItemsByAddress()) { for (const auto& address : this->selectedByteItemAddresses) {
const unsigned char byteValue = byteItem->excluded const unsigned char byteValue = excludedAddresses.contains(address)
? 0x00 ? 0x00
: (*this->state.data)[byteItem->startAddress - this->state.memoryDescriptor.addressRange.startAddress]; : (*this->state.data)[address - this->state.memoryDescriptor.addressRange.startAddress];
data.append( data.append(
withDelimiters withDelimiters
@@ -725,16 +744,17 @@ namespace Widgets
} }
void ItemGraphicsScene::copyDecimalValuesToClipboard() { void ItemGraphicsScene::copyDecimalValuesToClipboard() {
if (this->selectedByteItemsByAddress.empty() || !this->state.data.has_value()) { if (this->selectedByteItemAddresses.empty() || !this->state.data.has_value()) {
return; return;
} }
const auto excludedAddresses = this->excludedAddresses();
auto data = QString(); auto data = QString();
for (const auto& [address, byteItem] : this->sortedByteItemsByAddress()) { for (const auto& address : this->selectedByteItemAddresses) {
const unsigned char byteValue = byteItem->excluded const unsigned char byteValue = excludedAddresses.contains(address)
? 0x00 ? 0x00
: (*this->state.data)[byteItem->startAddress - this->state.memoryDescriptor.addressRange.startAddress]; : (*this->state.data)[address - this->state.memoryDescriptor.addressRange.startAddress];
data.append(QString::number(byteValue, 10) + "\n"); data.append(QString::number(byteValue, 10) + "\n");
} }
@@ -742,16 +762,17 @@ namespace Widgets
} }
void ItemGraphicsScene::copyBinaryBitStringToClipboard(bool withDelimiters) { void ItemGraphicsScene::copyBinaryBitStringToClipboard(bool withDelimiters) {
if (this->selectedByteItemsByAddress.empty()) { if (this->selectedByteItemAddresses.empty()) {
return; return;
} }
const auto excludedAddresses = this->excludedAddresses();
auto data = QString(); auto data = QString();
for (const auto& [address, byteItem] : this->sortedByteItemsByAddress()) { for (const auto& address : this->selectedByteItemAddresses) {
const unsigned char byteValue = byteItem->excluded const unsigned char byteValue = excludedAddresses.contains(address)
? 0x00 ? 0x00
: (*this->state.data)[byteItem->startAddress - this->state.memoryDescriptor.addressRange.startAddress]; : (*this->state.data)[address - this->state.memoryDescriptor.addressRange.startAddress];
data.append( data.append(
withDelimiters withDelimiters
@@ -764,16 +785,17 @@ namespace Widgets
} }
void ItemGraphicsScene::copyValueMappingToClipboard() { void ItemGraphicsScene::copyValueMappingToClipboard() {
if (this->selectedByteItemsByAddress.empty() || !this->state.data.has_value()) { if (this->selectedByteItemAddresses.empty() || !this->state.data.has_value()) {
return; return;
} }
const auto excludedAddresses = this->excludedAddresses();
auto data = QJsonObject(); auto data = QJsonObject();
for (const auto& [address, byteItem] : this->sortedByteItemsByAddress()) { for (const auto& address : this->selectedByteItemAddresses) {
const unsigned char byteValue = byteItem->excluded const unsigned char byteValue = excludedAddresses.contains(address)
? 0x00 ? 0x00
: (*this->state.data)[byteItem->startAddress - this->state.memoryDescriptor.addressRange.startAddress]; : (*this->state.data)[address - this->state.memoryDescriptor.addressRange.startAddress];
data.insert( data.insert(
"0x" + QString::number(address, 16).rightJustified(8, '0').toUpper(), "0x" + QString::number(address, 16).rightJustified(8, '0').toUpper(),
@@ -785,17 +807,18 @@ namespace Widgets
} }
void ItemGraphicsScene::copyAsciiValueToClipboard() { void ItemGraphicsScene::copyAsciiValueToClipboard() {
if (this->selectedByteItemsByAddress.empty() || !this->state.data.has_value()) { if (this->selectedByteItemAddresses.empty() || !this->state.data.has_value()) {
return; return;
} }
const auto excludedAddresses = this->excludedAddresses();
auto data = QString(); auto data = QString();
for (const auto& [address, byteItem] : this->sortedByteItemsByAddress()) { for (const auto& address : this->selectedByteItemAddresses) {
const unsigned char byteValue = const unsigned char byteValue =
(*this->state.data)[byteItem->startAddress - this->state.memoryDescriptor.addressRange.startAddress]; (*this->state.data)[address - this->state.memoryDescriptor.addressRange.startAddress];
if (byteItem->excluded || byteValue < 32 || byteValue > 126) { if (excludedAddresses.contains(address) || byteValue < 32 || byteValue > 126) {
continue; continue;
} }
@@ -804,4 +827,17 @@ namespace Widgets
QApplication::clipboard()->setText(std::move(data)); QApplication::clipboard()->setText(std::move(data));
} }
std::set<Targets::TargetMemoryAddress> ItemGraphicsScene::addressRangesToAddresses(
const std::set<Targets::TargetMemoryAddressRange>& addressRanges
) {
auto addresses = std::set<Targets::TargetMemoryAddress>();
for (const auto& range : addressRanges) {
const auto rangeAddresses = range.addresses();
addresses.insert(rangeAddresses.begin(), rangeAddresses.end());
}
return addresses;
}
} }

View File

@@ -56,7 +56,9 @@ namespace Widgets
void init(); void init();
void updateStackPointer(Targets::TargetStackPointer stackPointer); void updateStackPointer(Targets::TargetStackPointer stackPointer);
void selectByteItems(const std::set<Targets::TargetMemoryAddress>& addresses); void selectByteItems(const std::set<Targets::TargetMemoryAddress>& addresses);
void highlightByteItems(const std::set<Targets::TargetMemoryAddress>& addresses); void selectByteItemRanges(const std::set<Targets::TargetMemoryAddressRange>& addressRanges);
void highlightByteItemRanges(const std::set<Targets::TargetMemoryAddressRange>& addressRanges);
void clearByteItemHighlighting();
void rebuildItemHierarchy(); void rebuildItemHierarchy();
void adjustSize(); void adjustSize();
void setEnabled(bool enabled); void setEnabled(bool enabled);
@@ -67,9 +69,8 @@ namespace Widgets
signals: signals:
void ready(); void ready();
void hoveredAddress(const std::optional<Targets::TargetMemoryAddress>& address); void hoveredAddress(const std::optional<Targets::TargetMemoryAddress>& address);
void selectionChanged( void selectionChanged(const std::set<Targets::TargetMemoryAddress>& addresses);
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress void highlightingChanged(const std::set<Targets::TargetMemoryAddressRange>& addressRanges);
);
protected: protected:
bool enabled = true; bool enabled = true;
@@ -91,6 +92,7 @@ namespace Widgets
ByteAddressContainer* byteAddressContainer = nullptr; ByteAddressContainer* byteAddressContainer = nullptr;
std::unordered_map<Targets::TargetMemoryAddress, ByteItem*> selectedByteItemsByAddress; std::unordered_map<Targets::TargetMemoryAddress, ByteItem*> selectedByteItemsByAddress;
std::set<Targets::TargetMemoryAddress> selectedByteItemAddresses;
QGraphicsRectItem* rubberBandRectItem = nullptr; QGraphicsRectItem* rubberBandRectItem = nullptr;
std::optional<QPointF> rubberBandInitPoint = std::nullopt; std::optional<QPointF> rubberBandInitPoint = std::nullopt;
@@ -154,12 +156,15 @@ namespace Widgets
void clearByteItemSelection(); void clearByteItemSelection();
void selectAllByteItems(); void selectAllByteItems();
void setAddressType(AddressType type); void setAddressType(AddressType type);
std::map<Targets::TargetMemoryAddress, ByteItem*> sortedByteItemsByAddress(); std::set<Targets::TargetMemoryAddress> excludedAddresses();
void copyAddressesToClipboard(AddressType type); void copyAddressesToClipboard(AddressType type);
void copyHexValuesToClipboard(bool withDelimiters); void copyHexValuesToClipboard(bool withDelimiters);
void copyDecimalValuesToClipboard(); void copyDecimalValuesToClipboard();
void copyBinaryBitStringToClipboard(bool withDelimiters); void copyBinaryBitStringToClipboard(bool withDelimiters);
void copyValueMappingToClipboard(); void copyValueMappingToClipboard();
void copyAsciiValueToClipboard(); void copyAsciiValueToClipboard();
std::set<Targets::TargetMemoryAddress> addressRangesToAddresses(
const std::set<Targets::TargetMemoryAddressRange>& addressRanges
);
}; };
} }

View File

@@ -138,3 +138,14 @@ bool MemorySnapshot::isCompatible(const Targets::TargetMemoryDescriptor& memoryD
return true; return true;
} }
std::set<Targets::TargetMemoryAddress> MemorySnapshot::excludedAddresses() const {
auto output = std::set<Targets::TargetMemoryAddress>();
for (const auto& excludedRegion : this->excludedRegions) {
const auto regionAddresses = excludedRegion.addressRange.addresses();
output.insert(regionAddresses.begin(), regionAddresses.end());
}
return output;
}

View File

@@ -43,6 +43,7 @@ public:
QJsonObject toJson() const; QJsonObject toJson() const;
bool isCompatible(const Targets::TargetMemoryDescriptor& memoryDescriptor) const; bool isCompatible(const Targets::TargetMemoryDescriptor& memoryDescriptor) const;
std::set<Targets::TargetMemoryAddress> excludedAddresses() const;
virtual ~MemorySnapshot() = default; virtual ~MemorySnapshot() = default;

View File

@@ -12,5 +12,6 @@ namespace Widgets
bool syncingScroll = false; bool syncingScroll = false;
bool syncingHover = false; bool syncingHover = false;
bool syncingSelection = false; bool syncingSelection = false;
bool syncingHighlightedRanges = false;
}; };
} }

View File

@@ -44,6 +44,13 @@ namespace Widgets
this, this,
&DifferentialItemGraphicsScene::onOtherSelectionChanged &DifferentialItemGraphicsScene::onOtherSelectionChanged
); );
QObject::connect(
this->other,
&DifferentialItemGraphicsScene::highlightingChanged,
this,
&DifferentialItemGraphicsScene::onOtherHighlightedByteRangesChanged
);
} }
ByteItem* DifferentialItemGraphicsScene::byteItemAtViewportTop() { ByteItem* DifferentialItemGraphicsScene::byteItemAtViewportTop() {
@@ -97,23 +104,27 @@ namespace Widgets
} }
void DifferentialItemGraphicsScene::onOtherSelectionChanged( void DifferentialItemGraphicsScene::onOtherSelectionChanged(
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress const std::set<Targets::TargetMemoryAddress>& addresses
) { ) {
if (!this->snapshotDiffSettings.syncHexViewerSelection || this->diffHexViewerState.syncingSelection) { if (!this->snapshotDiffSettings.syncHexViewerSelection || this->diffHexViewerState.syncingSelection) {
return; return;
} }
this->diffHexViewerState.syncingSelection = true; this->diffHexViewerState.syncingSelection = true;
this->clearByteItemSelection(); this->selectByteItems(addresses);
this->diffHexViewerState.syncingSelection = false;
}
for (const auto& [address, otherByteItem] : selectedByteItemsByAddress) { void DifferentialItemGraphicsScene::onOtherHighlightedByteRangesChanged(
auto& byteItem = this->topLevelGroup->byteItemsByAddress.at(address); const std::set<Targets::TargetMemoryAddressRange>& addressRanges
byteItem.selected = true; ) {
this->selectedByteItemsByAddress.insert(std::pair(byteItem.startAddress, &byteItem)); if (this->diffHexViewerState.syncingHighlightedRanges) {
return;
} }
emit this->selectionChanged(this->selectedByteItemsByAddress); this->diffHexViewerState.syncingHighlightedRanges = true;
this->diffHexViewerState.syncingSelection = false; this->highlightByteItemRanges(addressRanges);
this->diffHexViewerState.syncingHighlightedRanges = false;
this->update(); this->update();
} }

View File

@@ -41,8 +41,7 @@ namespace Widgets
QMargins margins() override; QMargins margins() override;
void onOtherHoveredAddress(const std::optional<Targets::TargetMemoryAddress>& address); void onOtherHoveredAddress(const std::optional<Targets::TargetMemoryAddress>& address);
void onOtherSelectionChanged( void onOtherSelectionChanged(const std::set<Targets::TargetMemoryAddress>& addresses);
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress void onOtherHighlightedByteRangesChanged(const std::set<Targets::TargetMemoryAddressRange>& addressRanges);
);
}; };
} }

View File

@@ -85,7 +85,7 @@ namespace Widgets
this->restoreBytesAction = new ContextMenuAction( this->restoreBytesAction = new ContextMenuAction(
"Restore Selection", "Restore Selection",
[this] (const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>&) { [this] (const std::set<Targets::TargetMemoryAddress>&) {
return this->memoryDescriptor.access.writeableDuringDebugSession; return this->memoryDescriptor.access.writeableDuringDebugSession;
}, },
this this
@@ -95,8 +95,8 @@ namespace Widgets
this->restoreBytesAction, this->restoreBytesAction,
&ContextMenuAction::invoked, &ContextMenuAction::invoked,
this, this,
[this] (const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress) { [this] (const std::set<Targets::TargetMemoryAddress>& selectedByteItemAddresses) {
this->restoreSelectedBytes(selectedByteItemsByAddress, true); this->restoreSelectedBytes(selectedByteItemAddresses, true);
} }
); );
} }
@@ -446,20 +446,20 @@ namespace Widgets
} }
void SnapshotDiff::restoreSelectedBytes( void SnapshotDiff::restoreSelectedBytes(
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress, std::set<Targets::TargetMemoryAddress> addresses,
bool confirmationPromptEnabled bool confirmationPromptEnabled
) { ) {
auto sortedByteItemsByAddress = std::map<Targets::TargetMemoryAddress, ByteItem*>(); auto excludedAddresses = std::set<Targets::TargetMemoryAddress>();
for (const auto& excludedRegion : this->excludedRegionsA) {
for (const auto& pair : selectedByteItemsByAddress) { const auto regionAddresses = excludedRegion.addressRange.addresses();
if (pair.second->excluded) { excludedAddresses.insert(regionAddresses.begin(), regionAddresses.end());
continue;
}
sortedByteItemsByAddress.insert(pair);
} }
if (sortedByteItemsByAddress.empty()) { std::erase_if(addresses, [excludedAddresses] (const auto& address) {
return excludedAddresses.contains(address);
});
if (addresses.empty()) {
// The user has only selected bytes that are within an excluded region - nothing to do here // The user has only selected bytes that are within an excluded region - nothing to do here
return; return;
} }
@@ -467,8 +467,7 @@ namespace Widgets
if (confirmationPromptEnabled) { if (confirmationPromptEnabled) {
auto* confirmationDialog = new ConfirmationDialog( auto* confirmationDialog = new ConfirmationDialog(
"Restore selected bytes", "Restore selected bytes",
"This operation will write " + QString::number(sortedByteItemsByAddress.size()) "This operation will write " + QString::number(addresses.size()) + " byte(s) to the target's "
+ " byte(s) to the target's "
+ EnumToStringMappings::targetMemoryTypes.at(this->memoryDescriptor.type).toUpper() + EnumToStringMappings::targetMemoryTypes.at(this->memoryDescriptor.type).toUpper()
+ ".<br/><br/>Are you sure you want to proceed?", + ".<br/><br/>Are you sure you want to proceed?",
"Proceed", "Proceed",
@@ -480,8 +479,8 @@ namespace Widgets
confirmationDialog, confirmationDialog,
&ConfirmationDialog::confirmed, &ConfirmationDialog::confirmed,
this, this,
[this, selectedByteItemsByAddress] { [this, addresses] {
this->restoreSelectedBytes(selectedByteItemsByAddress, false); this->restoreSelectedBytes(addresses, false);
} }
); );
@@ -491,10 +490,10 @@ namespace Widgets
auto writeBlocks = std::vector<WriteTargetMemory::Block>(); auto writeBlocks = std::vector<WriteTargetMemory::Block>();
Targets::TargetMemoryAddress blockStartAddress = sortedByteItemsByAddress.begin()->first; Targets::TargetMemoryAddress blockStartAddress = *(addresses.begin());
Targets::TargetMemoryAddress blockEndAddress = blockStartAddress; Targets::TargetMemoryAddress blockEndAddress = blockStartAddress;
for (const auto& [address, byteItem] : sortedByteItemsByAddress) { for (const auto& address : addresses) {
if (address > (blockEndAddress + 1)) { if (address > (blockEndAddress + 1)) {
// Commit the block // Commit the block
const auto dataBeginOffset = blockStartAddress - this->memoryDescriptor.addressRange.startAddress; const auto dataBeginOffset = blockStartAddress - this->memoryDescriptor.addressRange.startAddress;

View File

@@ -128,7 +128,7 @@ namespace Widgets
void setSyncHexViewerSelectionEnabled(bool enabled); void setSyncHexViewerSelectionEnabled(bool enabled);
void restoreSelectedBytes( void restoreSelectedBytes(
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress, std::set<Targets::TargetMemoryAddress> addresses,
bool confirmationPromptEnabled bool confirmationPromptEnabled
); );
}; };

View File

@@ -126,7 +126,7 @@ namespace Widgets
this->restoreBytesAction = new ContextMenuAction( this->restoreBytesAction = new ContextMenuAction(
"Restore Selection", "Restore Selection",
[this] (const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>&) { [this] (const std::set<Targets::TargetMemoryAddress>&) {
return this->memoryDescriptor.access.writeableDuringDebugSession; return this->memoryDescriptor.access.writeableDuringDebugSession;
}, },
this this
@@ -170,8 +170,8 @@ namespace Widgets
this->restoreBytesAction, this->restoreBytesAction,
&ContextMenuAction::invoked, &ContextMenuAction::invoked,
this, this,
[this] (const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress) { [this] (const std::set<Targets::TargetMemoryAddress>& selectedByteItemAddresses) {
this->restoreSelectedBytes(selectedByteItemsByAddress, true); this->restoreSelectedBytes(selectedByteItemAddresses, true);
} }
); );
@@ -197,21 +197,15 @@ namespace Widgets
} }
void SnapshotViewer::restoreSelectedBytes( void SnapshotViewer::restoreSelectedBytes(
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress, std::set<Targets::TargetMemoryAddress> addresses,
bool confirmationPromptEnabled bool confirmationPromptEnabled
) { ) {
auto sortedByteItemsByAddress = std::map<Targets::TargetMemoryAddress, ByteItem*>(); const auto excludedAddresses = this->snapshot.excludedAddresses();
std::erase_if(addresses, [excludedAddresses] (const auto& address) {
return excludedAddresses.contains(address);
});
// Ideally, we'd use std::transform here, but that would require an additional pass to remove excluded bytes if (addresses.empty()) {
for (const auto& pair : selectedByteItemsByAddress) {
if (pair.second->excluded) {
continue;
}
sortedByteItemsByAddress.insert(pair);
}
if (sortedByteItemsByAddress.empty()) {
// The user has only selected bytes that are within an excluded region - nothing to do here // The user has only selected bytes that are within an excluded region - nothing to do here
return; return;
} }
@@ -219,8 +213,7 @@ namespace Widgets
if (confirmationPromptEnabled) { if (confirmationPromptEnabled) {
auto* confirmationDialog = new ConfirmationDialog( auto* confirmationDialog = new ConfirmationDialog(
"Restore selected bytes", "Restore selected bytes",
"This operation will write " + QString::number(sortedByteItemsByAddress.size()) "This operation will write " + QString::number(addresses.size()) + " byte(s) to the target's "
+ " byte(s) to the target's "
+ EnumToStringMappings::targetMemoryTypes.at(this->memoryDescriptor.type).toUpper() + EnumToStringMappings::targetMemoryTypes.at(this->memoryDescriptor.type).toUpper()
+ ".<br/><br/>Are you sure you want to proceed?", + ".<br/><br/>Are you sure you want to proceed?",
"Proceed", "Proceed",
@@ -232,8 +225,8 @@ namespace Widgets
confirmationDialog, confirmationDialog,
&ConfirmationDialog::confirmed, &ConfirmationDialog::confirmed,
this, this,
[this, selectedByteItemsByAddress] { [this, addresses] {
this->restoreSelectedBytes(selectedByteItemsByAddress, false); this->restoreSelectedBytes(addresses, false);
} }
); );
@@ -243,10 +236,10 @@ namespace Widgets
auto writeBlocks = std::vector<WriteTargetMemory::Block>(); auto writeBlocks = std::vector<WriteTargetMemory::Block>();
Targets::TargetMemoryAddress blockStartAddress = sortedByteItemsByAddress.begin()->first; Targets::TargetMemoryAddress blockStartAddress = *(addresses.begin());
Targets::TargetMemoryAddress blockEndAddress = blockStartAddress; Targets::TargetMemoryAddress blockEndAddress = blockStartAddress;
for (const auto& [address, byteItem] : sortedByteItemsByAddress) { for (const auto& address : addresses) {
if (address > (blockEndAddress + 1)) { if (address > (blockEndAddress + 1)) {
// Commit the block // Commit the block
const auto dataBeginOffset = blockStartAddress - this->memoryDescriptor.addressRange.startAddress; const auto dataBeginOffset = blockStartAddress - this->memoryDescriptor.addressRange.startAddress;

View File

@@ -63,7 +63,7 @@ namespace Widgets
void onHexViewerReady(); void onHexViewerReady();
void restoreSelectedBytes( void restoreSelectedBytes(
const std::unordered_map<Targets::TargetMemoryAddress, ByteItem*>& selectedByteItemsByAddress, std::set<Targets::TargetMemoryAddress> addresses,
bool confirmationPromptEnabled bool confirmationPromptEnabled
); );
}; };

View File

@@ -2,6 +2,7 @@
#include <cstdint> #include <cstdint>
#include <vector> #include <vector>
#include <set>
#include <optional> #include <optional>
namespace Targets namespace Targets
@@ -60,6 +61,17 @@ namespace Targets
[[nodiscard]] bool contains(const TargetMemoryAddressRange& addressRange) const { [[nodiscard]] bool contains(const TargetMemoryAddressRange& addressRange) const {
return this->startAddress <= addressRange.startAddress && this->endAddress >= addressRange.endAddress; return this->startAddress <= addressRange.startAddress && this->endAddress >= addressRange.endAddress;
} }
std::set<Targets::TargetMemoryAddress> addresses() const {
auto addresses = std::set<Targets::TargetMemoryAddress>();
auto addressesIt = addresses.end();
for (auto i = this->startAddress; i <= this->endAddress; ++i) {
addressesIt = addresses.insert(addressesIt, i);
}
return addresses;
}
}; };
struct TargetMemoryAccess struct TargetMemoryAccess