diff --git a/ui/breakpointswidget.cpp b/ui/breakpointswidget.cpp index a3dc39ce..bb57c3fa 100644 --- a/ui/breakpointswidget.cpp +++ b/ui/breakpointswidget.cpp @@ -29,6 +29,7 @@ limitations under the License. #include #include #include "breakpointswidget.h" +#include "debuggeruicommon.h" #include "base/assertions.h" #include "hardwarebreakpointdialog.h" #include "ui.h" @@ -453,7 +454,16 @@ DebugBreakpointsWidget::~DebugBreakpointsWidget() {} void DebugBreakpointsWidget::onDoubleClicked() { - jump(); + // Navigate to the breakpoint, opening it in the other pane when it is a different kind + // of thing (code vs data) than the current pane shows (see NavigateToAddress, issue + // #1134). The "Jump To Breakpoint" context menu action always uses the current pane. + QModelIndexList sel = selectionModel()->selectedRows(); + if (sel.empty()) + return; + + BreakpointItem bp = m_model->getRow(sel[0].row()); + if (m_controller->GetData()) + NavigateToAddress(this, m_controller->GetData(), bp.address()); } diff --git a/ui/debuggerinfowidget.cpp b/ui/debuggerinfowidget.cpp index c2ab9d82..397f2411 100644 --- a/ui/debuggerinfowidget.cpp +++ b/ui/debuggerinfowidget.cpp @@ -17,10 +17,13 @@ limitations under the License. #include #include #include +#include +#include #include #include #include "ui.h" #include "debuggerinfowidget.h" +#include "debuggeruicommon.h" #include "lowlevelilinstruction.h" #include "mediumlevelilinstruction.h" #include "highlevelilinstruction.h" @@ -935,25 +938,56 @@ void DebuggerInfoTable::updateFonts() } -void DebuggerInfoTable::onDoubleClicked() +bool DebuggerInfoTable::selectedValue(uint64_t& value) { QModelIndexList sel = selectionModel()->selectedIndexes(); if (sel.empty()) - return; + return false; - auto info = m_model->getRow(sel[0].row()); - uint64_t value = (uint64_t)info.value; + value = (uint64_t)m_model->getRow(sel[0].row()).value; + return true; +} - UIContext* context = UIContext::contextForWidget(this); - if (!context) - return; - ViewFrame* frame = context->getCurrentViewFrame(); - if (!frame) +void DebuggerInfoTable::onDoubleClicked() +{ + uint64_t value = 0; + if (!selectedValue(value)) return; + // Navigate to the target, opening it in the other pane when it is a different kind + // of thing (code vs data) than the current pane shows (see NavigateToAddress, #1134). if (m_debugger->GetData()) - frame->navigate(m_debugger->GetData(), value, true, true); + NavigateToAddress(this, m_debugger->GetData(), value); +} + + +void DebuggerInfoTable::navigateInCurrentPane() +{ + uint64_t value = 0; + if (selectedValue(value) && m_debugger->GetData()) + NavigateToAddressInCurrentPane(this, m_debugger->GetData(), value); +} + + +void DebuggerInfoTable::contextMenuEvent(QContextMenuEvent* event) +{ + // Select the row under the cursor so the action targets the right entry even when the + // right-click did not already move the selection. + QModelIndex index = indexAt(event->pos()); + if (index.isValid()) + selectRow(index.row()); + + // Force navigation into the currently focused pane (double-click instead picks the + // pane by content type; see NavigateToAddress). + uint64_t value = 0; + if (!selectedValue(value)) + return; + + QMenu menu(this); + QAction* navigate = menu.addAction("Navigate in Current Pane"); + connect(navigate, &QAction::triggered, this, &DebuggerInfoTable::navigateInCurrentPane); + menu.exec(event->globalPos()); } diff --git a/ui/debuggerinfowidget.h b/ui/debuggerinfowidget.h index c2054ff7..bd7d133d 100644 --- a/ui/debuggerinfowidget.h +++ b/ui/debuggerinfowidget.h @@ -127,9 +127,15 @@ Q_OBJECT; std::vector getInfoForHLILConditions(HighLevelILFunctionRef hlil, const HighLevelILInstruction& instr); void updateColumnWidths(); + // The value of the selected entry, interpreted as an address, or false if there is no + // selection. + bool selectedValue(uint64_t& value); + + virtual void contextMenuEvent(QContextMenuEvent* event) override; private slots: void onDoubleClicked(); + void navigateInCurrentPane(); public: DebuggerInfoTable(BinaryViewRef data); diff --git a/ui/debuggeruicommon.cpp b/ui/debuggeruicommon.cpp index 39408c59..d836e169 100644 --- a/ui/debuggeruicommon.cpp +++ b/ui/debuggeruicommon.cpp @@ -15,6 +15,8 @@ limitations under the License. */ #include "debuggeruicommon.h" +#include "uicontext.h" +#include "viewframe.h" using namespace BinaryNinja; @@ -69,3 +71,52 @@ bool ParseAddress(const QString& text, BinaryNinja::Ref return false; } + + +static bool AddressIsInFunction(Ref data, uint64_t address) +{ + return data && !data->GetAnalysisFunctionsContainingAddress(address).empty(); +} + + +void NavigateToAddress(QWidget* widget, Ref data, uint64_t target) +{ + UIContext* context = UIContext::contextForWidget(widget); + if (!context) + return; + + ViewFrame* frame = context->getCurrentViewFrame(); + if (!frame) + return; + + View* view = frame->getCurrentViewInterface(); + + // Decide same-pane vs other-pane by whether the target and the current view are the + // same kind of thing (both code, or both data). Matching types navigate in place; + // crossing between code and data opens the target in the other pane so what the user + // is looking at stays visible. + bool targetInFunction = AddressIsInFunction(data, target); + bool currentInFunction = view && AddressIsInFunction(data, view->getCurrentOffset()); + + if (view && currentInFunction == targetInFunction) + { + frame->navigate(data, target, true, true); + return; + } + + // Different types: show the target in the other pane. If there is no other pane to use + // (or no view), fall back to navigating the current pane. + if (!view || !view->navigateOnOtherPane(target)) + frame->navigate(data, target, true, true); +} + + +void NavigateToAddressInCurrentPane(QWidget* widget, Ref data, uint64_t target) +{ + UIContext* context = UIContext::contextForWidget(widget); + if (!context) + return; + + if (ViewFrame* frame = context->getCurrentViewFrame()) + frame->navigate(data, target, true, true); +} diff --git a/ui/debuggeruicommon.h b/ui/debuggeruicommon.h index 1835c583..e851a342 100644 --- a/ui/debuggeruicommon.h +++ b/ui/debuggeruicommon.h @@ -51,3 +51,21 @@ class NumericalTableWidgetItem : public QTableWidgetItem // Returns true if parsing succeeded and sets 'result' to the parsed address. // Returns false if parsing failed. bool ParseAddress(const QString& text, BinaryNinja::Ref data, uint64_t& result, std::string* errorMessage = nullptr); + +// Navigate to `target` from a debugger sidebar widget, choosing the pane by content type. +// +// If the current pane and `target` are the same kind of thing -- both inside a function +// (code), or both outside one (data) -- navigate the current pane in place, like an +// ordinary jump. If they differ, open `target` in the other pane so what the user is +// looking at stays visible. Because a data target then matches the (data) companion pane, +// subsequent data double-clicks reuse that pane instead of bouncing back to the code pane; +// that is what keeps repeated double-clicks from drifting. Falls back to the current pane +// when there is no other pane available. +// +// `widget` is any widget inside the window whose current view should be navigated from. +void NavigateToAddress(QWidget* widget, BinaryNinja::Ref data, uint64_t target); + +// Navigate to `target` in the currently focused pane, in place, unconditionally. This is +// the "force it into the pane I have focused" fallback exposed via the right-click menu. +void NavigateToAddressInCurrentPane( + QWidget* widget, BinaryNinja::Ref data, uint64_t target); diff --git a/ui/memorymapwidget.cpp b/ui/memorymapwidget.cpp index 9678bf84..9822423c 100644 --- a/ui/memorymapwidget.cpp +++ b/ui/memorymapwidget.cpp @@ -25,6 +25,7 @@ limitations under the License. #include #include "ui.h" #include "memorymapwidget.h" +#include "debuggeruicommon.h" #include "clickablelabel.h" using namespace BinaryNinja; @@ -705,16 +706,10 @@ void DebugMemoryMapWidget::onDoubleClicked() else address = region.endAddress(); - UIContext* context = UIContext::contextForWidget(this); - if (!context) - return; - - ViewFrame* frame = context->getCurrentViewFrame(); - if (!frame) - return; - + // Navigate to the target, opening it in the other pane when it is a different kind + // of thing (code vs data) than the current pane shows (see NavigateToAddress, #1134). if (m_controller->GetData()) - frame->navigate(m_controller->GetData(), address, true, true); + NavigateToAddress(this, m_controller->GetData(), address); }; diff --git a/ui/moduleswidget.cpp b/ui/moduleswidget.cpp index 035a6543..447323f9 100644 --- a/ui/moduleswidget.cpp +++ b/ui/moduleswidget.cpp @@ -21,6 +21,7 @@ limitations under the License. #include #include "ui.h" #include "moduleswidget.h" +#include "debuggeruicommon.h" #include "clickablelabel.h" using namespace BinaryNinja; @@ -697,16 +698,10 @@ void DebugModulesWidget::onDoubleClicked() else address = module.endAddress(); - UIContext* context = UIContext::contextForWidget(this); - if (!context) - return; - - ViewFrame* frame = context->getCurrentViewFrame(); - if (!frame) - return; - + // Navigate to the target, opening it in the other pane when it is a different kind + // of thing (code vs data) than the current pane shows (see NavigateToAddress, #1134). if (m_controller->GetData()) - frame->navigate(m_controller->GetData(), address, true, true); + NavigateToAddress(this, m_controller->GetData(), address); }; diff --git a/ui/registerswidget.cpp b/ui/registerswidget.cpp index 10fb6c44..e5fd2d03 100644 --- a/ui/registerswidget.cpp +++ b/ui/registerswidget.cpp @@ -25,6 +25,7 @@ limitations under the License. #include "util.h" #include "clickablelabel.h" #include "registerswidget.h" +#include "debuggeruicommon.h" #include "base/assertions.h" using namespace BinaryNinja; @@ -804,9 +805,23 @@ bool DebugRegistersWidget::canPaste() } -void DebugRegistersWidget::onDoubleClicked() +void DebugRegistersWidget::onDoubleClicked(const QModelIndex& index) { - jump(); + // Navigate to the address the register holds, opening it in the other pane when it is + // a different kind of thing (code vs data) than what the current pane shows, so the + // view the user is looking at stays put (see NavigateToAddress and issue #1134). + if (!index.isValid()) + return; + + auto sourceIndex = m_filter->mapToSource(index); + if (!sourceIndex.isValid()) + return; + + auto reg = m_model->getRow(sourceIndex.row()); + uint64_t value = (uint64_t)reg.value(); + + if (m_controller->GetData()) + NavigateToAddress(this, m_controller->GetData(), value); } diff --git a/ui/registerswidget.h b/ui/registerswidget.h index 180c0a1a..aca40f8e 100644 --- a/ui/registerswidget.h +++ b/ui/registerswidget.h @@ -210,7 +210,7 @@ private slots: void editValue(); void goToPrevRegisterWrite(); void goToNextRegisterWrite(); - void onDoubleClicked(); + void onDoubleClicked(const QModelIndex& index); void hoverTimerEvent(); public slots: diff --git a/ui/threadframes.cpp b/ui/threadframes.cpp index 536c7e56..8da649c8 100644 --- a/ui/threadframes.cpp +++ b/ui/threadframes.cpp @@ -15,6 +15,7 @@ limitations under the License. */ #include "threadframes.h" +#include "debuggeruicommon.h" #include FrameItem::~FrameItem() @@ -735,6 +736,16 @@ ThreadFramesWidget::ThreadFramesWidget(QWidget* parent, ViewFrame* frame, Binary m_menu.addAction(actionName, "Options", MENU_ORDER_NORMAL); m_actionHandler.bindAction(actionName, UIAction([this]() { copyAllFrames(); })); + // Force navigation into the currently focused pane (double-click instead picks the + // pane by content type; see NavigateToAddress). + actionName = QString::fromStdString("Navigate in Current Pane"); + UIAction::registerAction(actionName); + m_menu.addAction(actionName, "Options", MENU_ORDER_FIRST); + m_actionHandler.bindAction(actionName, UIAction([this]() { navigateInCurrentPane(); }, [this]() { + uint64_t addr = 0; + return navigationAddressForSelection(addr); + })); + // TODO: set as active thread action? connect(this, &QTreeView::doubleClicked, this, &ThreadFramesWidget::onDoubleClicked); @@ -849,30 +860,50 @@ void ThreadFramesWidget::onDoubleClicked() } uint64_t addrToJump = 0; - switch (column) + if (!navigationAddressForSelection(addrToJump)) + return; + + // Navigate to the target, opening it in the other pane when it is a different kind + // of thing (code vs data) than the current pane shows (see NavigateToAddress, #1134). + if (m_debugger->GetData()) + NavigateToAddress(this, m_debugger->GetData(), addrToJump); +} + + +bool ThreadFramesWidget::navigationAddressForSelection(uint64_t& addr) +{ + QModelIndexList sel = selectionModel()->selectedIndexes(); + if (sel.empty()) + return false; + + const QModelIndex& index = sel[0]; + FrameItem* frameItem = static_cast(index.internalPointer()); + if (!frameItem || !frameItem->isFrame()) + return false; + + switch (index.column()) { case ThreadFrameModel::FunctionColumn: case ThreadFrameModel::PcColumn: - addrToJump = frameItem->framePc(); - break; + addr = frameItem->framePc(); + return true; case ThreadFrameModel::SpColumn: - addrToJump = frameItem->sp(); - break; + addr = frameItem->sp(); + return true; case ThreadFrameModel::FpColumn: - addrToJump = frameItem->fp(); - break; + addr = frameItem->fp(); + return true; + default: + return false; } +} - UIContext* context = UIContext::contextForWidget(this); - if (!context) - return; - ViewFrame* frame = context->getCurrentViewFrame(); - if (!frame) - return; - - if (m_debugger->GetData()) - frame->navigate(m_debugger->GetData(), addrToJump, true, true); +void ThreadFramesWidget::navigateInCurrentPane() +{ + uint64_t addr = 0; + if (navigationAddressForSelection(addr) && m_debugger->GetData()) + NavigateToAddressInCurrentPane(this, m_debugger->GetData(), addr); } diff --git a/ui/threadframes.h b/ui/threadframes.h index 4d5423d4..6e9c45dc 100644 --- a/ui/threadframes.h +++ b/ui/threadframes.h @@ -175,6 +175,9 @@ class ThreadFramesWidget : public QTreeView virtual void contextMenuEvent(QContextMenuEvent* event) override; bool selectionNotEmpty(); + // The address the current column of the selected frame navigates to (PC/SP/FP), or + // false if the selection is not a navigable frame cell. + bool navigationAddressForSelection(uint64_t& addr); bool canSuspendOrResume(); void expandCurrentThread(); void updateContent(); @@ -193,6 +196,7 @@ public slots: private slots: void onDoubleClicked(); + void navigateInCurrentPane(); void suspendThread(); void resumeThread(); void makeItSoloThread(); diff --git a/ui/ttdcallswidget.cpp b/ui/ttdcallswidget.cpp index 2ad30a1f..267a8968 100644 --- a/ui/ttdcallswidget.cpp +++ b/ui/ttdcallswidget.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "ttdcallswidget.h" #include "ttdbookmarkwidget.h" #include "ui.h" +#include "debuggeruicommon.h" #include #include #include @@ -188,6 +189,14 @@ void TTDCallsQueryWidget::setupUIActions() m_menu.addAction("Reset Columns to Default", "Options", MENU_ORDER_NORMAL); m_actionHandler.bindAction("Reset Columns to Default", UIAction([&]() { resetColumnsToDefault(); })); + // Force navigation into the currently focused pane (double-click instead picks the + // pane by content type; see NavigateToAddress). + m_menu.addAction("Navigate in Current Pane", "Navigate", MENU_ORDER_FIRST); + m_actionHandler.bindAction("Navigate in Current Pane", UIAction([&]() { navigateInCurrentPane(); }, [&]() { + uint64_t addr = 0; + return addressForCell(m_resultsTable->currentRow(), m_resultsTable->currentColumn(), addr); + })); + m_menu.addAction("Add TTD Bookmark...", "Bookmark", MENU_ORDER_NORMAL); m_actionHandler.bindAction("Add TTD Bookmark...", UIAction([&]() { int row = m_resultsTable->currentRow(); @@ -484,30 +493,47 @@ void TTDCallsQueryWidget::onCellDoubleClicked(int row, int column) } else if (column == FunctionAddressColumn || column == ReturnAddressColumn) { - // Navigate to address in Binary Ninja - QTableWidgetItem* item = m_resultsTable->item(row, column); - if (item) + uint64_t address = 0; + if (addressForCell(row, column, address)) { - QString addressText = item->text(); - if (addressText.startsWith("0x", Qt::CaseInsensitive)) - { - bool ok; - uint64_t address = addressText.mid(2).toULongLong(&ok, 16); - - if (ok && address != 0) - { - // Navigate to address in Binary Ninja - ViewFrame* frame = ViewFrame::viewFrameForWidget(this); - if (frame) - { - frame->navigate(m_data, address); - } - } - } + // Navigate to the address, opening it in the other pane when it is a different + // kind of thing (code vs data) than the current pane shows (see NavigateToAddress, + // issue #1134). The time-travel columns above intentionally keep the current pane. + NavigateToAddress(this, m_data, address); } } } + +bool TTDCallsQueryWidget::addressForCell(int row, int column, uint64_t& addr) +{ + if (row < 0 || row >= m_resultsTable->rowCount()) + return false; + + // The return-address column navigates to the return address; any other cell navigates + // to the row's function address. + int addrColumn = (column == ReturnAddressColumn) ? ReturnAddressColumn : FunctionAddressColumn; + QTableWidgetItem* item = m_resultsTable->item(row, addrColumn); + if (!item) + return false; + + QString text = item->text(); + if (!text.startsWith("0x", Qt::CaseInsensitive)) + return false; + + bool ok = false; + addr = text.mid(2).toULongLong(&ok, 16); + return ok && addr != 0; +} + + +void TTDCallsQueryWidget::navigateInCurrentPane() +{ + uint64_t addr = 0; + if (addressForCell(m_resultsTable->currentRow(), m_resultsTable->currentColumn(), addr)) + NavigateToAddressInCurrentPane(this, m_data, addr); +} + void TTDCallsQueryWidget::updateColumnVisibility() { // Apply column visibility settings diff --git a/ui/ttdcallswidget.h b/ui/ttdcallswidget.h index e09b78be..a2dbe4ff 100644 --- a/ui/ttdcallswidget.h +++ b/ui/ttdcallswidget.h @@ -107,6 +107,9 @@ class TTDCallsQueryWidget : public QWidget void setupUIActions(); void updateColumnVisibility(); bool canCopy(); + // The raw address a given cell navigates to (the function address, or the return + // address for the return-address column), or false if the cell has no address. + bool addressForCell(int row, int column, uint64_t& addr); virtual void contextMenuEvent(QContextMenuEvent* event) override; @@ -133,6 +136,7 @@ private Q_SLOTS: void performQuery(); void clearResults(); void onCellDoubleClicked(int row, int column); + void navigateInCurrentPane(); void showColumnVisibilityDialog(); void resetColumnsToDefault(); void showContextMenu(const QPoint& position); diff --git a/ui/ttdeventswidget.cpp b/ui/ttdeventswidget.cpp index f62bd907..d0c27ff7 100644 --- a/ui/ttdeventswidget.cpp +++ b/ui/ttdeventswidget.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "ttdeventswidget.h" #include "ttdbookmarkwidget.h" #include "ui.h" +#include "debuggeruicommon.h" #include #include #include @@ -349,6 +350,14 @@ void TTDEventsQueryWidget::setupUIActions() m_menu.addAction("Refresh", "Options", MENU_ORDER_NORMAL); m_actionHandler.bindAction("Refresh", UIAction([&]() { refreshEvents(); })); + // Force navigation into the currently focused pane (double-click instead picks the + // pane by content type; see NavigateToAddress). + m_menu.addAction("Navigate in Current Pane", "Navigate", MENU_ORDER_FIRST); + m_actionHandler.bindAction("Navigate in Current Pane", UIAction([&]() { navigateInCurrentPane(); }, [&]() { + uint64_t addr = 0; + return addressForCell(m_resultsTable->currentRow(), m_resultsTable->currentColumn(), addr); + })); + m_menu.addAction("Add TTD Bookmark...", "Bookmark", MENU_ORDER_NORMAL); m_actionHandler.bindAction("Add TTD Bookmark...", UIAction([&]() { int row = m_resultsTable->currentRow(); @@ -817,34 +826,55 @@ void TTDEventsQueryWidget::onCellDoubleClicked(int row, int column) } } // Check if this is an address column - jump to address - else if (columnName.contains("Address", Qt::CaseInsensitive) || - columnName.contains("PC", Qt::CaseInsensitive) || - column == ModuleAddressColumn || - column == ExceptionPCColumn) + else { - if (cellText.startsWith("0x")) + uint64_t address = 0; + if (addressForCell(row, column, address)) { - bool ok; - uint64_t address = cellText.mid(2).toULongLong(&ok, 16); - if (ok) - { - // Jump to address in disassembly view - // Navigate to the address in the disassembly view - ViewFrame* frame = ViewFrame::viewFrameForWidget(this); - if (frame) - { - frame->navigate(m_data, address); - updateStatus(QString("Navigated to address %1").arg(cellText)); - } - else - { - updateStatus(QString("Address: %1 (no view frame available)").arg(cellText)); - } - } + // Navigate to the address, opening it in the other pane when it is a different + // kind of thing (code vs data) than the current pane shows (see NavigateToAddress, + // issue #1134). The Position (time-travel) column above keeps the current pane. + NavigateToAddress(this, m_data, address); + updateStatus(QString("Navigated to address %1").arg(cellText)); } } } + +bool TTDEventsQueryWidget::addressForCell(int row, int column, uint64_t& addr) +{ + if (row < 0 || row >= m_resultsTable->rowCount()) + return false; + + QString columnName = m_resultsTable->horizontalHeaderItem(column) + ? m_resultsTable->horizontalHeaderItem(column)->text() : ""; + bool isAddressColumn = columnName.contains("Address", Qt::CaseInsensitive) + || columnName.contains("PC", Qt::CaseInsensitive) + || column == ModuleAddressColumn || column == ExceptionPCColumn; + if (!isAddressColumn) + return false; + + QTableWidgetItem* item = m_resultsTable->item(row, column); + if (!item) + return false; + + QString text = item->text(); + if (!text.startsWith("0x", Qt::CaseInsensitive)) + return false; + + bool ok = false; + addr = text.mid(2).toULongLong(&ok, 16); + return ok; +} + + +void TTDEventsQueryWidget::navigateInCurrentPane() +{ + uint64_t addr = 0; + if (addressForCell(m_resultsTable->currentRow(), m_resultsTable->currentColumn(), addr)) + NavigateToAddressInCurrentPane(this, m_data, addr); +} + void TTDEventsQueryWidget::contextMenuEvent(QContextMenuEvent* event) { if (m_contextMenuManager) diff --git a/ui/ttdeventswidget.h b/ui/ttdeventswidget.h index 0f6bb280..b8e434d9 100644 --- a/ui/ttdeventswidget.h +++ b/ui/ttdeventswidget.h @@ -131,9 +131,12 @@ class TTDEventsQueryWidget : public QWidget void setupUIActions(); void updateColumnVisibility(); bool canCopy(); + // The raw address the given cell navigates to (from an Address/PC column), or false if + // the cell is not an address cell. + bool addressForCell(int row, int column, uint64_t& addr); void filterAndDisplayEvents(); void filterAndDisplaySpecializedEvents(); // For specialized widget types - + virtual void contextMenuEvent(QContextMenuEvent* event) override; public: @@ -152,6 +155,7 @@ public Q_SLOTS: private Q_SLOTS: void performQuery(); void onCellDoubleClicked(int row, int column); + void navigateInCurrentPane(); void showColumnVisibilityDialog(); void resetColumnsToDefault(); void showContextMenu(const QPoint& position); diff --git a/ui/ttdmemorywidget.cpp b/ui/ttdmemorywidget.cpp index ebf8f872..8e5b73e6 100644 --- a/ui/ttdmemorywidget.cpp +++ b/ui/ttdmemorywidget.cpp @@ -325,6 +325,14 @@ void TTDMemoryQueryWidget::setupUIActions() m_menu.addAction("Reset Columns to Default", "Options", MENU_ORDER_NORMAL); m_actionHandler.bindAction("Reset Columns to Default", UIAction([&]() { resetColumnsToDefault(); })); + // Force navigation into the currently focused pane (double-click instead picks the + // pane by content type; see NavigateToAddress). + m_menu.addAction("Navigate in Current Pane", "Navigate", MENU_ORDER_FIRST); + m_actionHandler.bindAction("Navigate in Current Pane", UIAction([&]() { navigateInCurrentPane(); }, [&]() { + uint64_t addr = 0; + return m_data && addressForCell(m_resultsTable->currentRow(), m_resultsTable->currentColumn(), addr); + })); + m_menu.addAction("Add TTD Bookmark...", "Bookmark", MENU_ORDER_NORMAL); m_actionHandler.bindAction("Add TTD Bookmark...", UIAction([&]() { int row = m_resultsTable->currentRow(); @@ -610,34 +618,48 @@ void TTDMemoryQueryWidget::onCellDoubleClicked(int row, int column) } else if (column == AddressColumn || column == IPColumn) { - // Navigate to address in disassembly view - QTableWidgetItem* addrItem = m_resultsTable->item(row, column); - if (addrItem && m_data) + uint64_t address = 0; + if (m_data && addressForCell(row, column, address)) { - QString addrStr = addrItem->text(); - if (addrStr.startsWith("0x", Qt::CaseInsensitive)) - { - bool ok; - uint64_t address = addrStr.mid(2).toULongLong(&ok, 16); - if (ok) - { - // Navigate to the address in the disassembly view - ViewFrame* frame = ViewFrame::viewFrameForWidget(this); - if (frame) - { - frame->navigate(m_data, address); - updateStatus(QString("Navigated to address %1").arg(addrStr)); - } - else - { - updateStatus(QString("Address: %1 (no view frame available)").arg(addrStr)); - } - } - } + // Navigate to the address, opening it in the other pane when it is a different + // kind of thing (code vs data) than the current pane shows (see NavigateToAddress, + // issue #1134). The Position (time-travel) column above keeps the current pane. + NavigateToAddress(this, m_data, address); + updateStatus(QString("Navigated to address 0x%1").arg(address, 0, 16)); } } } + +bool TTDMemoryQueryWidget::addressForCell(int row, int column, uint64_t& addr) +{ + if (row < 0 || row >= m_resultsTable->rowCount()) + return false; + + // The IP column navigates to the instruction pointer; any other cell navigates to the + // row's memory address. + int addrColumn = (column == IPColumn) ? IPColumn : AddressColumn; + QTableWidgetItem* item = m_resultsTable->item(row, addrColumn); + if (!item) + return false; + + QString text = item->text(); + if (!text.startsWith("0x", Qt::CaseInsensitive)) + return false; + + bool ok = false; + addr = text.mid(2).toULongLong(&ok, 16); + return ok; +} + + +void TTDMemoryQueryWidget::navigateInCurrentPane() +{ + uint64_t addr = 0; + if (m_data && addressForCell(m_resultsTable->currentRow(), m_resultsTable->currentColumn(), addr)) + NavigateToAddressInCurrentPane(this, m_data, addr); +} + void TTDMemoryQueryWidget::showColumnVisibilityDialog() { ColumnVisibilityDialog dialog(this, m_columnNames, m_columnVisibility); diff --git a/ui/ttdmemorywidget.h b/ui/ttdmemorywidget.h index b7347bf8..1c709c25 100644 --- a/ui/ttdmemorywidget.h +++ b/ui/ttdmemorywidget.h @@ -123,7 +123,10 @@ class TTDMemoryQueryWidget : public QWidget void updateColumnVisibility(); void selectRowByPosition(const TTDPosition& position); bool canCopy(); - + // The raw address a given cell navigates to (the IP for the IP column, otherwise the + // memory address), or false if the cell has no address. + bool addressForCell(int row, int column, uint64_t& addr); + virtual void contextMenuEvent(QContextMenuEvent* event) override; public: @@ -149,6 +152,7 @@ private Q_SLOTS: void performQuery(); void clearResults(); void onCellDoubleClicked(int row, int column); + void navigateInCurrentPane(); void showColumnVisibilityDialog(); void resetColumnsToDefault(); void showContextMenu(const QPoint& position);