From 46edcad2c3dfdfa764578732808df7cd8616063c Mon Sep 17 00:00:00 2001 From: Xusheng Date: Thu, 23 Jul 2026 13:31:01 -0400 Subject: [PATCH] Navigate double-clicked sidebar addresses by content type (#1134) Double-clicking an address in a debugger sidebar now navigates the current pane when the target is the same kind of thing it already shows (both code, or both data) and the other pane otherwise, so following a data pointer no longer hijacks the code view and repeated double-clicks stop drifting across panes. A right-click "Navigate in Current Pane" action forces the focused pane. The graph/linear token double-click and the TTD time-travel columns are unchanged. Fixes https://github.com/Vector35/debugger/issues/1134 Co-Authored-By: Claude Opus 4.8 (1M context) --- ui/breakpointswidget.cpp | 12 ++++++- ui/debuggerinfowidget.cpp | 54 ++++++++++++++++++++++------ ui/debuggerinfowidget.h | 6 ++++ ui/debuggeruicommon.cpp | 51 +++++++++++++++++++++++++++ ui/debuggeruicommon.h | 18 ++++++++++ ui/memorymapwidget.cpp | 13 +++---- ui/moduleswidget.cpp | 13 +++---- ui/registerswidget.cpp | 19 ++++++++-- ui/registerswidget.h | 2 +- ui/threadframes.cpp | 63 ++++++++++++++++++++++++--------- ui/threadframes.h | 4 +++ ui/ttdcallswidget.cpp | 64 +++++++++++++++++++++++---------- ui/ttdcallswidget.h | 4 +++ ui/ttdeventswidget.cpp | 74 +++++++++++++++++++++++++++------------ ui/ttdeventswidget.h | 6 +++- ui/ttdmemorywidget.cpp | 68 +++++++++++++++++++++++------------ ui/ttdmemorywidget.h | 6 +++- 17 files changed, 363 insertions(+), 114 deletions(-) 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);