From a5537da4dfcc98e7b691fe95b74d85ebaaab9b63 Mon Sep 17 00:00:00 2001 From: chaoticgd <43898262+chaoticgd@users.noreply.github.com> Date: Mon, 18 Aug 2025 09:40:55 +0100 Subject: [PATCH] Debugger: Add option to show leading zeroes in the symbol trees --- .../SymbolTree/SymbolTreeDelegates.cpp | 45 ++++----- .../Debugger/SymbolTree/SymbolTreeDelegates.h | 14 +-- .../Debugger/SymbolTree/SymbolTreeModel.cpp | 2 +- .../Debugger/SymbolTree/SymbolTreeModel.h | 7 +- .../Debugger/SymbolTree/SymbolTreeNode.cpp | 99 ++++++++++++++++--- pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h | 38 ++++--- .../Debugger/SymbolTree/SymbolTreeViews.cpp | 26 +++-- .../Debugger/SymbolTree/SymbolTreeViews.h | 4 +- 8 files changed, 163 insertions(+), 72 deletions(-) diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp index 7b2df96ec5..8db16bd161 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.cpp @@ -3,13 +3,14 @@ #include "SymbolTreeDelegates.h" +#include "Debugger/SymbolTree/SymbolTreeModel.h" +#include "Debugger/SymbolTree/TypeString.h" + #include #include #include #include #include -#include "Debugger/SymbolTree/SymbolTreeModel.h" -#include "Debugger/SymbolTree/TypeString.h" SymbolTreeValueDelegate::SymbolTreeValueDelegate( DebugInterface& cpu, @@ -28,6 +29,8 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti if (!tree_model) return nullptr; + const SymbolTreeDisplayOptions& display_options = tree_model->displayOptions(); + SymbolTreeNode* node = tree_model->nodeFromIndex(index); if (!node || !node->type.valid()) return nullptr; @@ -46,9 +49,9 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti { case ccc::ast::BUILTIN: { - const ccc::ast::BuiltIn& builtIn = type.as(); + const ccc::ast::BuiltIn& builtin = type.as(); - switch (builtIn.bclass) + switch (builtin.bclass) { case ccc::ast::BuiltInClass::UNSIGNED_8: case ccc::ast::BuiltInClass::UNQUALIFIED_8: @@ -56,7 +59,8 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti case ccc::ast::BuiltInClass::UNSIGNED_32: case ccc::ast::BuiltInClass::UNSIGNED_64: { - SymbolTreeIntegerLineEdit* editor = new SymbolTreeIntegerLineEdit(m_integer_base, parent); + SymbolTreeIntegerLineEdit* editor = new SymbolTreeIntegerLineEdit( + display_options, ccc::ast::builtin_class_size(builtin.bclass) * 8, parent); editor->setUnsignedValue(value.toULongLong()); result = editor; @@ -67,7 +71,8 @@ QWidget* SymbolTreeValueDelegate::createEditor(QWidget* parent, const QStyleOpti case ccc::ast::BuiltInClass::SIGNED_32: case ccc::ast::BuiltInClass::SIGNED_64: { - SymbolTreeIntegerLineEdit* editor = new SymbolTreeIntegerLineEdit(m_integer_base, parent); + SymbolTreeIntegerLineEdit* editor = new SymbolTreeIntegerLineEdit( + display_options, ccc::ast::builtin_class_size(builtin.bclass) * 8, parent); editor->setSignedValue(value.toLongLong()); result = editor; @@ -168,9 +173,9 @@ void SymbolTreeValueDelegate::setModelData(QWidget* editor, QAbstractItemModel* { case ccc::ast::BUILTIN: { - const ccc::ast::BuiltIn& builtIn = type.as(); + const ccc::ast::BuiltIn& builtin = type.as(); - switch (builtIn.bclass) + switch (builtin.bclass) { case ccc::ast::BuiltInClass::UNSIGNED_8: case ccc::ast::BuiltInClass::UNQUALIFIED_8: @@ -482,38 +487,30 @@ void SymbolTreeTypeDelegate::setModelData(QWidget* editor, QAbstractItemModel* m // ***************************************************************************** -SymbolTreeIntegerLineEdit::SymbolTreeIntegerLineEdit(int base, QWidget* parent) +SymbolTreeIntegerLineEdit::SymbolTreeIntegerLineEdit( + SymbolTreeDisplayOptions display_options, s32 size_bits, QWidget* parent) : QLineEdit(parent) - , m_base(base) + , m_display_options(display_options) + , m_size_bits(size_bits) { } std::optional SymbolTreeIntegerLineEdit::unsignedValue() { - bool ok; - u64 value = text().toULongLong(&ok, m_base); - if (!ok) - return std::nullopt; - - return value; + return m_display_options.stringToUnsignedInteger(text()); } void SymbolTreeIntegerLineEdit::setUnsignedValue(u64 value) { - setText(QString::number(value, m_base)); + setText(m_display_options.unsignedIntegerToString(value, m_size_bits)); } std::optional SymbolTreeIntegerLineEdit::signedValue() { - bool ok; - s64 value = text().toLongLong(&ok, m_base); - if (!ok) - return std::nullopt; - - return value; + return m_display_options.stringToSignedInteger(text()); } void SymbolTreeIntegerLineEdit::setSignedValue(s64 value) { - setText(QString::number(value, m_base)); + setText(m_display_options.signedIntegerToString(value, m_size_bits)); } diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h index e723038c1d..08ca862b82 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeDelegates.h @@ -3,11 +3,13 @@ #pragma once -#include -#include +#include "Debugger/SymbolTree/SymbolTreeNode.h" #include "DebugTools/DebugInterface.h" +#include +#include + class SymbolTreeValueDelegate : public QStyledItemDelegate { Q_OBJECT @@ -21,8 +23,6 @@ public: void setEditorData(QWidget* editor, const QModelIndex& index) const override; void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; - void setIntegerBase(int base) { m_integer_base = base; } - private: // These make it so the values inputted are written back to memory // immediately when the widgets are interacted with rather than when they @@ -31,7 +31,6 @@ private: void onComboBoxIndexChanged(int index); DebugInterface& m_cpu; - int m_integer_base = 10; }; class SymbolTreeLocationDelegate : public QStyledItemDelegate @@ -75,7 +74,7 @@ class SymbolTreeIntegerLineEdit : public QLineEdit Q_OBJECT public: - SymbolTreeIntegerLineEdit(int base, QWidget* parent); + SymbolTreeIntegerLineEdit(SymbolTreeDisplayOptions display_options, s32 size_bits, QWidget* parent); std::optional unsignedValue(); void setUnsignedValue(u64 value); @@ -84,5 +83,6 @@ public: void setSignedValue(s64 value); private: - int m_base; + SymbolTreeDisplayOptions m_display_options; + s32 m_size_bits; }; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp index 285b066318..0b8092c4b4 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.cpp @@ -410,7 +410,7 @@ std::vector> SymbolTreeModel::populateChildren( ccc::NodeHandle parent_handle, DebugInterface& cpu, const ccc::SymbolDatabase& database, - const SymbolTreeNode::DisplayOptions& display_options) + const SymbolTreeDisplayOptions& display_options) { auto [physical_type, symbol] = logical_type.physical_type(database); diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h index 551211e9a7..ff6c3569a8 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeModel.h @@ -65,7 +65,8 @@ public: std::optional changeTypeTemporarily(QModelIndex index, std::string_view type_string); std::optional typeFromModelIndexToString(QModelIndex index); - void setDisplayOptions(const SymbolTreeNode::DisplayOptions& options) { m_display_options = options; } + const SymbolTreeDisplayOptions& displayOptions() const { return m_display_options; } + void setDisplayOptions(const SymbolTreeDisplayOptions& options) { m_display_options = options; } protected: static std::vector> populateChildren( @@ -75,12 +76,12 @@ protected: ccc::NodeHandle parent_handle, DebugInterface& cpu, const ccc::SymbolDatabase& database, - const SymbolTreeNode::DisplayOptions& display_options); + const SymbolTreeDisplayOptions& display_options); static bool nodeHasChildren(const ccc::ast::Node& logical_type, const ccc::SymbolDatabase& database); std::unique_ptr m_root; QString m_filter; DebugInterface& m_cpu; - SymbolTreeNode::DisplayOptions m_display_options; + SymbolTreeDisplayOptions m_display_options; }; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp index f8a7d0210b..7455ab8c56 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.cpp @@ -21,7 +21,9 @@ std::optional SymbolTreeNode::liveness() } bool SymbolTreeNode::readFromVM( - DebugInterface& cpu, const ccc::SymbolDatabase& database, const DisplayOptions& display_options) + DebugInterface& cpu, + const ccc::SymbolDatabase& database, + const SymbolTreeDisplayOptions& display_options) { QVariant new_value; @@ -48,7 +50,10 @@ bool SymbolTreeNode::readFromVM( } bool SymbolTreeNode::writeToVM( - QVariant value, DebugInterface& cpu, const ccc::SymbolDatabase& database, const DisplayOptions& display_options) + QVariant value, + DebugInterface& cpu, + const ccc::SymbolDatabase& database, + const SymbolTreeDisplayOptions& display_options) { bool data_changed = false; @@ -205,7 +210,7 @@ bool SymbolTreeNode::writeValueFromVariant(QVariant value, const ccc::ast::Node& } bool SymbolTreeNode::updateDisplayString( - DebugInterface& cpu, const ccc::SymbolDatabase& database, const DisplayOptions& display) + DebugInterface& cpu, const ccc::SymbolDatabase& database, const SymbolTreeDisplayOptions& display) { QString result; @@ -240,7 +245,7 @@ QString SymbolTreeNode::generateDisplayString( const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database, - const DisplayOptions& display_options, + const SymbolTreeDisplayOptions& display_options, s32 depth) const { s32 max_elements_to_display = 0; @@ -300,28 +305,28 @@ QString SymbolTreeNode::generateDisplayString( switch (builtIn.bclass) { case ccc::ast::BuiltInClass::UNSIGNED_8: - result = QString::number(location.read8(cpu), display_options.integer_base); + result = display_options.unsignedIntegerToString(location.read8(cpu), 8); break; case ccc::ast::BuiltInClass::SIGNED_8: - result = QString::number((s8)location.read8(cpu), display_options.integer_base); + result = display_options.signedIntegerToString(static_cast(location.read8(cpu)), 8); break; case ccc::ast::BuiltInClass::UNQUALIFIED_8: - result = QString::number(location.read8(cpu), display_options.integer_base); + result = display_options.unsignedIntegerToString(location.read8(cpu), 8); break; case ccc::ast::BuiltInClass::BOOL_8: result = location.read8(cpu) ? "true" : "false"; break; case ccc::ast::BuiltInClass::UNSIGNED_16: - result = QString::number(location.read16(cpu), display_options.integer_base); + result = display_options.unsignedIntegerToString(location.read16(cpu), 16); break; case ccc::ast::BuiltInClass::SIGNED_16: - result = QString::number((s16)location.read16(cpu), display_options.integer_base); + result = display_options.signedIntegerToString(static_cast(location.read16(cpu)), 16); break; case ccc::ast::BuiltInClass::UNSIGNED_32: - result = QString::number(location.read32(cpu), display_options.integer_base); + result = display_options.unsignedIntegerToString(location.read32(cpu), 32); break; case ccc::ast::BuiltInClass::SIGNED_32: - result = QString::number((s32)location.read32(cpu), display_options.integer_base); + result = display_options.signedIntegerToString(static_cast(location.read32(cpu)), 32); break; case ccc::ast::BuiltInClass::FLOAT_32: { @@ -330,10 +335,10 @@ QString SymbolTreeNode::generateDisplayString( break; } case ccc::ast::BuiltInClass::UNSIGNED_64: - result = QString::number(location.read64(cpu), display_options.integer_base); + result = display_options.unsignedIntegerToString(location.read64(cpu), 64); break; case ccc::ast::BuiltInClass::SIGNED_64: - result = QString::number((s64)location.read64(cpu), display_options.integer_base); + result = display_options.signedIntegerToString(static_cast(location.read64(cpu)), 64); break; case ccc::ast::BuiltInClass::FLOAT_64: { @@ -716,3 +721,71 @@ void SymbolTreeNode::sortChildrenRecursively(bool sort_by_if_type_is_known) for (std::unique_ptr& child : m_children) child->sortChildrenRecursively(sort_by_if_type_is_known); } + +// ***************************************************************************** + +std::optional SymbolTreeDisplayOptions::stringToUnsignedInteger(QString string) const +{ + bool ok; + u64 value = string.toULongLong(&ok, integer_base); + if (!ok) + { + // Try parsing it as a signed integer too, just in case the user tried + // to use a minus sign. + value = static_cast(string.toLongLong(&ok, integer_base)); + if (!ok) + return std::nullopt; + } + + return value; +} + +QString SymbolTreeDisplayOptions::unsignedIntegerToString(u64 value, s32 size_bits) const +{ + int field_width = 0; + if (show_leading_zeroes && integer_base > 0) + field_width = static_cast(ceilf(size_bits / log2f(integer_base))); + + return QStringLiteral("%1").arg(value, field_width, integer_base, QLatin1Char('0')); +} + +std::optional SymbolTreeDisplayOptions::stringToSignedInteger(QString string) const +{ + bool ok; + s64 value = string.toLongLong(&ok, integer_base); + if (!ok) + { + // Try to parse it as an unsigned integer too to handle bases other than + // decimal (see below), and to handle the case that the user entered a + // value that was too big for a signed integer. + value = static_cast(string.toULongLong(&ok, integer_base)); + if (!ok) + return std::nullopt; + } + + return value; +} + +QString SymbolTreeDisplayOptions::signedIntegerToString(s64 value, s32 size_bits) const +{ + // For bases other than decimal, the user most likely just wants to view the + // underlying representation, so we want to print it as unsigned. + if (integer_base != 10) + { + // Truncate sign extended bits. + u64 mask = (static_cast(1) << size_bits) - 1; + return unsignedIntegerToString(static_cast(value) & mask, size_bits); + } + + int field_width = 0; + if (show_leading_zeroes && integer_base > 0) + { + field_width = static_cast(ceilf(size_bits / log2f(integer_base))); + + // An extra character is needed for the minus sign. + if (value < 0) + field_width++; + } + + return QStringLiteral("%1").arg(value, field_width, integer_base, QLatin1Char('0')); +} diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h index 4f57d0fca5..d7613823fc 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeNode.h @@ -9,9 +9,10 @@ #include "SymbolTreeLocation.h" class DebugInterface; +struct SymbolTreeDisplayOptions; // A node in a symbol tree model. -struct SymbolTreeNode +class SymbolTreeNode { public: enum Tag @@ -47,28 +48,30 @@ public: const QString& display_value() const; std::optional liveness(); - struct DisplayOptions - { - int integer_base = 10; - }; - // Read the value from the VM memory, update liveness information, and // generate a display string. Returns true if the data changed. - bool readFromVM(DebugInterface& cpu, const ccc::SymbolDatabase& database, const DisplayOptions& display_options); + bool readFromVM( + DebugInterface& cpu, + const ccc::SymbolDatabase& database, + const SymbolTreeDisplayOptions& display_options); // Write the value back to the VM memory. Returns true on success. bool writeToVM( - QVariant value, DebugInterface& cpu, const ccc::SymbolDatabase& database, const DisplayOptions& display_options); + QVariant value, + DebugInterface& cpu, + const ccc::SymbolDatabase& database, + const SymbolTreeDisplayOptions& display_options); QVariant readValueAsVariant(const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database) const; bool writeValueFromVariant(QVariant value, const ccc::ast::Node& physical_type, DebugInterface& cpu) const; - bool updateDisplayString(DebugInterface& cpu, const ccc::SymbolDatabase& database, const DisplayOptions& display); + bool updateDisplayString( + DebugInterface& cpu, const ccc::SymbolDatabase& database, const SymbolTreeDisplayOptions& display); QString generateDisplayString( const ccc::ast::Node& physical_type, DebugInterface& cpu, const ccc::SymbolDatabase& database, - const DisplayOptions& display_options, + const SymbolTreeDisplayOptions& display_options, s32 depth) const; bool updateLiveness(DebugInterface& cpu); @@ -91,7 +94,7 @@ public: void sortChildrenRecursively(bool sort_by_if_type_is_known); -protected: +private: QVariant m_value; QString m_display_value; std::optional m_liveness; @@ -101,3 +104,16 @@ protected: std::vector> m_children; bool m_children_fetched = false; }; + +// Settings that control how text in the edit column is displayed, including for +// the editor widgets. +struct SymbolTreeDisplayOptions +{ + int integer_base = 10; + bool show_leading_zeroes = false; + + std::optional stringToUnsignedInteger(QString string) const; + QString unsignedIntegerToString(u64 value, s32 size_bits) const; + std::optional stringToSignedInteger(QString string) const; + QString signedIntegerToString(s64 value, s32 size_bits) const; +}; diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.cpp b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.cpp index 97296035d0..5dd4a18aa7 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.cpp +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.cpp @@ -222,6 +222,7 @@ void SymbolTreeView::expandGroups(QModelIndex index) void SymbolTreeView::setupTree() { m_model = new SymbolTreeModel(cpu(), this); + m_model->setDisplayOptions(m_display_options); m_ui.treeView->setModel(m_model); auto location_delegate = new SymbolTreeLocationDelegate(cpu(), m_symbol_address_alignment, this); @@ -230,8 +231,8 @@ void SymbolTreeView::setupTree() auto type_delegate = new SymbolTreeTypeDelegate(cpu(), this); m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::TYPE, type_delegate); - m_value_delegate = new SymbolTreeValueDelegate(cpu(), this); - m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::VALUE, m_value_delegate); + auto value_delegate = new SymbolTreeValueDelegate(cpu(), this); + m_ui.treeView->setItemDelegateForColumn(SymbolTreeModel::VALUE, value_delegate); m_ui.treeView->setAlternatingRowColors(true); m_ui.treeView->setEditTriggers(QTreeView::AllEditTriggers); @@ -578,21 +579,26 @@ void SymbolTreeView::openContextMenu(QPoint pos) { QAction* base_action = integer_base_menu->addAction(name); base_action->setCheckable(true); - base_action->setChecked(m_integer_base == base); + base_action->setChecked(m_model->displayOptions().integer_base == base); connect(base_action, &QAction::toggled, this, [this, base](bool checked) { - m_integer_base = base; - - SymbolTreeNode::DisplayOptions display_options; - display_options.integer_base = base; - m_model->setDisplayOptions(display_options); - - m_value_delegate->setIntegerBase(base); + m_display_options.integer_base = base; + m_model->setDisplayOptions(m_display_options); updateVisibleNodes(false); }); base_actions->addAction(base_action); } + + QAction* show_leading_zeroes = menu->addAction(tr("Show Leading Zeroes")); + show_leading_zeroes->setCheckable(true); + show_leading_zeroes->setChecked(m_model->displayOptions().show_leading_zeroes); + connect(show_leading_zeroes, &QAction::toggled, this, [this](bool checked) { + m_display_options.show_leading_zeroes = checked; + m_model->setDisplayOptions(m_display_options); + + updateVisibleNodes(false); + }); } menu->popup(m_ui.treeView->viewport()->mapToGlobal(pos)); diff --git a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.h b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.h index 472cd72d84..eb215c5c60 100644 --- a/pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.h +++ b/pcsx2-qt/Debugger/SymbolTree/SymbolTreeViews.h @@ -96,8 +96,6 @@ protected: SymbolTreeModel* m_model = nullptr; - SymbolTreeValueDelegate* m_value_delegate = nullptr; - enum Flags { NO_SYMBOL_TREE_FLAGS = 0, @@ -117,7 +115,7 @@ protected: bool m_group_by_source_file = false; bool m_sort_by_if_type_is_known = false; - int m_integer_base = 10; + SymbolTreeDisplayOptions m_display_options; }; class FunctionTreeView : public SymbolTreeView