From 686c4b81c19a0365330d62fe65f752ab8622b717 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 23 Aug 2025 15:32:42 +1000 Subject: [PATCH] Qt: Add custom code view for debugger Branch arrows, syntax highlighting. --- src/duckstation-qt/CMakeLists.txt | 2 + src/duckstation-qt/debuggercodeview.cpp | 690 ++++++++++++++++++ src/duckstation-qt/debuggercodeview.h | 123 ++++ src/duckstation-qt/debuggermodels.cpp | 276 ------- src/duckstation-qt/debuggermodels.h | 41 -- src/duckstation-qt/debuggerwindow.cpp | 115 +-- src/duckstation-qt/debuggerwindow.h | 13 +- src/duckstation-qt/debuggerwindow.ui | 8 +- src/duckstation-qt/duckstation-qt.vcxproj | 2 + .../duckstation-qt.vcxproj.filters | 2 + 10 files changed, 864 insertions(+), 408 deletions(-) create mode 100644 src/duckstation-qt/debuggercodeview.cpp create mode 100644 src/duckstation-qt/debuggercodeview.h diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index a789b0c5f..94d797aa9 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -57,6 +57,8 @@ set(SRCS coverdownloadwindow.h coverdownloadwindow.ui debuggeraddbreakpointdialog.ui + debuggercodeview.cpp + debuggercodeview.h debuggermodels.cpp debuggermodels.h debuggerwindow.cpp diff --git a/src/duckstation-qt/debuggercodeview.cpp b/src/duckstation-qt/debuggercodeview.cpp new file mode 100644 index 000000000..84d857bec --- /dev/null +++ b/src/duckstation-qt/debuggercodeview.cpp @@ -0,0 +1,690 @@ +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#include "debuggercodeview.h" + +#include "core/bus.h" +#include "core/cpu_core.h" +#include "core/cpu_core_private.h" +#include "core/cpu_disasm.h" +#include "core/cpu_types.h" + +#include "common/log.h" +#include "common/small_string.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "moc_debuggercodeview.cpp" + +LOG_CHANNEL(Host); + +DebuggerCodeView::DebuggerCodeView(QWidget* parent) : QAbstractScrollArea(parent) +{ + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + updateRowHeight(); + + // Load icons + m_pc_pixmap = QIcon(QStringLiteral(":/icons/debug-pc.png")).pixmap(12); + m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(12); + + // Connect scroll bar + connect(verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int value) { + const VirtualMemoryAddress new_top = getAddressForRow(value); + // Clamp to valid range to prevent wrapping + if (new_top >= m_code_region_start && new_top < m_code_region_end) + { + m_top_address = new_top; + updateVisibleRange(); + // Recalculate arrows when scroll position changes + calculateBranchArrows(); + viewport()->update(); + } + }); + + setFocusPolicy(Qt::StrongFocus); + setAttribute(Qt::WA_OpaquePaintEvent); + + // Initialize code region + resetCodeView(0); +} + +DebuggerCodeView::~DebuggerCodeView() = default; + +void DebuggerCodeView::updateRowHeight() +{ + const QFontMetrics font_metrics(fontMetrics()); + m_row_height = font_metrics.height() + 2; + m_char_width = font_metrics.horizontalAdvance('M'); +} + +void DebuggerCodeView::resetCodeView(VirtualMemoryAddress start_address) +{ + updateRegion(start_address); + calculateBranchArrows(); // Recalculate arrows when region changes +} + +void DebuggerCodeView::setPC(VirtualMemoryAddress pc) +{ + if (m_last_pc == pc) + return; + + m_last_pc = pc; + + if (!updateRegion(pc)) + { + // Just update the display if region didn't change + viewport()->update(); + } + else + { + // Region changed, recalculate arrows + calculateBranchArrows(); + } +} + +void DebuggerCodeView::ensureAddressVisible(VirtualMemoryAddress address) +{ + const bool region_changed = updateRegion(address); + if (region_changed) + { + calculateBranchArrows(); + } +} + +void DebuggerCodeView::setBreakpointState(VirtualMemoryAddress address, bool enabled) +{ + if (enabled) + { + if (std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end()) + return; + + m_breakpoints.push_back(address); + } + else + { + auto it = std::find(m_breakpoints.begin(), m_breakpoints.end(), address); + if (it == m_breakpoints.end()) + return; + + m_breakpoints.erase(it); + } + + viewport()->update(); +} + +void DebuggerCodeView::clearBreakpoints() +{ + m_breakpoints.clear(); + viewport()->update(); +} + +bool DebuggerCodeView::hasBreakpointAtAddress(VirtualMemoryAddress address) const +{ + return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end(); +} + +int DebuggerCodeView::getRowForAddress(VirtualMemoryAddress address) const +{ + if (address < m_code_region_start) + return 0; + if (address >= m_code_region_end) + return static_cast((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE) - 1; + + return static_cast((address - m_code_region_start) / CPU::INSTRUCTION_SIZE); +} + +VirtualMemoryAddress DebuggerCodeView::getAddressForRow(int row) const +{ + const VirtualMemoryAddress address = m_code_region_start + (static_cast(row) * CPU::INSTRUCTION_SIZE); + return std::clamp(address, m_code_region_start, m_code_region_end - CPU::INSTRUCTION_SIZE); +} + +bool DebuggerCodeView::updateRegion(VirtualMemoryAddress address) +{ + CPU::Segment segment = CPU::GetSegmentForAddress(address); + std::optional region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address)); + if (!region.has_value() || (address >= m_code_region_start && address < m_code_region_end)) + return false; + + static constexpr unsigned NUM_INSTRUCTIONS_BEFORE = 4096; + static constexpr unsigned NUM_INSTRUCTIONS_AFTER = 4096; + static constexpr unsigned NUM_BYTES_BEFORE = NUM_INSTRUCTIONS_BEFORE * sizeof(u32); + static constexpr unsigned NUM_BYTES_AFTER = NUM_INSTRUCTIONS_AFTER * sizeof(u32); + + const VirtualMemoryAddress start_address = + CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionStart(region.value()), segment); + const VirtualMemoryAddress end_address = + CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionEnd(region.value()), segment); + + m_code_region_start = ((address - start_address) < NUM_BYTES_BEFORE) ? start_address : (address - NUM_BYTES_BEFORE); + m_code_region_end = ((end_address - address) < NUM_BYTES_AFTER) ? end_address : (address + NUM_BYTES_AFTER); + m_current_segment = segment; + m_current_code_region = region.value(); + + updateScrollBars(); + viewport()->update(); + return true; +} + +void DebuggerCodeView::scrollToAddress(VirtualMemoryAddress address, bool center) +{ + ensureAddressVisible(address); + + VirtualMemoryAddress old_top = m_top_address; + + if (center) + { + const int visible_rows = getVisibleRowCount(); + const int target_row = getRowForAddress(address); + const int top_row = std::max(0, target_row - visible_rows / 2); + m_top_address = getAddressForRow(top_row); + } + else + { + const VirtualMemoryAddress first_visible = getFirstVisibleAddress(); + const VirtualMemoryAddress last_visible = getLastVisibleAddress(); + + if (address < first_visible || address > last_visible) + { + m_top_address = address; + } + } + + // Only recalculate arrows if scroll position actually changed + if (m_top_address != old_top) + { + calculateBranchArrows(); + } + + updateScrollBars(); + updateVisibleRange(); + viewport()->update(); +} + +std::optional DebuggerCodeView::getSelectedAddress() const +{ + if (!m_has_selection) + return std::nullopt; + return m_selected_address; +} + +void DebuggerCodeView::setSelectedAddress(VirtualMemoryAddress address) +{ + m_selected_address = address; + m_has_selection = true; + viewport()->update(); +} + +VirtualMemoryAddress DebuggerCodeView::getAddressAtPoint(const QPoint& point) const +{ + const int top_row = getRowForAddress(m_top_address); + const int clicked_row = top_row + (point.y() / m_row_height); + return getAddressForRow(clicked_row); +} + +void DebuggerCodeView::paintEvent(QPaintEvent* event) +{ + QPainter painter(viewport()); + painter.setFont(font()); + + const QRect visible_rect = event->rect(); + + // Calculate which rows are visible based on scroll position + const int top_row = getRowForAddress(m_top_address); + const int first_visible_row = top_row + (visible_rect.top() / m_row_height); + const int last_visible_row = top_row + ((visible_rect.bottom() + m_row_height - 1) / m_row_height); + + painter.fillRect(visible_rect, palette().base()); + + for (int row = first_visible_row; row <= last_visible_row; ++row) + { + const VirtualMemoryAddress address = getAddressForRow(row); + + // Calculate y position relative to scroll position + const int y = (row - top_row) * m_row_height; + + if (y + m_row_height < visible_rect.top() || y > visible_rect.bottom()) + continue; + + const bool is_selected = (m_has_selection && address == m_selected_address); + const bool is_pc = (address == m_last_pc); + + drawInstruction(painter, address, y, is_selected, is_pc); + } + + drawBranchArrows(painter, visible_rect); +} + +void DebuggerCodeView::drawInstruction(QPainter& painter, VirtualMemoryAddress address, int y, bool is_selected, + bool is_pc) +{ + const bool has_breakpoint = hasBreakpointAtAddress(address); + + // Draw background + if (has_breakpoint || is_pc || is_selected) + { + const QRect row_rect(0, y, viewport()->width(), m_row_height); + + QColor bg_color; + if (has_breakpoint) + bg_color = QColor(171, 97, 107); + else if (is_pc) + bg_color = QColor(100, 100, 0); + else if (is_selected) + bg_color = palette().highlight().color(); + else + bg_color = palette().base().color(); + + painter.fillRect(row_rect, bg_color); + } + + // Set text color + QColor address_color; + QColor bytes_color; + QColor instruction_color; + QColor register_color; + QColor immediate_color; + QColor comment_color; + if (is_pc || has_breakpoint) + { + address_color = bytes_color = instruction_color = register_color = immediate_color = comment_color = Qt::white; + } + else if (is_selected) + { + address_color = bytes_color = instruction_color = register_color = immediate_color = comment_color = + palette().highlightedText().color(); + } + else + { + instruction_color = palette().text().color(); + bytes_color = bytes_color.darker(200); + address_color = instruction_color.darker(230); + register_color = QColor(0, 150, 255); // Blue for registers + immediate_color = QColor(255, 150, 0); // Orange for immediates + comment_color = QColor(150, 150, 150); // Gray for comments + } + + // Start from the left edge of the arrow column + int x = ARROW_COLUMN_WIDTH + 2; + + // Draw breakpoint/PC icon + if (is_pc) + { + const int icon_y = y + 2 + (m_row_height - m_pc_pixmap.height()) / 2; + painter.drawPixmap(x, icon_y, m_pc_pixmap); + } + else if (has_breakpoint) + { + const int icon_y = y + 2 + (m_row_height - m_breakpoint_pixmap.height()) / 2; + painter.drawPixmap(x, icon_y, m_breakpoint_pixmap); + } + x += BREAKPOINT_COLUMN_WIDTH; + + // Draw address + const QFontMetrics font_metrics = painter.fontMetrics(); + y += font_metrics.ascent() + 1; + + painter.setPen(address_color); + const QString address_text = QString::asprintf("0x%08X", address); + painter.drawText(x, y, address_text); + x += ADDRESS_COLUMN_WIDTH; + + // Draw instruction bytes + if (u32 instruction_bits; CPU::SafeReadInstruction(address, &instruction_bits)) + { + const QString bytes_text = QString::asprintf("%08X", instruction_bits); + painter.setPen(address_color); + painter.drawText(x, y, bytes_text); + x += BYTES_COLUMN_WIDTH; + + SmallString str; + CPU::DisassembleInstruction(&str, address, instruction_bits); + const QString disasm_text = QString::fromUtf8(str.c_str(), static_cast(str.length())); + painter.setPen(instruction_color); + + // Highlight registers and immediates in the disassembly + qsizetype start_pos = 0; + while (start_pos >= 0) + { + qsizetype end_pos = disasm_text.indexOf(' ', start_pos); + if (end_pos > 0) + end_pos++; // Include the space in the highlight + + const QString token = disasm_text.mid(start_pos, end_pos - start_pos); + QColor color; + if (start_pos == 0) + color = instruction_color; // Instruction mnemonic + else if (token[0].isDigit() || (token.length() > 1 && token[0] == '-' && token[1].isDigit())) + color = immediate_color; // Immediate value + else + color = register_color; // Register + + painter.setPen(color); + painter.drawText(x, y, token); + x += font_metrics.horizontalAdvance(token); + + start_pos = end_pos; + } + } + else + { + painter.setPen(instruction_color); + painter.drawText(x, y, QStringLiteral("")); + } +} + +void DebuggerCodeView::calculateBranchArrows() +{ + m_branch_arrows.clear(); + + const VirtualMemoryAddress first_visible = getFirstVisibleAddress(); + const VirtualMemoryAddress last_visible = getLastVisibleAddress(); + + // Expand search range to include arrows that might be partially visible + const VirtualMemoryAddress search_start = + (first_visible > 64 * CPU::INSTRUCTION_SIZE) ? first_visible - (64 * CPU::INSTRUCTION_SIZE) : m_code_region_start; + const VirtualMemoryAddress search_end = std::min(last_visible + (64 * CPU::INSTRUCTION_SIZE), m_code_region_end); + + for (VirtualMemoryAddress addr = search_start; addr < search_end; addr += CPU::INSTRUCTION_SIZE) + { + u32 instruction_bits; + if (!CPU::SafeReadInstruction(addr, &instruction_bits)) + continue; + + const CPU::Instruction instruction{instruction_bits}; + + if (CPU::IsDirectBranchInstruction(instruction) && !CPU::IsCallInstruction(instruction)) + { + const VirtualMemoryAddress target = CPU::GetDirectBranchTarget(instruction, addr); + + // Only include arrows where at least one end (source or target) is in the current region + if ((addr >= search_start && addr < search_end) && (target >= search_start && target < search_end)) + { + BranchArrow arrow; + arrow.source = addr; + arrow.target = target; + arrow.is_conditional = !CPU::IsUnconditionalBranchInstruction(instruction); + arrow.is_forward = (target > addr); + m_branch_arrows.push_back(arrow); + } + } + } +} + +void DebuggerCodeView::drawBranchArrows(QPainter& painter, const QRect& visible_rect) +{ + if (m_branch_arrows.empty()) + return; + + const int top_row = getRowForAddress(m_top_address); + const int arrow_left = 2; + const int arrow_right = ARROW_COLUMN_WIDTH - 2; + const int viewport_height = viewport()->height(); + + static constexpr const QColor colors[] = { + QColor(0, 150, 255), QColor(255, 0, 150), QColor(0, 255, 0), QColor(255, 255, 0), + QColor(150, 0, 255), QColor(150, 255, 255), QColor(0, 255, 0), QColor(0, 150, 255), + }; + + for (const BranchArrow& arrow : m_branch_arrows) + { + const int source_row = getRowForAddress(arrow.source); + const int target_row = getRowForAddress(arrow.target); + + // Calculate y positions relative to scroll position + const int source_y = (source_row - top_row) * m_row_height + m_row_height / 2; + const int target_y = (target_row - top_row) * m_row_height + m_row_height / 2; + + // Only draw if at least part of the arrow is visible + const int min_y = std::min(source_y, target_y); + const int max_y = std::max(source_y, target_y); + + // Skip if completely outside viewport + if (max_y < 0 || min_y > viewport_height) + continue; + + // Clamp to viewport bounds for drawing + const int clamped_source_y = std::clamp(source_y, 0, viewport_height); + const int clamped_target_y = std::clamp(target_y, 0, viewport_height); + + // Choose color based on arrow type + QColor arrow_color = colors[source_row % std::size(colors)]; + painter.setPen(QPen(arrow_color, 1)); // Extra thick for debugging + + int nest_level = 8 - (source_row % 8); // Adjust nesting level based on row + +#if 0 + for (const BranchArrow& other : m_branch_arrows) + { + if (&other == &arrow) // Skip self-comparison + break; + + const int other_source_row = getRowForAddress(other.source); + const int other_target_row = getRowForAddress(other.target); + if ((other_source_row < top_row || other_source_row >= end_row) && + (other_target_row < top_row || other_target_row >= end_row)) + { + continue; // Skip arrows outside the visible range + } + + const int min_row = std::min(source_row, target_row); + const int max_row = std::max(source_row, target_row); + const int other_min_row = std::min(other_source_row, other_target_row); + const int other_max_row = std::max(other_source_row, other_target_row); + + // Check if ranges overlap + if (!(max_row < other_min_row || min_row > other_max_row)) + { + nest_level = std::max(nest_level - 1, 0); + } + } +#endif + + const int arrow_x = arrow_left + (nest_level * 4); + + // Draw debug circles at source and target positions + painter.setBrush(arrow_color); + painter.drawEllipse(arrow_right - 1, source_y - 1, 3, 3); + + // Draw straight line arrow with right angles + if (source_y == target_y) + { + // Horizontal line for same-row branches + painter.drawLine(arrow_x, source_y, arrow_x + 15, target_y); + } + else + { + // Horizontal line from left edge to source + if (source_y >= 0 && source_y < viewport_height) + painter.drawLine(arrow_x, source_y, arrow_right, source_y); + + // Vertical line from source to target + painter.drawLine(arrow_x, clamped_source_y, arrow_x, clamped_target_y); + + // Horizontal line to target + if (target_y >= 0 && target_y < viewport_height) + painter.drawLine(arrow_x, target_y, arrow_right, target_y); + + // Draw arrowhead with simple lines + const int arrow_size = 3; + const bool pointing_down = (target_y > source_y); + + if (pointing_down) + { + painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y - arrow_size); + painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y + arrow_size); + } + else + { + painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y - arrow_size); + painter.drawLine(arrow_right, target_y, arrow_right - arrow_size, target_y + arrow_size); + } + } + } +} + +void DebuggerCodeView::mousePressEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + { + const VirtualMemoryAddress address = getAddressAtPoint(event->pos()); + setSelectedAddress(address); + } + + QAbstractScrollArea::mousePressEvent(event); +} + +void DebuggerCodeView::mouseMoveEvent(QMouseEvent* event) +{ + if (event->buttons() & Qt::LeftButton) + { + const VirtualMemoryAddress address = getAddressAtPoint(event->pos()); + setSelectedAddress(address); + } + QAbstractScrollArea::mouseMoveEvent(event); +} + +void DebuggerCodeView::mouseDoubleClickEvent(QMouseEvent* event) +{ + if (event->button() == Qt::LeftButton) + { + const VirtualMemoryAddress address = getAddressAtPoint(event->pos()); + + const int x = event->pos().x(); + + if ((x >= BREAKPOINT_COLUMN_START && x < ADDRESS_COLUMN_START) || + (x >= INSTRUCTION_COLUMN_START && x < COMMENT_COLUMN_START)) + { + emit toggleBreakpointActivated(address); + } + else if (x >= ADDRESS_COLUMN_START && x < INSTRUCTION_COLUMN_START) + { + emit addressActivated(address); + } + else + { + emit commentActivated(address); + } + } + + QAbstractScrollArea::mouseDoubleClickEvent(event); +} + +void DebuggerCodeView::contextMenuEvent(QContextMenuEvent* event) +{ + const VirtualMemoryAddress address = getAddressAtPoint(event->pos()); + emit contextMenuRequested(event->pos(), address); +} + +void DebuggerCodeView::keyPressEvent(QKeyEvent* event) +{ + if (!m_has_selection) + { + QAbstractScrollArea::keyPressEvent(event); + return; + } + + VirtualMemoryAddress new_address = m_selected_address; + + switch (event->key()) + { + case Qt::Key_Up: + new_address -= CPU::INSTRUCTION_SIZE; + break; + case Qt::Key_Down: + new_address += CPU::INSTRUCTION_SIZE; + break; + case Qt::Key_PageUp: + new_address -= CPU::INSTRUCTION_SIZE * getVisibleRowCount(); + break; + case Qt::Key_PageDown: + new_address += CPU::INSTRUCTION_SIZE * getVisibleRowCount(); + break; + default: + QAbstractScrollArea::keyPressEvent(event); + return; + } + + scrollToAddress(new_address, false); + setSelectedAddress(new_address); +} + +void DebuggerCodeView::wheelEvent(QWheelEvent* event) +{ + const int delta = event->angleDelta().y(); + const int steps = delta / 120; // Standard wheel step + + VirtualMemoryAddress old_top = m_top_address; + VirtualMemoryAddress new_top = m_top_address - (steps * CPU::INSTRUCTION_SIZE * 3); + + // Clamp to valid range + new_top = std::clamp(new_top, m_code_region_start, m_code_region_end - CPU::INSTRUCTION_SIZE); + m_top_address = new_top; + + // Only recalculate arrows if scroll position actually changed + if (m_top_address != old_top) + { + calculateBranchArrows(); + } + + updateScrollBars(); + updateVisibleRange(); + viewport()->update(); + + event->accept(); +} + +void DebuggerCodeView::resizeEvent(QResizeEvent* event) +{ + QAbstractScrollArea::resizeEvent(event); + updateScrollBars(); + updateVisibleRange(); + // Recalculate arrows on resize since visible range changes + calculateBranchArrows(); +} + +void DebuggerCodeView::updateScrollBars() +{ + const int total_rows = static_cast((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE); + const int visible_rows = getVisibleRowCount(); + const int max_value = std::max(0, total_rows - visible_rows); + + verticalScrollBar()->setRange(0, max_value); + verticalScrollBar()->setPageStep(visible_rows); + + const int current_row = getRowForAddress(m_top_address); + verticalScrollBar()->setValue(current_row); +} + +void DebuggerCodeView::updateVisibleRange() +{ + m_visible_rows = getVisibleRowCount(); +} + +int DebuggerCodeView::getVisibleRowCount() const +{ + return viewport()->height() / m_row_height; +} + +VirtualMemoryAddress DebuggerCodeView::getFirstVisibleAddress() const +{ + return m_top_address; +} + +VirtualMemoryAddress DebuggerCodeView::getLastVisibleAddress() const +{ + const int visible_rows = getVisibleRowCount(); + return m_top_address + (visible_rows * CPU::INSTRUCTION_SIZE); +} diff --git a/src/duckstation-qt/debuggercodeview.h b/src/duckstation-qt/debuggercodeview.h new file mode 100644 index 000000000..c2d0a2957 --- /dev/null +++ b/src/duckstation-qt/debuggercodeview.h @@ -0,0 +1,123 @@ +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin +// SPDX-License-Identifier: CC-BY-NC-ND-4.0 + +#pragma once + +#include "core/bus.h" +#include "core/cpu_types.h" +#include "core/types.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +class DebuggerCodeView : public QAbstractScrollArea +{ + Q_OBJECT + +public: + explicit DebuggerCodeView(QWidget* parent = nullptr); + ~DebuggerCodeView(); + + // Call when font or theme changes + void updateRowHeight(); + + void scrollToAddress(VirtualMemoryAddress address, bool center = false); + std::optional getSelectedAddress() const; + void setSelectedAddress(VirtualMemoryAddress address); + VirtualMemoryAddress getAddressAtPoint(const QPoint& point) const; + + // Code model functionality integrated + void resetCodeView(VirtualMemoryAddress start_address); + void setPC(VirtualMemoryAddress pc); + void ensureAddressVisible(VirtualMemoryAddress address); + void setBreakpointState(VirtualMemoryAddress address, bool enabled); + void clearBreakpoints(); + VirtualMemoryAddress getPC() const { return m_last_pc; } + bool hasBreakpointAtAddress(VirtualMemoryAddress address) const; + +Q_SIGNALS: + void toggleBreakpointActivated(VirtualMemoryAddress address); + void addressActivated(VirtualMemoryAddress address); + void commentActivated(VirtualMemoryAddress address); + void contextMenuRequested(const QPoint& point, VirtualMemoryAddress address); + +protected: + void paintEvent(QPaintEvent* event) override; + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseDoubleClickEvent(QMouseEvent* event) override; + void contextMenuEvent(QContextMenuEvent* event) override; + void keyPressEvent(QKeyEvent* event) override; + void wheelEvent(QWheelEvent* event) override; + void resizeEvent(QResizeEvent* event) override; + +private: + // Column positions + static constexpr int ARROW_COLUMN_WIDTH = 60; + static constexpr int BREAKPOINT_COLUMN_WIDTH = 20; + static constexpr int ADDRESS_COLUMN_WIDTH = 100; + static constexpr int BYTES_COLUMN_WIDTH = 90; + static constexpr int INSTRUCTION_COLUMN_WIDTH = 250; + + static constexpr int BREAKPOINT_COLUMN_START = ARROW_COLUMN_WIDTH; + static constexpr int ADDRESS_COLUMN_START = BREAKPOINT_COLUMN_START + BREAKPOINT_COLUMN_WIDTH; + static constexpr int BYTES_COLUMN_START = ADDRESS_COLUMN_START + ADDRESS_COLUMN_WIDTH; + static constexpr int INSTRUCTION_COLUMN_START = BYTES_COLUMN_START + BYTES_COLUMN_WIDTH; + static constexpr int COMMENT_COLUMN_START = INSTRUCTION_COLUMN_START + INSTRUCTION_COLUMN_WIDTH; + + struct BranchArrow + { + VirtualMemoryAddress source; + VirtualMemoryAddress target; + bool is_conditional; + bool is_forward; + }; + + // Address/row conversion methods + int getRowForAddress(VirtualMemoryAddress address) const; + VirtualMemoryAddress getAddressForRow(int row) const; + + // Memory region management + bool updateRegion(VirtualMemoryAddress address); + + // Drawing methods + void updateScrollBars(); + void updateVisibleRange(); + void calculateBranchArrows(); + void drawBranchArrows(QPainter& painter, const QRect& visible_rect); + void drawInstruction(QPainter& painter, VirtualMemoryAddress address, int y, bool is_selected, bool is_pc); + + int getVisibleRowCount() const; + VirtualMemoryAddress getFirstVisibleAddress() const; + VirtualMemoryAddress getLastVisibleAddress() const; + + int m_row_height = 1; + int m_char_width = 0; + + VirtualMemoryAddress m_selected_address = 0; + bool m_has_selection = false; + + std::vector m_branch_arrows; + + QPixmap m_pc_pixmap; + QPixmap m_breakpoint_pixmap; + + // Scroll state + VirtualMemoryAddress m_top_address = 0; + int m_visible_rows = 0; + + // Code region state (from DebuggerCodeModel) + Bus::MemoryRegion m_current_code_region = Bus::MemoryRegion::Count; + CPU::Segment m_current_segment = CPU::Segment::KUSEG; + VirtualMemoryAddress m_code_region_start = 0; + VirtualMemoryAddress m_code_region_end = 0; + VirtualMemoryAddress m_last_pc = 0; + std::vector m_breakpoints; +}; diff --git a/src/duckstation-qt/debuggermodels.cpp b/src/duckstation-qt/debuggermodels.cpp index a0717a649..eede77360 100644 --- a/src/duckstation-qt/debuggermodels.cpp +++ b/src/duckstation-qt/debuggermodels.cpp @@ -18,285 +18,9 @@ #include "moc_debuggermodels.cpp" -static constexpr int NUM_COLUMNS = 5; static constexpr int STACK_RANGE = 128; static constexpr u32 STACK_VALUE_SIZE = sizeof(u32); -DebuggerCodeModel::DebuggerCodeModel(QObject* parent /*= nullptr*/) : QAbstractTableModel(parent) -{ - resetCodeView(0); - m_pc_pixmap = QIcon(QStringLiteral(":/icons/debug-pc.png")).pixmap(12); - m_breakpoint_pixmap = QIcon(QStringLiteral(":/icons/media-record.png")).pixmap(12); -} - -DebuggerCodeModel::~DebuggerCodeModel() -{ -} - -int DebuggerCodeModel::rowCount(const QModelIndex& parent /*= QModelIndex()*/) const -{ - return static_cast((m_code_region_end - m_code_region_start) / CPU::INSTRUCTION_SIZE); -} - -int DebuggerCodeModel::columnCount(const QModelIndex& parent /*= QModelIndex()*/) const -{ - return NUM_COLUMNS; -} - -int DebuggerCodeModel::getRowForAddress(VirtualMemoryAddress address) const -{ - return static_cast((address - m_code_region_start) / CPU::INSTRUCTION_SIZE); -} - -VirtualMemoryAddress DebuggerCodeModel::getAddressForRow(int row) const -{ - return m_code_region_start + (static_cast(row) * CPU::INSTRUCTION_SIZE); -} - -VirtualMemoryAddress DebuggerCodeModel::getAddressForIndex(QModelIndex index) const -{ - return getAddressForRow(index.row()); -} - -int DebuggerCodeModel::getRowForPC() const -{ - return getRowForAddress(m_last_pc); -} - -QVariant DebuggerCodeModel::data(const QModelIndex& index, int role /*= Qt::DisplayRole*/) const -{ - if (index.column() < 0 || index.column() >= NUM_COLUMNS) - return QVariant(); - - if (role == Qt::DisplayRole) - { - const VirtualMemoryAddress address = getAddressForRow(index.row()); - switch (index.column()) - { - case 0: - // breakpoint - return QVariant(); - - case 1: - { - // Address - return QVariant(QString::asprintf("0x%08X", address)); - } - - case 2: - { - // Bytes - u32 instruction_bits; - if (!CPU::SafeReadInstruction(address, &instruction_bits)) - return tr(""); - - return QString::asprintf("%08X", instruction_bits); - } - - case 3: - { - // Instruction - u32 instruction_bits; - if (!CPU::SafeReadInstruction(address, &instruction_bits)) - return tr(""); - - SmallString str; - CPU::DisassembleInstruction(&str, address, instruction_bits); - return QString::fromUtf8(str.c_str(), static_cast(str.length())); - } - - case 4: - { - // Comment - if (address != m_last_pc) - return QVariant(); - - u32 instruction_bits; - if (!CPU::SafeReadInstruction(address, &instruction_bits)) - return tr(""); - - TinyString str; - CPU::DisassembleInstructionComment(&str, address, instruction_bits); - return QString::fromUtf8(str.c_str(), static_cast(str.length())); - } - - default: - return QVariant(); - } - } - else if (role == Qt::DecorationRole) - { - if (index.column() == 0) - { - // breakpoint - const VirtualMemoryAddress address = getAddressForRow(index.row()); - if (m_last_pc == address) - return m_pc_pixmap; - else if (hasBreakpointAtAddress(address)) - return m_breakpoint_pixmap; - } - - return QVariant(); - } - else if (role == Qt::BackgroundRole) - { - const VirtualMemoryAddress address = getAddressForRow(index.row()); - - // breakpoint - if (hasBreakpointAtAddress(address)) - return QVariant(QColor(171, 97, 107)); - - // if (address == m_last_pc) - // return QApplication::palette().toolTipBase(); - if (address == m_last_pc) - return QColor(100, 100, 0); - else - return QVariant(); - } - else if (role == Qt::ForegroundRole) - { - const VirtualMemoryAddress address = getAddressForRow(index.row()); - - // if (address == m_last_pc) - // return QApplication::palette().toolTipText(); - if (address == m_last_pc || hasBreakpointAtAddress(address)) - return QColor(Qt::white); - else - return QVariant(); - } - else - { - return QVariant(); - } -} - -QVariant DebuggerCodeModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const -{ - if (orientation != Qt::Horizontal) - return QVariant(); - - if (role != Qt::DisplayRole) - return QVariant(); - - switch (section) - { - case 1: - return tr("Address"); - case 2: - return tr("Bytes"); - case 3: - return tr("Instruction"); - case 4: - return tr("Comment"); - case 0: - default: - return QVariant(); - } -} - -bool DebuggerCodeModel::updateRegion(VirtualMemoryAddress address) -{ - CPU::Segment segment = CPU::GetSegmentForAddress(address); - std::optional region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address)); - if (!region.has_value() || (address >= m_code_region_start && address < m_code_region_end)) - return false; - - static constexpr unsigned NUM_INSTRUCTIONS_BEFORE = 4096; - static constexpr unsigned NUM_INSTRUCTIONS_AFTER = 4096; - static constexpr unsigned NUM_BYTES_BEFORE = NUM_INSTRUCTIONS_BEFORE * sizeof(CPU::Instruction); - static constexpr unsigned NUM_BYTES_AFTER = NUM_INSTRUCTIONS_AFTER * sizeof(CPU::Instruction); - - const VirtualMemoryAddress start_address = - CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionStart(region.value()), segment); - const VirtualMemoryAddress end_address = - CPU::PhysicalAddressToVirtual(Bus::GetMemoryRegionEnd(region.value()), segment); - - beginResetModel(); - m_code_region_start = ((address - start_address) < NUM_BYTES_BEFORE) ? start_address : (address - NUM_BYTES_BEFORE); - m_code_region_end = ((end_address - address) < NUM_BYTES_AFTER) ? end_address : (address + NUM_BYTES_AFTER); - m_current_segment = segment; - m_current_code_region = region.value(); - endResetModel(); - return true; -} - -bool DebuggerCodeModel::emitDataChangedForAddress(VirtualMemoryAddress address) -{ - CPU::Segment segment = CPU::GetSegmentForAddress(address); - std::optional region = Bus::GetMemoryRegionForAddress(CPU::VirtualAddressToPhysical(address)); - if (!region.has_value() || segment != m_current_segment || region != m_current_code_region) - return false; - - const int row = getRowForAddress(address); - emit dataChanged(index(row, 0), index(row, NUM_COLUMNS - 1)); - return true; -} - -bool DebuggerCodeModel::hasBreakpointAtAddress(VirtualMemoryAddress address) const -{ - return std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end(); -} - -void DebuggerCodeModel::resetCodeView(VirtualMemoryAddress start_address) -{ - updateRegion(start_address); -} - -void DebuggerCodeModel::setPC(VirtualMemoryAddress pc) -{ - const VirtualMemoryAddress prev_pc = m_last_pc; - - m_last_pc = pc; - if (!updateRegion(pc)) - { - emitDataChangedForAddress(prev_pc); - emitDataChangedForAddress(pc); - } -} - -void DebuggerCodeModel::ensureAddressVisible(VirtualMemoryAddress address) -{ - updateRegion(address); -} - -void DebuggerCodeModel::setBreakpointList(std::vector bps) -{ - clearBreakpoints(); - - m_breakpoints = std::move(bps); - for (VirtualMemoryAddress bp : m_breakpoints) - emitDataChangedForAddress(bp); -} - -void DebuggerCodeModel::clearBreakpoints() -{ - std::vector old_bps(std::move(m_breakpoints)); - - for (VirtualMemoryAddress old_bp : old_bps) - emitDataChangedForAddress(old_bp); -} - -void DebuggerCodeModel::setBreakpointState(VirtualMemoryAddress address, bool enabled) -{ - if (enabled) - { - if (std::find(m_breakpoints.begin(), m_breakpoints.end(), address) != m_breakpoints.end()) - return; - - m_breakpoints.push_back(address); - emitDataChangedForAddress(address); - } - else - { - auto it = std::find(m_breakpoints.begin(), m_breakpoints.end(), address); - if (it == m_breakpoints.end()) - return; - - m_breakpoints.erase(it); - emitDataChangedForAddress(address); - } -} - DebuggerRegistersModel::DebuggerRegistersModel(QObject* parent /*= nullptr*/) : QAbstractListModel(parent) { } diff --git a/src/duckstation-qt/debuggermodels.h b/src/duckstation-qt/debuggermodels.h index 327a7d52a..5fed80d6c 100644 --- a/src/duckstation-qt/debuggermodels.h +++ b/src/duckstation-qt/debuggermodels.h @@ -15,47 +15,6 @@ #include #include -class DebuggerCodeModel final : public QAbstractTableModel -{ - Q_OBJECT - -public: - explicit DebuggerCodeModel(QObject* parent = nullptr); - ~DebuggerCodeModel() override; - - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - - // Returns the row for this instruction pointer - void resetCodeView(VirtualMemoryAddress start_address); - int getRowForAddress(VirtualMemoryAddress address) const; - int getRowForPC() const; - VirtualMemoryAddress getAddressForRow(int row) const; - VirtualMemoryAddress getAddressForIndex(QModelIndex index) const; - void setPC(VirtualMemoryAddress pc); - void ensureAddressVisible(VirtualMemoryAddress address); - void setBreakpointList(std::vector bps); - void setBreakpointState(VirtualMemoryAddress address, bool enabled); - void clearBreakpoints(); - -private: - bool updateRegion(VirtualMemoryAddress address); - bool emitDataChangedForAddress(VirtualMemoryAddress address); - bool hasBreakpointAtAddress(VirtualMemoryAddress address) const; - - Bus::MemoryRegion m_current_code_region = Bus::MemoryRegion::Count; - CPU::Segment m_current_segment = CPU::Segment::KUSEG; - VirtualMemoryAddress m_code_region_start = 0; - VirtualMemoryAddress m_code_region_end = 0; - VirtualMemoryAddress m_last_pc = 0; - std::vector m_breakpoints; - - QPixmap m_pc_pixmap; - QPixmap m_breakpoint_pixmap; -}; - class DebuggerRegistersModel final : public QAbstractListModel { Q_OBJECT diff --git a/src/duckstation-qt/debuggerwindow.cpp b/src/duckstation-qt/debuggerwindow.cpp index 246fbeeb8..aeddd71c3 100644 --- a/src/duckstation-qt/debuggerwindow.cpp +++ b/src/duckstation-qt/debuggerwindow.cpp @@ -83,7 +83,7 @@ void DebuggerWindow::refreshAll() m_stack_model->invalidateView(); m_ui.memoryView->forceRefresh(); - m_code_model->setPC(CPU::g_state.pc); + m_ui.codeView->setPC(CPU::g_state.pc); scrollToPC(false); } @@ -94,25 +94,8 @@ void DebuggerWindow::scrollToPC(bool center) void DebuggerWindow::scrollToCodeAddress(VirtualMemoryAddress address, bool center) { - m_code_model->ensureAddressVisible(address); - - const int row = m_code_model->getRowForAddress(address); - if (row >= 0) - { - qApp->processEvents(QEventLoop::ExcludeUserInputEvents); - - const QModelIndex index = m_code_model->index(row, 0); - const QRect rect = m_ui.codeView->visualRect(index); - if (rect.left() < 0 || rect.top() < 0 || rect.right() > m_ui.codeView->viewport()->width() || - rect.bottom() > m_ui.codeView->viewport()->height()) - { - center = true; - } - - m_ui.codeView->scrollTo(index, center ? QAbstractItemView::PositionAtCenter : QAbstractItemView::EnsureVisible); - m_ui.codeView->selectionModel()->setCurrentIndex(index, - QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); - } + m_ui.codeView->scrollToAddress(address, center); + m_ui.codeView->setSelectedAddress(address); } void DebuggerWindow::onPauseActionToggled(bool paused) @@ -128,7 +111,7 @@ void DebuggerWindow::onPauseActionToggled(bool paused) void DebuggerWindow::onRunToCursorTriggered() { - std::optional addr = getSelectedCodeAddress(); + std::optional addr = m_ui.codeView->getSelectedAddress(); if (!addr.has_value()) { QMessageBox::critical(this, windowTitle(), tr("No address selected.")); @@ -180,11 +163,6 @@ void DebuggerWindow::onTraceTriggered() } } -void DebuggerWindow::onFollowAddressTriggered() -{ - // -} - void DebuggerWindow::onAddBreakpointTriggered() { DebuggerAddBreakpointDialog dlg(this); @@ -196,7 +174,7 @@ void DebuggerWindow::onAddBreakpointTriggered() void DebuggerWindow::onToggleBreakpointTriggered() { - std::optional address = getSelectedCodeAddress(); + std::optional address = m_ui.codeView->getSelectedAddress(); if (!address.has_value()) return; @@ -281,37 +259,26 @@ void DebuggerWindow::onStepOutActionTriggered() g_emu_thread->setSystemPaused(false); } -void DebuggerWindow::onCodeViewItemActivated(QModelIndex index) +void DebuggerWindow::onCodeViewAddressActivated(VirtualMemoryAddress address) { - if (!index.isValid()) - return; + scrollToMemoryAddress(address); +} - const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index); - switch (index.column()) - { - case 0: // breakpoint - case 3: // disassembly - toggleBreakpoint(address); - break; - - case 1: // address - case 2: // bytes - scrollToMemoryAddress(address); - break; - - case 4: // comment - tryFollowLoadStore(address); - break; - } +void DebuggerWindow::onCodeViewToggleBreakpointActivated(VirtualMemoryAddress address) +{ + toggleBreakpoint(address); } -void DebuggerWindow::onCodeViewContextMenuRequested(const QPoint& pt) +void DebuggerWindow::onCodeViewCommentActivated(VirtualMemoryAddress address) { - const QModelIndex index = m_ui.codeView->indexAt(pt); - if (!index.isValid()) - return; + if (!tryFollowLoadStore(address)) + toggleBreakpoint(address); +} - const VirtualMemoryAddress address = m_code_model->getAddressForIndex(index); +void DebuggerWindow::onCodeViewContextMenuRequested(const QPoint& pt) +{ + const VirtualMemoryAddress address = m_ui.codeView->getAddressAtPoint(pt); + m_ui.codeView->setSelectedAddress(address); QMenu menu; menu.addAction(QStringLiteral("0x%1").arg(static_cast(address), 8, 16, QChar('0')))->setEnabled(false); @@ -484,6 +451,7 @@ void DebuggerWindow::setupAdditionalUi() #endif #endif m_ui.codeView->setFont(fixedFont); + m_ui.codeView->updateRowHeight(); m_ui.registerView->setFont(fixedFont); m_ui.memoryView->setFont(fixedFont); m_ui.stackView->setFont(fixedFont); @@ -518,8 +486,11 @@ void DebuggerWindow::connectSignals() connect(m_ui.actionToggleBreakpoint, &QAction::triggered, this, &DebuggerWindow::onToggleBreakpointTriggered); connect(m_ui.actionClearBreakpoints, &QAction::triggered, this, &DebuggerWindow::onClearBreakpointsTriggered); connect(m_ui.actionClose, &QAction::triggered, this, &DebuggerWindow::close); - connect(m_ui.codeView, &QTreeView::activated, this, &DebuggerWindow::onCodeViewItemActivated); - connect(m_ui.codeView, &QTreeView::customContextMenuRequested, this, &DebuggerWindow::onCodeViewContextMenuRequested); + connect(m_ui.codeView, &DebuggerCodeView::addressActivated, this, &DebuggerWindow::onCodeViewAddressActivated); + connect(m_ui.codeView, &DebuggerCodeView::toggleBreakpointActivated, this, + &DebuggerWindow::onCodeViewToggleBreakpointActivated); + connect(m_ui.codeView, &DebuggerCodeView::commentActivated, this, &DebuggerWindow::onCodeViewCommentActivated); + connect(m_ui.codeView, &QWidget::customContextMenuRequested, this, &DebuggerWindow::onCodeViewContextMenuRequested); connect(m_ui.breakpointsWidget, &QTreeWidget::customContextMenuRequested, this, &DebuggerWindow::onBreakpointListContextMenuRequested); connect(m_ui.breakpointsWidget, &QTreeWidget::itemChanged, this, &DebuggerWindow::onBreakpointListItemChanged); @@ -545,22 +516,12 @@ void DebuggerWindow::disconnectSignals() void DebuggerWindow::createModels() { - m_code_model = std::make_unique(); - m_ui.codeView->setModel(m_code_model.get()); - - // set default column width in code view - m_ui.codeView->setColumnWidth(0, 40); - m_ui.codeView->setColumnWidth(1, 100); - m_ui.codeView->setColumnWidth(2, 90); - m_ui.codeView->setColumnWidth(3, 250); - m_ui.codeView->setColumnWidth(4, m_ui.codeView->width() - (40 + 100 + 90 + 250)); - - m_registers_model = std::make_unique(); - m_ui.registerView->setModel(m_registers_model.get()); + m_registers_model = new DebuggerRegistersModel(this); + m_ui.registerView->setModel(m_registers_model); // m_ui->registerView->resizeRowsToContents(); - m_stack_model = std::make_unique(); - m_ui.stackView->setModel(m_stack_model.get()); + m_stack_model = new DebuggerStackModel(this); + m_ui.stackView->setModel(m_stack_model); m_ui.breakpointsWidget->setColumnWidth(0, 50); m_ui.breakpointsWidget->setColumnWidth(1, 80); @@ -670,7 +631,7 @@ void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address) } Host::RunOnUIThread([this, address, new_bp_state, bps = CPU::CopyBreakpointList()]() { - m_code_model->setBreakpointState(address, new_bp_state); + m_ui.codeView->setBreakpointState(address, new_bp_state); refreshBreakpointList(bps); }); }); @@ -678,20 +639,10 @@ void DebuggerWindow::toggleBreakpoint(VirtualMemoryAddress address) void DebuggerWindow::clearBreakpoints() { - m_code_model->clearBreakpoints(); + m_ui.codeView->clearBreakpoints(); Host::RunOnCPUThread(&CPU::ClearBreakpoints); } -std::optional DebuggerWindow::getSelectedCodeAddress() -{ - QItemSelectionModel* sel_model = m_ui.codeView->selectionModel(); - const QModelIndexList indices(sel_model->selectedIndexes()); - if (indices.empty()) - return std::nullopt; - - return m_code_model->getAddressForIndex(indices[0]); -} - bool DebuggerWindow::tryFollowLoadStore(VirtualMemoryAddress address) { CPU::Instruction inst; @@ -760,7 +711,7 @@ void DebuggerWindow::addBreakpoint(CPU::BreakpointType type, u32 address) } if (type == CPU::BreakpointType::Execute) - m_code_model->setBreakpointState(address, true); + m_ui.codeView->setBreakpointState(address, true); refreshBreakpointList(bps); }); @@ -779,7 +730,7 @@ void DebuggerWindow::removeBreakpoint(CPU::BreakpointType type, u32 address) } if (type == CPU::BreakpointType::Execute) - m_code_model->setBreakpointState(address, false); + m_ui.codeView->setBreakpointState(address, false); refreshBreakpointList(bps); }); diff --git a/src/duckstation-qt/debuggerwindow.h b/src/duckstation-qt/debuggerwindow.h index c1cc3453a..c44dcf309 100644 --- a/src/duckstation-qt/debuggerwindow.h +++ b/src/duckstation-qt/debuggerwindow.h @@ -10,14 +10,12 @@ #include #include -#include #include namespace Bus { enum class MemoryRegion; } -class DebuggerCodeModel; class DebuggerRegistersModel; class DebuggerStackModel; @@ -50,7 +48,6 @@ private Q_SLOTS: void onGoToPCTriggered(); void onGoToAddressTriggered(); void onDumpAddressTriggered(); - void onFollowAddressTriggered(); void onTraceTriggered(); void onAddBreakpointTriggered(); void onToggleBreakpointTriggered(); @@ -60,7 +57,9 @@ private Q_SLOTS: void onStepIntoActionTriggered(); void onStepOverActionTriggered(); void onStepOutActionTriggered(); - void onCodeViewItemActivated(QModelIndex index); + void onCodeViewAddressActivated(VirtualMemoryAddress address); + void onCodeViewToggleBreakpointActivated(VirtualMemoryAddress address); + void onCodeViewCommentActivated(VirtualMemoryAddress address); void onCodeViewContextMenuRequested(const QPoint& pt); void onMemorySearchTriggered(); void onMemorySearchStringChanged(const QString&); @@ -75,7 +74,6 @@ private: void setMemoryViewRegion(Bus::MemoryRegion region); void toggleBreakpoint(VirtualMemoryAddress address); void clearBreakpoints(); - std::optional getSelectedCodeAddress(); bool tryFollowLoadStore(VirtualMemoryAddress address); void scrollToPC(bool center); void scrollToCodeAddress(VirtualMemoryAddress address, bool center); @@ -87,9 +85,8 @@ private: Ui::DebuggerWindow m_ui; - std::unique_ptr m_code_model; - std::unique_ptr m_registers_model; - std::unique_ptr m_stack_model; + DebuggerRegistersModel* m_registers_model; + DebuggerStackModel* m_stack_model; QTimer m_refresh_timer; diff --git a/src/duckstation-qt/debuggerwindow.ui b/src/duckstation-qt/debuggerwindow.ui index 5ecfc10d7..991fcd746 100644 --- a/src/duckstation-qt/debuggerwindow.ui +++ b/src/duckstation-qt/debuggerwindow.ui @@ -94,7 +94,7 @@ 4 - + 0 @@ -481,6 +481,12 @@
duckstation-qt/memoryviewwidget.h
1 + + DebuggerCodeView + QAbstractScrollArea +
duckstation-qt/debuggercodeview.h
+ 1 +
diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 83f04c2df..d4e4198f6 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -15,6 +15,7 @@ + @@ -84,6 +85,7 @@ + diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index dc7acb673..4fed6d7fb 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -48,6 +48,7 @@ + @@ -107,6 +108,7 @@ +