Repository: PCSX2/pcsx2
Pull request #12534: Debugger: Add breakpoint description label
Author: login: recursean
Labels: name: Debugger
name: GUI/Qt
Created: 2025-04-18T00:14:19Z | Merged: 2025-04-25T22:08:09Z

### Description of Changes
In the debugger, descriptions can now be assigned to breakpoints in the Breakpoints view. A new column named Description is added to the table view of breakpoints:

![Screenshot from 2025-04-17 19-51-29](https://github.com/user-attachments/assets/0f794ed6-0a6c-468b-8f8f-8434a55f252f)

A new Description label is added to the new/edit breakpoint dialog:

![Screenshot from 2025-04-17 19-52-00](https://github.com/user-attachments/assets/212ea1ed-097e-4898-81e7-d47a9ffd9abb)

With this new addition, the format of saved breakpoints has changed. On Linux, my breakpoints are saved in a JSON file at `PCSX2/inis/debuggersettings`. These settings are tagged with a version (0.00 currently) defined in `DebuggerSettingsManager.cpp`: 
```
const QString DebuggerSettingsManager::settingsFileVersion = "0.00";
```
When I initially tried loading an old breakpoint I had saved prior to adding the Description, the parsing failed and the breakpoint failed to load. So, I updated the version to "0.01" and added code to `DebuggerSettingsManager::loadGameSettings` to "convert" breakpoints being loaded at v0.00 into breakpoints at v0.01, which just means adding in an empty Description field. Seems to work from my testing. 

I also fixed behavior in the Breakpoints view where if you added an Execute type breakpoint (either through double clicking an instruction in the Disassembly view or via the new/edit dialog), the first column in the Breakpoints view titled "X" would contain a checkbox but would be missing the "Enabled" or "Disabled" text that appears when you create a read/write memory type breakpoint. Now both execute and read/write breakpoints will be consistent in the displaying of Enabled/Disabled text. 

### Rationale behind Changes
#10696

### Suggested Testing Steps
- Ensure previously saved breakpoints load correctly
- Ensure new breakpoints with descriptions save and load correctly
- Description column should resize based on contents



Diff:
diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.cpp
index 5cee329a2d1c3..194197e00e180 100644
--- a/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.cpp
+++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.cpp
@@ -42,6 +42,7 @@ BreakpointDialog::BreakpointDialog(QWidget* parent, DebugInterface* cpu, Breakpo
 		m_ui.rdoExecute->setChecked(true);
 		m_ui.chkEnable->setChecked(bp->enabled);
 		m_ui.txtAddress->setText(QtUtils::FilledQStringFromValue(bp->addr, 16));
+		m_ui.txtDescription->setText(QString::fromStdString(bp->description));
 
 		if (bp->hasCond)
 			m_ui.txtCondition->setText(QString::fromStdString(bp->cond.expressionString));
@@ -53,6 +54,8 @@ BreakpointDialog::BreakpointDialog(QWidget* parent, DebugInterface* cpu, Breakpo
 		m_ui.txtAddress->setText(QtUtils::FilledQStringFromValue(mc->start, 16));
 		m_ui.txtSize->setText(QtUtils::FilledQStringFromValue(mc->end - mc->start, 16));
 
+		m_ui.txtDescription->setText(QString::fromStdString(mc->description));
+
 		m_ui.chkRead->setChecked(mc->memCond & MEMCHECK_READ);
 		m_ui.chkWrite->setChecked(mc->memCond & MEMCHECK_WRITE);
 		m_ui.chkChange->setChecked(mc->memCond & MEMCHECK_WRITE_ONCHANGE);
@@ -102,6 +105,7 @@ void BreakpointDialog::accept()
 		}
 
 		bp->addr = address;
+		bp->description = m_ui.txtDescription->text().toStdString();
 
 		bp->enabled = m_ui.chkEnable->isChecked();
 
@@ -138,6 +142,7 @@ void BreakpointDialog::accept()
 
 		mc->start = startAddress;
 		mc->end = startAddress + size;
+		mc->description = m_ui.txtDescription->text().toStdString();
 
 		if (!m_ui.txtCondition->text().isEmpty())
 		{
diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.ui b/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.ui
index a15212baf057b..ec751dd211d52 100644
--- a/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.ui
+++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointDialog.ui
@@ -10,7 +10,7 @@
     <x>0</x>
     <y>0</y>
     <width>375</width>
-    <height>250</height>
+    <height>300</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -22,19 +22,19 @@
   <property name="minimumSize">
    <size>
     <width>375</width>
-    <height>250</height>
+    <height>300</height>
    </size>
   </property>
   <property name="maximumSize">
    <size>
     <width>375</width>
-    <height>250</height>
+    <height>300</height>
    </size>
   </property>
   <property name="baseSize">
    <size>
     <width>375</width>
-    <height>250</height>
+    <height>300</height>
    </size>
   </property>
   <property name="windowTitle">
@@ -44,7 +44,7 @@
    <property name="geometry">
     <rect>
      <x>20</x>
-     <y>210</y>
+     <y>260</y>
      <width>341</width>
      <height>32</height>
     </rect>
@@ -102,14 +102,17 @@
     <rect>
      <x>110</x>
      <y>10</y>
-     <width>251</width>
-     <height>41</height>
+     <width>250</width>
+     <height>79</height>
     </rect>
    </property>
    <property name="title">
     <string/>
    </property>
    <layout class="QFormLayout" name="formLayout">
+    <property name="formAlignment">
+     <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+    </property>
     <item row="0" column="0">
      <widget class="QLabel" name="label">
       <property name="text">
@@ -139,6 +142,29 @@
       </property>
      </widget>
     </item>
+    <item row="1" column="0">
+     <widget class="QLabel" name="label_4">
+      <property name="text">
+       <string>Description</string>
+      </property>
+     </widget>
+    </item>
+    <item row="1" column="1">
+     <widget class="QLineEdit" name="txtDescription">
+      <property name="minimumSize">
+       <size>
+        <width>0</width>
+        <height>19</height>
+       </size>
+      </property>
+      <property name="maximumSize">
+       <size>
+        <width>16777215</width>
+        <height>19</height>
+       </size>
+      </property>
+     </widget>
+    </item>
    </layout>
   </widget>
   <widget class="QGroupBox" name="grpMemory">
@@ -148,7 +174,7 @@
    <property name="geometry">
     <rect>
      <x>110</x>
-     <y>50</y>
+     <y>100</y>
      <width>251</width>
      <height>91</height>
     </rect>
@@ -224,13 +250,13 @@
    <property name="geometry">
     <rect>
      <x>110</x>
-     <y>140</y>
+     <y>190</y>
      <width>251</width>
      <height>61</height>
     </rect>
    </property>
    <property name="title">
-    <string></string>
+    <string/>
    </property>
    <layout class="QFormLayout" name="formLayout_3">
     <item row="0" column="0">
diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp
index 7f8deb09cf90a..80ac55d7b4ab8 100644
--- a/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp
+++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.cpp
@@ -72,11 +72,13 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
 			switch (index.column())
 			{
 				case BreakpointColumns::ENABLED:
-					return "";
+					return (bp->enabled) ? tr("Enabled") : tr("Disabled");
 				case BreakpointColumns::TYPE:
 					return tr("Execute");
 				case BreakpointColumns::OFFSET:
 					return QtUtils::FilledQStringFromValue(bp->addr, 16);
+				case BreakpointColumns::DESCRIPTION:
+					return QString::fromStdString(bp->description);
 				case BreakpointColumns::SIZE_LABEL:
 					return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(bp->addr).name);
 				case BreakpointColumns::OPCODE:
@@ -105,6 +107,8 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
 				}
 				case BreakpointColumns::OFFSET:
 					return QtUtils::FilledQStringFromValue(mc->start, 16);
+				case BreakpointColumns::DESCRIPTION:
+					return QString::fromStdString(mc->description);
 				case BreakpointColumns::SIZE_LABEL:
 					return QString::number(mc->end - mc->start, 16);
 				case BreakpointColumns::OPCODE:
@@ -116,6 +120,29 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
 			}
 		}
 	}
+	else if (role == Qt::EditRole)
+	{
+		if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
+		{
+			switch (index.column())
+			{
+				case BreakpointColumns::CONDITION:
+					return bp->hasCond ? QString::fromStdString(bp->cond.expressionString) : "";
+				case BreakpointColumns::DESCRIPTION:
+					return QString::fromStdString(bp->description);
+			}
+		}
+		else if (const auto* mc = std::get_if<MemCheck>(&bp_mc))
+		{
+			switch (index.column())
+			{
+				case BreakpointColumns::CONDITION:
+					return mc->hasCond ? QString::fromStdString(mc->cond.expressionString) : "";
+				case BreakpointColumns::DESCRIPTION:
+					return QString::fromStdString(mc->description);
+			}
+		}
+	}
 	else if (role == BreakpointModel::DataRole)
 	{
 		if (const auto* bp = std::get_if<BreakPoint>(&bp_mc))
@@ -128,6 +155,8 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
 					return MEMCHECK_INVALID;
 				case BreakpointColumns::OFFSET:
 					return bp->addr;
+				case BreakpointColumns::DESCRIPTION:
+					return QString::fromStdString(bp->description);
 				case BreakpointColumns::SIZE_LABEL:
 					return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(bp->addr).name);
 				case BreakpointColumns::OPCODE:
@@ -149,6 +178,8 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
 					return mc->memCond;
 				case BreakpointColumns::OFFSET:
 					return mc->start;
+				case BreakpointColumns::DESCRIPTION:
+					return QString::fromStdString(mc->description);
 				case BreakpointColumns::SIZE_LABEL:
 					return mc->end - mc->start;
 				case BreakpointColumns::OPCODE:
@@ -172,6 +203,8 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
 					return MEMCHECK_INVALID;
 				case BreakpointColumns::OFFSET:
 					return QtUtils::FilledQStringFromValue(bp->addr, 16);
+				case BreakpointColumns::DESCRIPTION:
+					return QString::fromStdString(bp->description);
 				case BreakpointColumns::SIZE_LABEL:
 					return QString::fromStdString(m_cpu.GetSymbolGuardian().FunctionStartingAtAddress(bp->addr).name);
 				case BreakpointColumns::OPCODE:
@@ -191,6 +224,8 @@ QVariant BreakpointModel::data(const QModelIndex& index, int role) const
 					return mc->memCond;
 				case BreakpointColumns::OFFSET:
 					return QtUtils::FilledQStringFromValue(mc->start, 16);
+				case BreakpointColumns::DESCRIPTION:
+					return QString::fromStdString(mc->description);
 				case BreakpointColumns::SIZE_LABEL:
 					return mc->end - mc->start;
 				case BreakpointColumns::OPCODE:
@@ -233,6 +268,8 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, i
 			case BreakpointColumns::OFFSET:
 				//: Warning: limited space available. Abbreviate if needed.
 				return tr("OFFSET");
+			case BreakpointColumns::DESCRIPTION:
+				return "DESCRIPTION";
 			case BreakpointColumns::SIZE_LABEL:
 				//: Warning: limited space available. Abbreviate if needed.
 				return tr("SIZE / LABEL");
@@ -260,6 +297,8 @@ QVariant BreakpointModel::headerData(int section, Qt::Orientation orientation, i
 				return "TYPE";
 			case BreakpointColumns::OFFSET:
 				return "OFFSET";
+			case BreakpointColumns::DESCRIPTION:
+				return "DESCRIPTION";
 			case BreakpointColumns::SIZE_LABEL:
 				return "SIZE / LABEL";
 			case BreakpointColumns::OPCODE:
@@ -282,6 +321,7 @@ Qt::ItemFlags BreakpointModel::flags(const QModelIndex& index) const
 	switch (index.column())
 	{
 		case BreakpointColumns::CONDITION:
+		case BreakpointColumns::DESCRIPTION:
 			return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEditable;
 		case BreakpointColumns::TYPE:
 		case BreakpointColumns::OPCODE:
@@ -395,6 +435,27 @@ bool BreakpointModel::setData(const QModelIndex& index, const QVariant& value, i
 		emit dataChanged(index, index);
 		return true;
 	}
+	else if (role == Qt::EditRole && index.column() == BreakpointColumns::DESCRIPTION)
+	{
+		// Update BreakPoint description
+		if (auto* bp = std::get_if<BreakPoint>(&bp_mc))
+		{
+			const QString descValue = value.toString();
+			Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), bp, descValue] {
+				CBreakPoints::ChangeBreakPointDescription(cpu, bp->addr, descValue.toStdString());
+			});
+		}
+		// Update MemCheck description
+		else if (auto* mc = std::get_if<MemCheck>(&bp_mc))
+		{
+			const QString descValue = value.toString();
+			Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), mc, descValue] {
+				CBreakPoints::ChangeMemCheckDescription(cpu, mc->start, mc->end, descValue.toStdString());
+			});
+		}
+		emit dataChanged(index, index);
+		return true;
+	}
 
 	return false;
 }
@@ -451,7 +512,7 @@ bool BreakpointModel::insertBreakpointRows(int row, int count, std::vector<Break
 		{
 			Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), bp = *bp] {
 				CBreakPoints::AddBreakPoint(cpu, bp.addr, false, bp.enabled);
-
+				CBreakPoints::ChangeBreakPointDescription(cpu, bp.addr, bp.description);
 				if (bp.hasCond)
 				{
 					CBreakPoints::ChangeBreakPointAddCond(cpu, bp.addr, bp.cond);
@@ -462,6 +523,7 @@ bool BreakpointModel::insertBreakpointRows(int row, int count, std::vector<Break
 		{
 			Host::RunOnCPUThread([cpu = m_cpu.getCpuType(), mc = *mc] {
 				CBreakPoints::AddMemCheck(cpu, mc.start, mc.end, mc.memCond, mc.result);
+				CBreakPoints::ChangeMemCheckDescription(cpu, mc.start, mc.end, mc.description);
 				if (mc.hasCond)
 				{
 					CBreakPoints::ChangeMemCheckAddCond(cpu, mc.start, mc.end, mc.cond);
@@ -548,6 +610,12 @@ void BreakpointModel::loadBreakpointFromFieldList(QStringList fields)
 			return;
 		}
 
+		// Description
+		if (!fields[BreakpointColumns::DESCRIPTION].isEmpty())
+		{
+			bp.description = fields[BreakpointColumns::DESCRIPTION].toStdString();
+		}
+
 		insertBreakpointRows(0, 1, {bp});
 	}
 	else
@@ -608,6 +676,12 @@ void BreakpointModel::loadBreakpointFromFieldList(QStringList fields)
 		}
 		mc.result = static_cast<MemCheckResult>(result);
 
+		// Description
+		if (!fields[BreakpointColumns::DESCRIPTION].isEmpty())
+		{
+			mc.description = fields[BreakpointColumns::DESCRIPTION].toStdString();
+		}
+
 		insertBreakpointRows(0, 1, {mc});
 	}
 }
diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.h b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.h
index fd603bb858409..161625eb6e9ba 100644
--- a/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.h
+++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointModel.h
@@ -21,6 +21,7 @@ class BreakpointModel : public QAbstractTableModel
 		ENABLED = 0,
 		TYPE,
 		OFFSET,
+		DESCRIPTION,
 		SIZE_LABEL,
 		OPCODE,
 		CONDITION,
@@ -38,6 +39,7 @@ class BreakpointModel : public QAbstractTableModel
 		QHeaderView::ResizeMode::ResizeToContents,
 		QHeaderView::ResizeMode::ResizeToContents,
 		QHeaderView::ResizeMode::ResizeToContents,
+		QHeaderView::ResizeMode::ResizeToContents,
 		QHeaderView::ResizeMode::Stretch,
 		QHeaderView::ResizeMode::Stretch,
 		QHeaderView::ResizeMode::ResizeToContents,
diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointView.cpp b/pcsx2-qt/Debugger/Breakpoints/BreakpointView.cpp
index 603f399d2748c..64eec9f8b500d 100644
--- a/pcsx2-qt/Debugger/Breakpoints/BreakpointView.cpp
+++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointView.cpp
@@ -21,11 +21,7 @@ BreakpointView::BreakpointView(const DebuggerViewParameters& parameters)
 	connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &BreakpointView::onDoubleClicked);
 
 	m_ui.breakpointList->setModel(m_model);
-	for (std::size_t i = 0; auto mode : BreakpointModel::HeaderResizeModes)
-	{
-		m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(i, mode);
-		i++;
-	}
+	this->resizeColumns();
 }
 
 void BreakpointView::onDoubleClicked(const QModelIndex& index)
@@ -124,6 +120,7 @@ void BreakpointView::contextDelete()
 void BreakpointView::contextNew()
 {
 	BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), *m_model);
+	connect(bpDialog, &QDialog::accepted, this, &BreakpointView::resizeColumns);
 	bpDialog->setAttribute(Qt::WA_DeleteOnClose);
 	bpDialog->show();
 }
@@ -140,6 +137,7 @@ void BreakpointView::contextEdit()
 	auto bpObject = m_model->at(selectedRow);
 
 	BreakpointDialog* bpDialog = new BreakpointDialog(this, &cpu(), *m_model, bpObject, selectedRow);
+	connect(bpDialog, &QDialog::accepted, this, &BreakpointView::resizeColumns);
 	bpDialog->setAttribute(Qt::WA_DeleteOnClose);
 	bpDialog->show();
 }
@@ -172,3 +170,12 @@ void BreakpointView::saveBreakpointsToDebuggerSettings()
 {
 	DebuggerSettingsManager::saveGameSettings(m_model);
 }
+
+void BreakpointView::resizeColumns()
+{
+	for (std::size_t i = 0; auto mode : BreakpointModel::HeaderResizeModes)
+	{
+		m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(i, mode);
+		i++;
+	}
+}
diff --git a/pcsx2-qt/Debugger/Breakpoints/BreakpointView.h b/pcsx2-qt/Debugger/Breakpoints/BreakpointView.h
index 333fd3631519d..52dd529f501b6 100644
--- a/pcsx2-qt/Debugger/Breakpoints/BreakpointView.h
+++ b/pcsx2-qt/Debugger/Breakpoints/BreakpointView.h
@@ -32,6 +32,8 @@ class BreakpointView : public DebuggerView
 	void contextEdit();
 	void contextPasteCSV();
 
+	void resizeColumns();
+
 	void saveBreakpointsToDebuggerSettings();
 
 private:
diff --git a/pcsx2-qt/Debugger/DebuggerSettingsManager.cpp b/pcsx2-qt/Debugger/DebuggerSettingsManager.cpp
index 81a23c8858384..d83a73adca929 100644
--- a/pcsx2-qt/Debugger/DebuggerSettingsManager.cpp
+++ b/pcsx2-qt/Debugger/DebuggerSettingsManager.cpp
@@ -12,7 +12,7 @@
 #include "VMManager.h"
 
 std::mutex DebuggerSettingsManager::writeLock;
-const QString DebuggerSettingsManager::settingsFileVersion = "0.00";
+const QString DebuggerSettingsManager::settingsFileVersion = "0.01";
 
 QJsonObject DebuggerSettingsManager::loadGameSettingsJSON()
 {
@@ -62,6 +62,17 @@ void DebuggerSettingsManager::loadGameSettings(BreakpointModel* bpModel)
 		return;
 	}
 
+	// Breakpoint descriptions were added at debugger settings file version 0.01. If loading
+	// saved breakpoints from a previous version (only 0.00 existed prior), the breakpoints will be
+	// missing a description. This code will add in an empty description so that the previous
+	// version, 0.00, is compatible with 0.01.
+	bool isMissingDescription = false;
+	const QJsonValue savedVersionValue = loadGameSettingsJSON().value("Version");
+	if (!savedVersionValue.isUndefined())
+	{
+		isMissingDescription = savedVersionValue.toString().toStdString() == "0.00";
+	}
+
 	const QJsonArray breakpointsArray = breakpointsValue.toArray();
 	for (u32 row = 0; row < breakpointsArray.size(); row++)
 	{
@@ -71,7 +82,13 @@ void DebuggerSettingsManager::loadGameSettings(BreakpointModel* bpModel)
 			Console.WriteLn("Debugger Settings Manager: Failed to load invalid Breakpoint object.");
 			continue;
 		}
-		const QJsonObject rowObject = rowValue.toObject();
+		QJsonObject rowObject = rowValue.toObject();
+
+		// Add empty description for saved breakpoints from debugger settings versions prior to 0.01
+		if (isMissingDescription)
+		{
+			rowObject.insert(QString("DESCRIPTION"), QJsonValue(""));
+		}
 
 		QStringList fields;
 		u32 col = 0;
diff --git a/pcsx2/DebugTools/Breakpoints.cpp b/pcsx2/DebugTools/Breakpoints.cpp
index 8bc670e294200..db1bbd384543b 100644
--- a/pcsx2/DebugTools/Breakpoints.cpp
+++ b/pcsx2/DebugTools/Breakpoints.cpp
@@ -285,6 +285,16 @@ BreakPointCond* CBreakPoints::GetBreakPointCondition(BreakPointCpu cpu, u32 addr
 	return NULL;
 }
 
+void CBreakPoints::ChangeBreakPointDescription(BreakPointCpu cpu, u32 addr, const std::string& description)
+{
+	const size_t bp = FindBreakpoint(cpu, addr, true, false);
+	if (bp != INVALID_BREAKPOINT)
+	{
+		breakPoints_[bp].description = description;
+		Update();
+	}
+}
+
 void CBreakPoints::AddMemCheck(BreakPointCpu cpu, u32 start, u32 end, MemCheckCondition cond, MemCheckResult result)
 {
 	// This will ruin any pending memchecks.
@@ -356,6 +366,16 @@ void CBreakPoints::ChangeMemCheckAddCond(BreakPointCpu cpu, u32 start, u32 end,
 	}
 }
 
+void CBreakPoints::ChangeMemCheckDescription(BreakPointCpu cpu, u32 start, u32 end, const std::string& description)
+{
+	const size_t mc = FindMemCheck(cpu, start, end);
+	if (mc != INVALID_MEMCHECK)
+	{
+		memChecks_[mc].description = description;
+		Update(cpu);
+	}
+}
+
 void CBreakPoints::ClearAllMemChecks()
 {
 	// This will ruin any pending memchecks.
diff --git a/pcsx2/DebugTools/Breakpoints.h b/pcsx2/DebugTools/Breakpoints.h
index fd100b4cc72d6..61c532adb8c90 100644
--- a/pcsx2/DebugTools/Breakpoints.h
+++ b/pcsx2/DebugTools/Breakpoints.h
@@ -37,6 +37,8 @@ struct BreakPoint
 	BreakPointCond cond;
 	BreakPointCpu cpu;
 
+	std::string description;
+
 	bool operator==(const BreakPoint& other) const
 	{
 		return addr == other.addr;
@@ -78,6 +80,8 @@ struct MemCheck
 	MemCheckResult result;
 	BreakPointCpu cpu;
 
+	std::string description;
+
 	u32 numHits;
 
 	u32 lastPC;
@@ -119,12 +123,14 @@ class CBreakPoints
 	static void ChangeBreakPointAddCond(BreakPointCpu cpu, u32 addr, const BreakPointCond& cond);
 	static void ChangeBreakPointRemoveCond(BreakPointCpu cpu, u32 addr);
 	static BreakPointCond* GetBreakPointCondition(BreakPointCpu cpu, u32 addr);
+	static void ChangeBreakPointDescription(BreakPointCpu cpu, u32 addr, const std::string& description);
 
 	static void AddMemCheck(BreakPointCpu cpu, u32 start, u32 end, MemCheckCondition cond, MemCheckResult result);
 	static void RemoveMemCheck(BreakPointCpu cpu, u32 start, u32 end);
 	static void ChangeMemCheck(BreakPointCpu cpu, u32 start, u32 end, MemCheckCondition cond, MemCheckResult result);
 	static void ChangeMemCheckRemoveCond(BreakPointCpu cpu, u32 start, u32 end);
 	static void ChangeMemCheckAddCond(BreakPointCpu cpu, u32 start, u32 end, const BreakPointCond& cond);
+	static void ChangeMemCheckDescription(BreakPointCpu cpu, u32 start, u32 end, const std::string& description);
 	static void ClearAllMemChecks();
 
 	static void SetSkipFirst(BreakPointCpu cpu, u32 pc);


Reviews:
user: login: F0bes
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-18T14:23:20Z
user: login: F0bes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-24T23:48:13Z
user: login: F0bes
body: Thanks!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-25T22:07:44Z

Review comments:
diff_hunk: @@ -285,6 +285,16 @@ BreakPointCond* CBreakPoints::GetBreakPointCondition(BreakPointCpu cpu, u32 addr
 	return NULL;
 }
 
+void CBreakPoints::ChangeBreakPointDescription(BreakPointCpu cpu, u32 addr, std::string description)
path: pcsx2/DebugTools/Breakpoints.cpp
user: login: F0bes
body: ```suggestion
void CBreakPoints::ChangeBreakPointDescription(BreakPointCpu cpu, u32 addr, const std::string& description)
```
created_at: 2025-04-18T14:14:03Z
author_association: MEMBER
diff_hunk: @@ -356,6 +366,16 @@ void CBreakPoints::ChangeMemCheckAddCond(BreakPointCpu cpu, u32 start, u32 end,
 	}
 }
 
+void CBreakPoints::ChangeMemCheckDescription(BreakPointCpu cpu, u32 start, u32 end, std::string description)
path: pcsx2/DebugTools/Breakpoints.cpp
user: login: F0bes
body: ```suggestion
void CBreakPoints::ChangeMemCheckDescription(BreakPointCpu cpu, u32 start, u32 end, const std::string& description)
```
created_at: 2025-04-18T14:14:18Z
author_association: MEMBER
diff_hunk: @@ -119,12 +123,14 @@ class CBreakPoints
 	static void ChangeBreakPointAddCond(BreakPointCpu cpu, u32 addr, const BreakPointCond& cond);
 	static void ChangeBreakPointRemoveCond(BreakPointCpu cpu, u32 addr);
 	static BreakPointCond* GetBreakPointCondition(BreakPointCpu cpu, u32 addr);
+	static void ChangeBreakPointDescription(BreakPointCpu cpu, u32 addr, std::string description);
path: pcsx2/DebugTools/Breakpoints.h
user: login: F0bes
body: ```suggestion
	static void ChangeBreakPointDescription(BreakPointCpu cpu, u32 addr, const std::string& description);
```
created_at: 2025-04-18T14:15:19Z
author_association: MEMBER
diff_hunk: @@ -119,12 +123,14 @@ class CBreakPoints
 	static void ChangeBreakPointAddCond(BreakPointCpu cpu, u32 addr, const BreakPointCond& cond);
 	static void ChangeBreakPointRemoveCond(BreakPointCpu cpu, u32 addr);
 	static BreakPointCond* GetBreakPointCondition(BreakPointCpu cpu, u32 addr);
+	static void ChangeBreakPointDescription(BreakPointCpu cpu, u32 addr, std::string description);
 
 	static void AddMemCheck(BreakPointCpu cpu, u32 start, u32 end, MemCheckCondition cond, MemCheckResult result);
 	static void RemoveMemCheck(BreakPointCpu cpu, u32 start, u32 end);
 	static void ChangeMemCheck(BreakPointCpu cpu, u32 start, u32 end, MemCheckCondition cond, MemCheckResult result);
 	static void ChangeMemCheckRemoveCond(BreakPointCpu cpu, u32 start, u32 end);
 	static void ChangeMemCheckAddCond(BreakPointCpu cpu, u32 start, u32 end, const BreakPointCond& cond);
+	static void ChangeMemCheckDescription(BreakPointCpu cpu, u32 start, u32 end, std::string description);
path: pcsx2/DebugTools/Breakpoints.h
user: login: F0bes
body: ```suggestion
	static void ChangeMemCheckDescription(BreakPointCpu cpu, u32 start, u32 end, const std::string& description);
```
created_at: 2025-04-18T14:15:40Z
author_association: MEMBER
diff_hunk: @@ -61,6 +61,18 @@ void DebuggerSettingsManager::loadGameSettings(BreakpointModel* bpModel)
 		Console.WriteLnFmt("Debugger Settings Manager: Failed to read Breakpoints array from settings file: '{}'", path);
 		return;
 	}
+	
+	// Breakpoint descriptions were added at debugger settings file version 0.01. If loading
+	// saved breakpoints from a previous version (only 0.00 existed prior), the breakpoints will be 
+	// missing a description. This code will add in an empty description so that the previous 
+	// version, 0.00, is compatible with 0.01.
+	const QJsonValue savedVersionValue = loadGameSettingsJSON().value("Version");
+	const QString savedVersion = savedVersionValue.toString();
+	bool isMissingDescription = false;
+	if (!savedVersionValue.isUndefined())
+	{
+		isMissingDescription = savedVersion.toStdString() == "0.00";
path: pcsx2-qt/Debugger/DebuggerSettingsManager.cpp
user: login: F0bes
body: ```suggestion
		isMissingDescription = savedVersionValue.toString().toStdString() == "0.00";
```
Maybe this would be better instead of calling toString() on a possibly undefined QJsonValue on line 70?
created_at: 2025-04-18T14:22:55Z
author_association: MEMBER
diff_hunk: @@ -172,3 +170,12 @@ void BreakpointView::saveBreakpointsToDebuggerSettings()
 {
 	DebuggerSettingsManager::saveGameSettings(m_model);
 }
+
+void BreakpointView::resizeColumns()
+{
+	for (std::size_t i = 0; auto mode : BreakpointModel::HeaderResizeModes)
+	{
+		m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(i, mode);
+		i++;
+	}
+}
path: pcsx2-qt/Debugger/Breakpoints/BreakpointView.cpp
user: login: F0bes
body: Missing newline at the end of the file.
created_at: 2025-04-24T23:47:50Z
author_association: MEMBER

PR comments:
user: login: Risae
created_at: 2025-04-18T05:54:55Z
body: Thank you very much for taking the time to add this valuable feature!
In regards to `Does order of column in table and dialog views make sense?` i would recommend the following:

- `Create / Modify Breakpoint`
  - "Description" at the top and "Address" at the bottom:

<details><summary>Picture</summary>

![image](https://github.com/user-attachments/assets/435a7f76-b172-48b1-894d-3f30b9c146dc)

</details>

- `Breakpoints` view
  - "DESCRIPTION" at the beginning or after "X" (i think this could use a better column label?) - In my opinion it should not be in the middle of a Breakpoint configuration and rather follow a hierarchical order.

<details><summary>Picture</summary>

![image](https://github.com/user-attachments/assets/b87cfaff-bc90-43d3-b300-1b08de273a02)

</details>

But, it would be even better if a user could (after using `Layout Unlocked`) move the column labels anywhere where he wants (not sure how difficult this is to implement and if this is saved after closing PCSX2):

<details><summary>Picture</summary>

![image](https://github.com/user-attachments/assets/e8c3df1b-476b-4a53-ba22-ba664ce4d6b4)

</details>

I did a few tests, such as saving and loading, and could not find an issue yet.
author_association: CONTRIBUTOR
user: login: recursean
created_at: 2025-04-18T12:56:47Z
body: Thanks for taking the time to thoroughly test everything and coming up with the idea to do this in the first place!

In regards to moving the Description field higher up in the field hierarchy, my only hesitation in that is that the Description is an optional field that may be blank, while the other fields that currently come before it like Address/Offset/Type can never be blank AFAIK. So, if you had a blank field like Description higher in the hierarchy, it may look odd. I could be convinced either way, but currently I think the Address/Offset should come before Description because a breakpoint is truly all about what address you are setting a breakpoint on and the description is just some associated metadata for the breakpoint. Just something to think about and curious to hear your thoughts. 

Totally agreed about how it would be nice to be able to allow the user to reorder the columns. I honestly also don't know the effort that would be needed to implement functionality like that, but maybe if you open another enhancement request for that in the future, I can take a look :)
author_association: CONTRIBUTOR
user: login: recursean
created_at: 2025-04-23T02:23:42Z
body: Ok, I think this one is ready for review. I don't have any more changes planned for this.
author_association: CONTRIBUTOR
user: login: F0bes
created_at: 2025-04-23T02:35:52Z
body: Thanks. I will try to make some time to review it.
author_association: MEMBER
user: login: F0bes
created_at: 2025-04-24T23:49:45Z
body: Just the one formatting issue. Tested and it looks good.
author_association: MEMBER