diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp index c12fbf0d3b..81178106af 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.cpp @@ -33,6 +33,8 @@ // 120; i.e., 120 units * 1/8 = 15 degrees." (http://doc.qt.io/qt-5/qwheelevent.html#angleDelta) constexpr double SCROLL_FRACTION_DEGREES = 15.; +// Number of columns that don't contain the value of a memory address. +constexpr int MISC_COLUMNS = 2; constexpr auto USER_ROLE_IS_ROW_BREAKPOINT_CELL = Qt::UserRole; constexpr auto USER_ROLE_CELL_ADDRESS = Qt::UserRole + 1; constexpr auto USER_ROLE_VALUE_TYPE = Qt::UserRole + 2; @@ -55,6 +57,9 @@ public: setShowGrid(false); setContextMenuPolicy(Qt::CustomContextMenu); setSelectionMode(SingleSelection); + setTextElideMode(Qt::TextElideMode::ElideNone); + setRowCount(30); + setColumnCount(8); connect(this, &MemoryViewTable::customContextMenuRequested, m_view, &MemoryViewWidget::OnContextMenu); @@ -64,7 +69,7 @@ public: void resizeEvent(QResizeEvent* event) override { QTableWidget::resizeEvent(event); - m_view->Update(); + m_view->CreateTable(); } void keyPressEvent(QKeyEvent* event) override @@ -175,11 +180,12 @@ MemoryViewWidget::MemoryViewWidget(QWidget* parent) : QWidget(parent) this->setLayout(layout); connect(&Settings::Instance(), &Settings::DebugFontChanged, this, &MemoryViewWidget::UpdateFont); - connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this] { Update(); }); - connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &MemoryViewWidget::Update); + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, + &MemoryViewWidget::UpdateColumns); + connect(Host::GetInstance(), &Host::UpdateDisasmDialog, this, &MemoryViewWidget::UpdateColumns); connect(&Settings::Instance(), &Settings::ThemeChanged, this, &MemoryViewWidget::Update); - // Also calls update. + // Also calls create table. UpdateFont(); } @@ -196,7 +202,8 @@ void MemoryViewWidget::UpdateFont() m_font_width = fm.width(QLatin1Char('0')); #endif m_table->setFont(Settings::Instance().GetDebugFont()); - Update(); + + CreateTable(); } constexpr int GetTypeSize(MemoryViewWidget::Type type) @@ -258,269 +265,265 @@ constexpr int GetCharacterCount(MemoryViewWidget::Type type) } } -void MemoryViewWidget::Update() +void MemoryViewWidget::CreateTable() { - const QSignalBlocker blocker(m_table); - - m_table->clearSelection(); - - u32 address = m_address; - address = Common::AlignDown(address, m_alignment); - - const int data_columns = m_bytes_per_row / GetTypeSize(m_type); - - if (m_dual_view) - m_table->setColumnCount(2 + 2 * data_columns); - else - m_table->setColumnCount(2 + data_columns); - - if (m_table->rowCount() == 0) - m_table->setRowCount(1); + m_table->clearContents(); // This sets all row heights and determines horizontal ascii spacing. + // Could be placed in UpdateFont() but doesn't apply correctly unless called more. m_table->verticalHeader()->setDefaultSectionSize(m_font_vspace - 1); m_table->verticalHeader()->setMinimumSectionSize(m_font_vspace - 1); m_table->horizontalHeader()->setMinimumSectionSize(m_font_width * 2); - m_table->setTextElideMode(Qt::TextElideMode::ElideNone); - const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space); + const QSignalBlocker blocker(m_table); + + // Set column and row parameters. + // Span is the number of unique memory values covered in one row. + const int data_span = m_bytes_per_row / GetTypeSize(m_type); + m_data_columns = m_dual_view ? data_span * 2 : data_span; + const int total_columns = MISC_COLUMNS + m_data_columns; - // Calculate (roughly) how many rows will fit in our table const int rows = std::round((m_table->height() / static_cast(m_table->rowHeight(0))) - 0.25); + m_table->setColumnCount(total_columns); m_table->setRowCount(rows); + m_table->setColumnWidth(0, m_table->rowHeight(0)); - for (int i = 0; i < rows; i++) - { - u32 row_address = address - ((m_table->rowCount() / 2) * m_bytes_per_row) + i * m_bytes_per_row; - - auto* bp_item = new QTableWidgetItem; - bp_item->setFlags(Qt::ItemIsEnabled); - bp_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, true); - bp_item->setData(USER_ROLE_CELL_ADDRESS, row_address); - bp_item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); - - m_table->setItem(i, 0, bp_item); - - auto* row_item = - new QTableWidgetItem(QStringLiteral("%1").arg(row_address, 8, 16, QLatin1Char('0'))); - - row_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - row_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); - row_item->setData(USER_ROLE_CELL_ADDRESS, row_address); - row_item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); - - m_table->setItem(i, 1, row_item); - - if (row_address == address) - row_item->setSelected(true); - - if (Core::GetState() != Core::State::Paused || !accessors->IsValidAddress(row_address)) - { - for (int c = 2; c < m_table->columnCount(); c++) - { - auto* item = new QTableWidgetItem(QStringLiteral("-")); - item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); - item->setData(USER_ROLE_CELL_ADDRESS, row_address); - item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); - - m_table->setItem(i, c, item); - } - - continue; - } - } - - int starting_column = 2; + // Get optional dual-view type + std::optional left_type = std::nullopt; if (m_dual_view) { - // Match left columns to number of right columns. - Type left_type = Type::Hex32; if (GetTypeSize(m_type) == 1) left_type = Type::Hex8; else if (GetTypeSize(m_type) == 2) left_type = Type::Hex16; else if (GetTypeSize(m_type) == 8) left_type = Type::Hex64; - - UpdateColumns(left_type, starting_column); - - const int column_count = m_bytes_per_row / GetTypeSize(left_type); - - // Update column width - for (int i = starting_column; i < starting_column + column_count - 1; i++) - m_table->setColumnWidth(i, m_font_width * GetCharacterCount(left_type)); - - // Extra spacing between dual views. - m_table->setColumnWidth(starting_column + column_count - 1, - m_font_width * (GetCharacterCount(left_type) + 2)); - - starting_column += column_count; + else + left_type = Type::Hex32; } - UpdateColumns(m_type, starting_column); + // Create cells and add data that won't be changing. + // Breakpoint buttons + auto* bp_item = new QTableWidgetItem; + bp_item->setFlags(Qt::ItemIsEnabled); + bp_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, true); + bp_item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); + + // Row Addresses + auto* row_item = new QTableWidgetItem(QStringLiteral("-")); + row_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); + row_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); + row_item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); + + // Data item + auto* item = new QTableWidgetItem(QStringLiteral("-")); + item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); + item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); + + for (int i = 0; i < rows; i++) + { + m_table->setItem(i, 0, bp_item->clone()); + m_table->setItem(i, 1, row_item->clone()); + + for (int c = 0; c < m_data_columns; c++) + { + if (left_type && c < data_span) + { + item->setData(USER_ROLE_VALUE_TYPE, static_cast(left_type.value())); + } + else + { + item->setData(USER_ROLE_VALUE_TYPE, static_cast(m_type)); + + // Left type will never be these. + auto text_alignment = Qt::AlignLeft; + if (m_type == Type::Signed32 || m_type == Type::Unsigned32 || m_type == Type::Signed16 || + m_type == Type::Unsigned16 || m_type == Type::Signed8 || m_type == Type::Unsigned8) + { + text_alignment = Qt::AlignRight; + } + item->setTextAlignment(text_alignment | Qt::AlignVCenter); + } + + m_table->setItem(i, c + MISC_COLUMNS, item->clone()); + } + } + + // Update column width + int start_fill = MISC_COLUMNS; + if (left_type) + { + const int width_left = m_font_width * GetCharacterCount(left_type.value()); + + for (int i = 0; i < data_span - 1; i++) + m_table->setColumnWidth(i + start_fill, width_left); + + // Extra spacing between dual views. + m_table->setColumnWidth(start_fill + data_span - 1, width_left + m_font_width * 2); + + start_fill += data_span; + } + + // If dual-view, updates the right-side columns only. + const int width = m_font_width * GetCharacterCount(m_type); + for (int i = start_fill; i < total_columns; i++) + m_table->setColumnWidth(i, width); + + Update(); +} + +void MemoryViewWidget::Update() +{ + // Check if table is created + if (m_table->item(1, 1) == nullptr) + return; + + const QSignalBlocker blocker(m_table); + m_table->clearSelection(); + + // Update addresses + const u32 address = Common::AlignDown(m_address, m_alignment); + u32 row_address = address - (m_table->rowCount() / 2) * m_bytes_per_row; + const int data_span = m_bytes_per_row / GetTypeSize(m_type); + + for (int i = 0; i < m_table->rowCount(); i++, row_address += m_bytes_per_row) + { + auto* bp_item = m_table->item(i, 0); + bp_item->setData(USER_ROLE_CELL_ADDRESS, row_address); + + auto* row_item = m_table->item(i, 1); + row_item->setText(QStringLiteral("%1").arg(row_address, 8, 16, QLatin1Char('0'))); + row_item->setData(USER_ROLE_CELL_ADDRESS, row_address); + + if (row_address == address) + row_item->setSelected(true); + + for (int c = 0; c < m_data_columns; c++) + { + auto* item = m_table->item(i, c + MISC_COLUMNS); + + u32 item_address; + if (m_dual_view && c >= data_span) + item_address = row_address + (c - data_span) * GetTypeSize(m_type); + else + item_address = row_address + c * GetTypeSize(m_type); + + item->setData(USER_ROLE_CELL_ADDRESS, item_address); + } + } + + UpdateColumns(); UpdateBreakpointTags(); - m_table->setColumnWidth(0, m_table->rowHeight(0)); - - for (int i = starting_column; i <= m_table->columnCount(); i++) - m_table->setColumnWidth(i, m_font_width * GetCharacterCount(m_type)); - m_table->viewport()->update(); m_table->update(); update(); } -void MemoryViewWidget::UpdateColumns(Type type, int first_column) +void MemoryViewWidget::UpdateColumns() { - if (Core::GetState() != Core::State::Paused) + // Check if table is created + if (m_table->item(1, 1) == nullptr) return; - const int data_columns = m_bytes_per_row / GetTypeSize(type); - const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space); - - auto text_alignment = Qt::AlignLeft; - if (type == Type::Signed32 || type == Type::Unsigned32 || type == Type::Signed16 || - type == Type::Unsigned16 || type == Type::Signed8 || type == Type::Unsigned8) - { - text_alignment = Qt::AlignRight; - } + const QSignalBlocker blocker(m_table); for (int i = 0; i < m_table->rowCount(); i++) { - u32 row_address = m_table->item(i, 1)->data(USER_ROLE_CELL_ADDRESS).toUInt(); - if (!accessors->IsValidAddress(row_address)) - continue; - - auto update_values = [&](auto value_to_string) { - for (int c = 0; c < data_columns; c++) - { - auto* cell_item = new QTableWidgetItem; - cell_item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); - cell_item->setTextAlignment(text_alignment); - - const u32 cell_address = row_address + c * GetTypeSize(type); - - m_table->setItem(i, first_column + c, cell_item); - - if (accessors->IsValidAddress(cell_address)) - { - cell_item->setText(value_to_string(cell_address)); - cell_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); - cell_item->setData(USER_ROLE_CELL_ADDRESS, cell_address); - cell_item->setData(USER_ROLE_VALUE_TYPE, static_cast(type)); - } - else - { - cell_item->setText(QStringLiteral("-")); - cell_item->setData(USER_ROLE_IS_ROW_BREAKPOINT_CELL, false); - cell_item->setData(USER_ROLE_CELL_ADDRESS, cell_address); - cell_item->setData(USER_ROLE_VALUE_TYPE, static_cast(Type::Null)); - } - } - }; - switch (type) + for (int c = 0; c < m_data_columns; c++) { - case Type::Hex8: - update_values([&accessors](u32 address) { - const u8 value = accessors->ReadU8(address); - return QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0')); - }); - break; - case Type::ASCII: - update_values([&accessors](u32 address) { - const char value = accessors->ReadU8(address); - return IsPrintableCharacter(value) ? QString{QChar::fromLatin1(value)} : - QString{QChar::fromLatin1('.')}; - }); - break; - case Type::Hex16: - update_values([&accessors](u32 address) { - const u16 value = accessors->ReadU16(address); - return QStringLiteral("%1").arg(value, 4, 16, QLatin1Char('0')); - }); - break; - case Type::Hex32: - update_values([&accessors](u32 address) { - const u32 value = accessors->ReadU32(address); - return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0')); - }); - break; - case Type::Hex64: - update_values([&accessors](u32 address) { - const u64 value = accessors->ReadU64(address); - return QStringLiteral("%1").arg(value, 16, 16, QLatin1Char('0')); - }); - break; - case Type::Unsigned8: - update_values( - [&accessors](u32 address) { return QString::number(accessors->ReadU8(address)); }); - break; - case Type::Unsigned16: - update_values( - [&accessors](u32 address) { return QString::number(accessors->ReadU16(address)); }); - break; - case Type::Unsigned32: - update_values( - [&accessors](u32 address) { return QString::number(accessors->ReadU32(address)); }); - break; - case Type::Signed8: - update_values([&accessors](u32 address) { - return QString::number(Common::BitCast(accessors->ReadU8(address))); - }); - break; - case Type::Signed16: - update_values([&accessors](u32 address) { - return QString::number(Common::BitCast(accessors->ReadU16(address))); - }); - break; - case Type::Signed32: - update_values([&accessors](u32 address) { - return QString::number(Common::BitCast(accessors->ReadU32(address))); - }); - break; - case Type::Float32: - update_values([&accessors](u32 address) { - QString string = QString::number(accessors->ReadF32(address), 'g', 4); - // Align to first digit. - if (!string.startsWith(QLatin1Char('-'))) - string.prepend(QLatin1Char(' ')); + auto* cell_item = m_table->item(i, c + MISC_COLUMNS); + const u32 cell_address = cell_item->data(USER_ROLE_CELL_ADDRESS).toUInt(); + const Type type = static_cast(cell_item->data(USER_ROLE_VALUE_TYPE).toInt()); - return string; - }); - break; - case Type::Double: - update_values([&accessors](u32 address) { - QString string = - QString::number(Common::BitCast(accessors->ReadU64(address)), 'g', 4); - // Align to first digit. - if (!string.startsWith(QLatin1Char('-'))) - string.prepend(QLatin1Char(' ')); - - return string; - }); - break; + cell_item->setText(ValueToString(cell_address, type)); } } } +QString MemoryViewWidget::ValueToString(u32 address, Type type) +{ + const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space); + if (!accessors->IsValidAddress(address) || Core::GetState() != Core::State::Paused) + return QStringLiteral("-"); + + switch (type) + { + case Type::Hex8: + { + const u8 value = accessors->ReadU8(address); + return QStringLiteral("%1").arg(value, 2, 16, QLatin1Char('0')); + } + case Type::ASCII: + { + const char value = accessors->ReadU8(address); + return IsPrintableCharacter(value) ? QString{QChar::fromLatin1(value)} : + QString{QChar::fromLatin1('.')}; + } + case Type::Hex16: + { + const u16 value = accessors->ReadU16(address); + return QStringLiteral("%1").arg(value, 4, 16, QLatin1Char('0')); + } + case Type::Hex32: + { + const u32 value = accessors->ReadU32(address); + return QStringLiteral("%1").arg(value, 8, 16, QLatin1Char('0')); + } + case Type::Hex64: + { + const u64 value = accessors->ReadU64(address); + return QStringLiteral("%1").arg(value, 16, 16, QLatin1Char('0')); + } + case Type::Unsigned8: + return QString::number(accessors->ReadU8(address)); + case Type::Unsigned16: + return QString::number(accessors->ReadU16(address)); + case Type::Unsigned32: + return QString::number(accessors->ReadU32(address)); + case Type::Signed8: + return QString::number(Common::BitCast(accessors->ReadU8(address))); + case Type::Signed16: + return QString::number(Common::BitCast(accessors->ReadU16(address))); + case Type::Signed32: + return QString::number(Common::BitCast(accessors->ReadU32(address))); + case Type::Float32: + { + QString string = QString::number(accessors->ReadF32(address), 'g', 4); + // Align to first digit. + if (!string.startsWith(QLatin1Char('-'))) + string.prepend(QLatin1Char(' ')); + + return string; + } + case Type::Double: + { + QString string = QString::number(Common::BitCast(accessors->ReadU64(address)), 'g', 4); + // Align to first digit. + if (!string.startsWith(QLatin1Char('-'))) + string.prepend(QLatin1Char(' ')); + + return string; + } + default: + return QStringLiteral("-"); + } +} + void MemoryViewWidget::UpdateBreakpointTags() { - if (Core::GetState() != Core::State::Paused) - return; - for (int i = 0; i < m_table->rowCount(); i++) { bool row_breakpoint = false; - for (int c = 2; c < m_table->columnCount(); c++) + for (int c = 0; c < m_data_columns; c++) { // Pull address from cell itself, helpful for dual column view. - auto cell = m_table->item(i, c); - u32 address = cell->data(USER_ROLE_CELL_ADDRESS).toUInt(); + auto cell = m_table->item(i, c + MISC_COLUMNS); + const u32 address = cell->data(USER_ROLE_CELL_ADDRESS).toUInt(); if (address == 0) { @@ -528,14 +531,16 @@ void MemoryViewWidget::UpdateBreakpointTags() continue; } - // In dual view the only sizes that dont match up on both left and right views are for - // Double, which uses two or four columns of hex32. if (m_address_space == AddressSpace::Type::Effective && PowerPC::memchecks.GetMemCheck(address, GetTypeSize(m_type)) != nullptr) { row_breakpoint = true; cell->setBackground(Qt::red); } + else + { + cell->setBackground(Qt::white); + } } if (row_breakpoint) @@ -545,6 +550,10 @@ void MemoryViewWidget::UpdateBreakpointTags() Resources::GetScaledThemeIcon("debugger_breakpoint") .pixmap(QSize(m_table->rowHeight(0) - 3, m_table->rowHeight(0) - 3))); } + else + { + m_table->item(i, 0)->setData(Qt::DecorationRole, QIcon()); + } } } @@ -724,7 +733,7 @@ void MemoryViewWidget::SetDisplay(Type type, int bytes_per_row, int alignment, b else m_alignment = alignment; - Update(); + CreateTable(); } void MemoryViewWidget::SetBPType(BPType type) @@ -815,25 +824,21 @@ void MemoryViewWidget::OnContextMenu(const QPoint& pos) if (!item_selected || item_selected->data(USER_ROLE_IS_ROW_BREAKPOINT_CELL).toBool()) return; - const bool item_has_value = - item_selected->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast(Type::Null); const u32 addr = item_selected->data(USER_ROLE_CELL_ADDRESS).toUInt(); + const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space); + const bool item_has_value = + item_selected->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast(Type::Null) && + accessors->IsValidAddress(addr); auto* menu = new QMenu(this); menu->addAction(tr("Copy Address"), this, [this, addr] { OnCopyAddress(addr); }); auto* copy_hex = menu->addAction(tr("Copy Hex"), this, [this, addr] { OnCopyHex(addr); }); + copy_hex->setEnabled(item_has_value); - const AddressSpace::Accessors* accessors = AddressSpace::GetAccessors(m_address_space); - copy_hex->setEnabled(item_has_value && Core::GetState() != Core::State::Uninitialized && - accessors->IsValidAddress(addr)); - - auto* copy_value = menu->addAction(tr("Copy Value"), this, [this, &pos] { - // Re-fetch the item in case the underlying table has refreshed since the menu was opened. - auto* item = m_table->itemAt(pos); - if (item && item->data(USER_ROLE_VALUE_TYPE).toInt() != static_cast(Type::Null)) - QApplication::clipboard()->setText(item->text()); + auto* copy_value = menu->addAction(tr("Copy Value"), this, [this, item_selected] { + QApplication::clipboard()->setText(item_selected->text()); }); copy_value->setEnabled(item_has_value); diff --git a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h index 16e3650068..ddf93d84d2 100644 --- a/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h +++ b/Source/Core/DolphinQt/Debugger/MemoryViewWidget.h @@ -49,6 +49,7 @@ public: explicit MemoryViewWidget(QWidget* parent = nullptr); + void CreateTable(); void Update(); void UpdateFont(); void ToggleBreakpoint(u32 addr, bool row); @@ -72,9 +73,10 @@ private: void OnCopyAddress(u32 addr); void OnCopyHex(u32 addr); void UpdateBreakpointTags(); - void UpdateColumns(Type type, int first_column); + void UpdateColumns(); void ScrollbarActionTriggered(int action); void ScrollbarSliderReleased(); + QString ValueToString(u32 address, Type type); MemoryViewTable* m_table; QScrollBar* m_scrollbar; @@ -82,11 +84,12 @@ private: Type m_type = Type::Hex32; BPType m_bp_type = BPType::ReadWrite; bool m_do_log = true; - u32 m_address = 0; + u32 m_address = 0x80000000; int m_font_width = 0; int m_font_vspace = 0; int m_bytes_per_row = 16; int m_alignment = 16; + int m_data_columns; bool m_dual_view = false; friend class MemoryViewTable;