diff --git a/pcsx2-qt/CMakeLists.txt b/pcsx2-qt/CMakeLists.txt index 816ed5dbd5..a6e56d4e5a 100644 --- a/pcsx2-qt/CMakeLists.txt +++ b/pcsx2-qt/CMakeLists.txt @@ -136,6 +136,8 @@ target_sources(pcsx2-qt PRIVATE Debugger/BreakpointDialog.ui Debugger/Models/BreakpointModel.cpp Debugger/Models/BreakpointModel.h + Debugger/Models/ThreadModel.cpp + Debugger/Models/ThreadModel.h Tools/InputRecording/NewInputRecordingDlg.cpp Tools/InputRecording/NewInputRecordingDlg.h Tools/InputRecording/NewInputRecordingDlg.ui diff --git a/pcsx2-qt/Debugger/CpuWidget.cpp b/pcsx2-qt/Debugger/CpuWidget.cpp index 4f5a2635a0..b6f9d1ec84 100644 --- a/pcsx2-qt/Debugger/CpuWidget.cpp +++ b/pcsx2-qt/Debugger/CpuWidget.cpp @@ -20,6 +20,7 @@ #include "DisassemblyWidget.h" #include "BreakpointDialog.h" #include "Models/BreakpointModel.h" +#include "Models/ThreadModel.h" #include "DebugTools/DebugInterface.h" #include "DebugTools/Breakpoints.h" @@ -41,6 +42,7 @@ using namespace MipsStackWalk; CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) : m_cpu(cpu) , m_bpModel(cpu) + , m_threadModel(cpu) { m_ui.setupUi(this); @@ -59,13 +61,14 @@ CpuWidget::CpuWidget(QWidget* parent, DebugInterface& cpu) connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &CpuWidget::onBPListContextMenu); connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &CpuWidget::onBPListDoubleClicked); + m_ui.breakpointList->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); m_ui.breakpointList->setModel(&m_bpModel); - connect(m_ui.threadList, &QTableWidget::customContextMenuRequested, this, &CpuWidget::onThreadListContextMenu); - connect(m_ui.threadList, &QTableWidget::cellDoubleClicked, this, &CpuWidget::onThreadListDoubleClick); + connect(m_ui.threadList, &QTableView::customContextMenuRequested, this, &CpuWidget::onThreadListContextMenu); + connect(m_ui.threadList, &QTableView::doubleClicked, this, &CpuWidget::onThreadListDoubleClick); - connect(m_ui.threadList, &QTableWidget::customContextMenuRequested, this, &CpuWidget::onThreadListContextMenu); - connect(m_ui.threadList, &QTableWidget::cellDoubleClicked, this, &CpuWidget::onThreadListDoubleClick); + m_ui.threadList->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_ui.threadList->setModel(&m_threadModel); connect(m_ui.stackframeList, &QTableWidget::customContextMenuRequested, this, &CpuWidget::onStackListContextMenu); connect(m_ui.stackframeList, &QTableWidget::cellDoubleClicked, this, &CpuWidget::onStackListDoubleClick); @@ -247,14 +250,11 @@ void CpuWidget::onBPListContextMenu(QPoint pos) if (!m_cpu.isAlive()) return; - if (m_bplistContextMenu) - delete m_bplistContextMenu; - - m_bplistContextMenu = new QMenu(m_ui.breakpointList); + QMenu* contextMenu = new QMenu(tr("Breakpoint List Context Menu"), m_ui.breakpointList); QAction* newAction = new QAction(tr("New"), m_ui.breakpointList); connect(newAction, &QAction::triggered, this, &CpuWidget::contextBPListNew); - m_bplistContextMenu->addAction(newAction); + contextMenu->addAction(newAction); const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel(); @@ -262,21 +262,21 @@ void CpuWidget::onBPListContextMenu(QPoint pos) { QAction* editAction = new QAction(tr("Edit"), m_ui.breakpointList); connect(editAction, &QAction::triggered, this, &CpuWidget::contextBPListEdit); - m_bplistContextMenu->addAction(editAction); + contextMenu->addAction(editAction); if (selModel->selectedIndexes().count() == 1) { QAction* copyAction = new QAction(tr("Copy"), m_ui.breakpointList); connect(copyAction, &QAction::triggered, this, &CpuWidget::contextBPListCopy); - m_bplistContextMenu->addAction(copyAction); + contextMenu->addAction(copyAction); } QAction* deleteAction = new QAction(tr("Delete"), m_ui.breakpointList); connect(deleteAction, &QAction::triggered, this, &CpuWidget::contextBPListDelete); - m_bplistContextMenu->addAction(deleteAction); + contextMenu->addAction(deleteAction); } - m_bplistContextMenu->popup(m_ui.breakpointList->mapToGlobal(pos)); + contextMenu->popup(m_ui.breakpointList->mapToGlobal(pos)); } void CpuWidget::contextBPListCopy() @@ -334,8 +334,6 @@ void CpuWidget::updateFunctionList(bool whenEmpty) if (!m_cpu.isAlive()) return; - m_bpModel.refreshData(); - if (whenEmpty && m_ui.listFunctions->count()) return; @@ -371,127 +369,41 @@ void CpuWidget::updateFunctionList(bool whenEmpty) void CpuWidget::updateThreads() { - m_ui.threadList->setRowCount(0); - - if (m_cpu.getCpuType() == BREAKPOINT_EE) - m_threadlistObjects = getEEThreads(); - - for (size_t i = 0; i < m_threadlistObjects.size(); i++) - { - m_ui.threadList->insertRow(i); - - const auto& thread = m_threadlistObjects[i]; - - if (thread.data.status == THS_RUN) - m_activeThread = thread; - - QTableWidgetItem* idItem = new QTableWidgetItem(); - idItem->setText(QString::number(thread.tid)); - idItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled); - m_ui.threadList->setItem(i, 0, idItem); - - QTableWidgetItem* pcItem = new QTableWidgetItem(); - if (thread.data.status == THS_RUN) - pcItem->setText(FilledQStringFromValue(m_cpu.getPC(), 16)); - else - pcItem->setText(FilledQStringFromValue(thread.data.entry, 16)); - pcItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled); - m_ui.threadList->setItem(i, 1, pcItem); - - QTableWidgetItem* entryItem = new QTableWidgetItem(); - entryItem->setText(FilledQStringFromValue(thread.data.entry_init, 16)); - entryItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled); - m_ui.threadList->setItem(i, 2, entryItem); - - QTableWidgetItem* priorityItem = new QTableWidgetItem(); - priorityItem->setText(QString::number(thread.data.currentPriority)); - priorityItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled); - m_ui.threadList->setItem(i, 3, priorityItem); - - QString statusString; - switch (thread.data.status) - { - case THS_BAD: - statusString = tr("Bad"); - break; - case THS_RUN: - statusString = tr("Running"); - break; - case THS_READY: - statusString = tr("Ready"); - break; - case THS_WAIT: - statusString = tr("Waiting"); - break; - case THS_SUSPEND: - statusString = tr("Suspended"); - break; - case THS_WAIT_SUSPEND: - statusString = tr("Waiting/Suspended"); - break; - case THS_DORMANT: - statusString = tr("Dormant"); - break; - } - - QTableWidgetItem* statusItem = new QTableWidgetItem(); - statusItem->setText(statusString); - statusItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled); - m_ui.threadList->setItem(i, 4, statusItem); - - QString waitTypeString; - switch (thread.data.waitType) - { - case WAIT_NONE: - waitTypeString = tr("None"); - break; - case WAIT_WAKEUP_REQ: - waitTypeString = tr("Wakeup request"); - break; - case WAIT_SEMA: - waitTypeString = tr("Semaphore"); - break; - } - - QTableWidgetItem* waitItem = new QTableWidgetItem(); - waitItem->setText(waitTypeString); - waitItem->setFlags(Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEnabled); - m_ui.threadList->setItem(i, 5, waitItem); - } + m_threadModel.refreshData(); } void CpuWidget::onThreadListContextMenu(QPoint pos) { - if (!m_threadlistContextMenu) - { - m_threadlistContextMenu = new QMenu(m_ui.threadList); + if (!m_ui.threadList->selectionModel()->hasSelection()) + return; - QAction* copyAction = new QAction(tr("Copy"), m_ui.threadList); - connect(copyAction, &QAction::triggered, [this] { - const auto& items = m_ui.threadList->selectedItems(); - if (!items.size()) - return; - QApplication::clipboard()->setText(items.first()->text()); - }); - m_threadlistContextMenu->addAction(copyAction); - } + QMenu* contextMenu = new QMenu(tr("Thread List Context Menu"), m_ui.threadList); - m_threadlistContextMenu->exec(m_ui.threadList->mapToGlobal(pos)); + QAction* actionCopy = new QAction(tr("Copy"), m_ui.threadList); + connect(actionCopy, &QAction::triggered, [this]() { + const auto* selModel = m_ui.threadList->selectionModel(); + + if (!selModel->hasSelection()) + return; + + QGuiApplication::clipboard()->setText(m_ui.threadList->model()->data(selModel->currentIndex()).toString()); + }); + contextMenu->addAction(actionCopy); + + contextMenu->popup(m_ui.threadList->mapToGlobal(pos)); } -void CpuWidget::onThreadListDoubleClick(int row, int column) +void CpuWidget::onThreadListDoubleClick(const QModelIndex& index) { - const auto& entry = m_threadlistObjects.at(row); - if (column == 1) // PC + switch (index.column()) { - if (entry.data.status == THS_RUN) - m_ui.disassemblyWidget->gotoAddress(m_cpu.getPC()); - else - m_ui.disassemblyWidget->gotoAddress(entry.data.entry); - } - else if (column == 2) // Entry Point - { - m_ui.disassemblyWidget->gotoAddress(entry.data.entry_init); + case ThreadModel::ThreadColumns::ENTRY: + m_ui.memoryviewWidget->gotoAddress(m_ui.threadList->model()->data(index, Qt::UserRole).toUInt()); + m_ui.tabWidget->setCurrentWidget(m_ui.tab_memory); + break; + default: // Default to PC + m_ui.disassemblyWidget->gotoAddress(m_ui.threadList->model()->data(m_ui.threadList->model()->index(index.row(), ThreadModel::ThreadColumns::PC), Qt::UserRole).toUInt()); + break; } } diff --git a/pcsx2-qt/Debugger/CpuWidget.h b/pcsx2-qt/Debugger/CpuWidget.h index c860731b8c..b182ba96f7 100644 --- a/pcsx2-qt/Debugger/CpuWidget.h +++ b/pcsx2-qt/Debugger/CpuWidget.h @@ -23,6 +23,7 @@ #include "DebugTools/MipsStackWalk.h" #include "Models/BreakpointModel.h" +#include "Models/ThreadModel.h" #include "QtHost.h" #include @@ -50,7 +51,6 @@ public slots: void onVMPaused(); void updateBreakpoints(); - void onBPListDoubleClicked(const QModelIndex& index); void onBPListContextMenu(QPoint pos); @@ -60,8 +60,8 @@ public slots: void contextBPListEdit(); void updateThreads(); + void onThreadListDoubleClick(const QModelIndex& index); void onThreadListContextMenu(QPoint pos); - void onThreadListDoubleClick(int row, int column); void updateStackFrames(); void onStackListContextMenu(QPoint pos); @@ -93,8 +93,6 @@ public slots: private: std::vector m_registerTableViews; - QMenu* m_bplistContextMenu = 0; - QMenu* m_threadlistContextMenu = 0; QMenu* m_stacklistContextMenu = 0; QMenu* m_funclistContextMenu = 0; @@ -103,6 +101,7 @@ private: DebugInterface& m_cpu; BreakpointModel m_bpModel; + ThreadModel m_threadModel; std::vector m_threadlistObjects; EEThread m_activeThread; diff --git a/pcsx2-qt/Debugger/CpuWidget.ui b/pcsx2-qt/Debugger/CpuWidget.ui index 465a23e35a..447c30d7c7 100644 --- a/pcsx2-qt/Debugger/CpuWidget.ui +++ b/pcsx2-qt/Debugger/CpuWidget.ui @@ -322,7 +322,7 @@ QTabWidget::South - 1 + 0 @@ -438,7 +438,7 @@ 0 - + Qt::CustomContextMenu @@ -451,48 +451,6 @@ false - - 0 - - - 6 - - - true - - - false - - - - ID - - - - - PC - - - - - Entry Point - - - - - Priority - - - - - State - - - - - Wait Type - - diff --git a/pcsx2-qt/Debugger/DisassemblyWidget.cpp b/pcsx2-qt/Debugger/DisassemblyWidget.cpp index e467bcbb58..f4eb5b4484 100644 --- a/pcsx2-qt/Debugger/DisassemblyWidget.cpp +++ b/pcsx2-qt/Debugger/DisassemblyWidget.cpp @@ -734,4 +734,5 @@ void DisassemblyWidget::gotoAddress(u32 address) m_selectedAddressEnd = destAddress; this->repaint(); + this->setFocus(); } diff --git a/pcsx2-qt/Debugger/MemoryViewWidget.cpp b/pcsx2-qt/Debugger/MemoryViewWidget.cpp index 926e9d9009..3a6b53f99b 100644 --- a/pcsx2-qt/Debugger/MemoryViewWidget.cpp +++ b/pcsx2-qt/Debugger/MemoryViewWidget.cpp @@ -500,4 +500,5 @@ void MemoryViewWidget::gotoAddress(u32 address) m_table.UpdateStartAddress(address & ~0xF); m_table.selectedAddress = address; this->repaint(); + this->setFocus(); } diff --git a/pcsx2-qt/Debugger/Models/BreakpointModel.h b/pcsx2-qt/Debugger/Models/BreakpointModel.h index 88897ff31e..43f87144ed 100644 --- a/pcsx2-qt/Debugger/Models/BreakpointModel.h +++ b/pcsx2-qt/Debugger/Models/BreakpointModel.h @@ -50,7 +50,7 @@ public: bool insertRows(int row, int count, std::vector breakpoints, const QModelIndex& index = QModelIndex()); BreakpointMemcheck at(int row) const { return m_breakpoints.at(row); }; -public slots: + void refreshData(); private: diff --git a/pcsx2-qt/Debugger/Models/ThreadModel.cpp b/pcsx2-qt/Debugger/Models/ThreadModel.cpp new file mode 100644 index 0000000000..5cca4c5e03 --- /dev/null +++ b/pcsx2-qt/Debugger/Models/ThreadModel.cpp @@ -0,0 +1,142 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#include "PrecompiledHeader.h" + +#include "ThreadModel.h" + +#include "QtUtils.h" + +ThreadModel::ThreadModel(DebugInterface& cpu, QObject* parent) + : QAbstractTableModel(parent) + , m_cpu(cpu) +{ +} + +int ThreadModel::rowCount(const QModelIndex&) const +{ + if (m_cpu.getCpuType() == BREAKPOINT_EE) + { + Console.Warning("%d", getEEThreads().size()); + return getEEThreads().size(); + } + else + return 0; +} + +int ThreadModel::columnCount(const QModelIndex&) const +{ + return ThreadModel::COLUMN_COUNT; +} + +QVariant ThreadModel::data(const QModelIndex& index, int role) const +{ + if (role == Qt::DisplayRole) + { + const auto thread = getEEThreads().at(index.row()); + + switch (index.column()) + { + case ThreadModel::ID: + return thread.tid; + case ThreadModel::PC: + { + if (thread.data.status == THS_RUN) + return QtUtils::FilledQStringFromValue(m_cpu.getPC(), 16); + else + return QtUtils::FilledQStringFromValue(thread.data.entry, 16); + } + case ThreadModel::ENTRY: + return QtUtils::FilledQStringFromValue(thread.data.entry_init, 16); + case ThreadModel::PRIORITY: + return QString::number(thread.data.currentPriority); + case ThreadModel::STATE: + { + const auto& state = ThreadStateStrings.find(thread.data.status); + if (state != ThreadStateStrings.end()) + return state->second; + else + return tr("INVALID"); + } + case ThreadModel::WAIT_TYPE: + { + const auto& waitType = ThreadWaitStrings.find(thread.data.waitType); + if (waitType != ThreadWaitStrings.end()) + return waitType->second; + else + return tr("INVALID"); + } + } + } + else if (role == Qt::UserRole) + { + const auto& thread = getEEThreads().at(index.row()); + + switch (index.column()) + { + case ThreadModel::ID: + return thread.tid; + case ThreadModel::PC: + { + if (thread.data.status == THS_RUN) + return m_cpu.getPC(); + else + return thread.data.entry; + } + case ThreadModel::ENTRY: + return thread.data.entry_init; + case ThreadModel::PRIORITY: + return thread.data.currentPriority; + case ThreadModel::STATE: + return thread.data.status; + case ThreadModel::WAIT_TYPE: + return thread.data.waitType; + default: + return QVariant(); + } + } + return QVariant(); +} + +QVariant ThreadModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) + { + switch (section) + { + case ThreadColumns::ID: + return tr("ID"); + case ThreadColumns::PC: + return tr("PC"); + case ThreadColumns::ENTRY: + return tr("ENTRY"); + case ThreadColumns::PRIORITY: + return tr("PRIORITY"); + case ThreadColumns::STATE: + return tr("STATE"); + case ThreadColumns::WAIT_TYPE: + return tr("WAIT TYPE"); + default: + return QVariant(); + } + } + return QVariant(); +} + +void ThreadModel::refreshData() +{ + beginResetModel(); + endResetModel(); +} \ No newline at end of file diff --git a/pcsx2-qt/Debugger/Models/ThreadModel.h b/pcsx2-qt/Debugger/Models/ThreadModel.h new file mode 100644 index 0000000000..f615878b86 --- /dev/null +++ b/pcsx2-qt/Debugger/Models/ThreadModel.h @@ -0,0 +1,66 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2023 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + +#pragma once + +#include + +#include "DebugTools/DebugInterface.h" +#include "DebugTools/BiosDebugData.h" + +#include + +class ThreadModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum ThreadColumns : int + { + ID = 0, + PC, + ENTRY, + PRIORITY, + STATE, + WAIT_TYPE, + COLUMN_COUNT + }; + + explicit ThreadModel(DebugInterface& cpu, QObject* parent = nullptr); + + 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) const override; + + void refreshData(); + +private: + const std::map ThreadStateStrings{ + {THS_BAD, tr("BAD")}, + {THS_RUN, tr("RUN")}, + {THS_READY, tr("READY")}, + {THS_WAIT, tr("WAIT")}, + {THS_SUSPEND, tr("SUSPEND")}, + {THS_WAIT_SUSPEND, tr("WAIT SUSPEND")}, + {THS_DORMANT, tr("DORMANT")}}; + + const std::map ThreadWaitStrings{ + {WAIT_NONE, tr("NONE")}, + {WAIT_WAKEUP_REQ, tr("WAKEUP REQUEST")}, + {WAIT_SEMA, tr("SEMAPHORE")}}; + + DebugInterface& m_cpu; +}; \ No newline at end of file diff --git a/pcsx2-qt/pcsx2-qt.vcxproj b/pcsx2-qt/pcsx2-qt.vcxproj index 9b6d237669..fd6c4abe01 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj +++ b/pcsx2-qt/pcsx2-qt.vcxproj @@ -149,6 +149,7 @@ + @@ -230,6 +231,7 @@ + @@ -281,6 +283,7 @@ + diff --git a/pcsx2-qt/pcsx2-qt.vcxproj.filters b/pcsx2-qt/pcsx2-qt.vcxproj.filters index 9d4d27b7b0..e79d8c3b33 100644 --- a/pcsx2-qt/pcsx2-qt.vcxproj.filters +++ b/pcsx2-qt/pcsx2-qt.vcxproj.filters @@ -280,6 +280,9 @@ Debugger\Models + + Debugger\Models + moc @@ -301,6 +304,9 @@ moc + + moc + @@ -441,6 +447,9 @@ Debugger\Models + + Debugger\Models +