mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-10-12 02:08:45 +00:00
225 lines
7.2 KiB
C++
225 lines
7.2 KiB
C++
// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
#include "BreakpointView.h"
|
|
|
|
#include "QtUtils.h"
|
|
#include "Debugger/DebuggerSettingsManager.h"
|
|
#include "Debugger/DebuggerWindow.h"
|
|
#include "BreakpointDialog.h"
|
|
#include "BreakpointModel.h"
|
|
|
|
#include <QtGui/QClipboard>
|
|
|
|
BreakpointView::BreakpointView(const DebuggerViewParameters& parameters)
|
|
: DebuggerView(parameters, DISALLOW_MULTIPLE_INSTANCES)
|
|
, m_model(BreakpointModel::getInstance(cpu()))
|
|
{
|
|
m_ui.setupUi(this);
|
|
|
|
m_ui.breakpointList->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(m_ui.breakpointList, &QTableView::customContextMenuRequested, this, &BreakpointView::openContextMenu);
|
|
connect(m_ui.breakpointList, &QTableView::doubleClicked, this, &BreakpointView::onDoubleClicked);
|
|
|
|
m_ui.breakpointList->setModel(m_model);
|
|
m_ui.breakpointList->horizontalHeader()->setSectionsMovable(true);
|
|
this->resizeColumns();
|
|
|
|
connect(g_debugger_window, &DebuggerWindow::onVMActuallyPaused, m_model, &BreakpointModel::refreshData);
|
|
|
|
connect(m_model, &QAbstractTableModel::modelAboutToBeReset, this, [this]() {
|
|
const QItemSelectionModel* sel = m_ui.breakpointList->selectionModel();
|
|
if (sel->hasSelection())
|
|
{
|
|
m_selectedRowOnRefresh = sel->selectedIndexes().first().row();
|
|
m_selectedColOnRefresh = sel->selectedIndexes().first().column();
|
|
}
|
|
else
|
|
{
|
|
m_selectedRowOnRefresh = -1;
|
|
m_selectedColOnRefresh = -1;
|
|
}
|
|
});
|
|
connect(m_model, &QAbstractTableModel::modelReset, this, [this]() {
|
|
if (m_selectedRowOnRefresh >= 0 && m_selectedRowOnRefresh < m_model->rowCount())
|
|
{
|
|
const QModelIndex idx = m_model->index(m_selectedRowOnRefresh, m_selectedColOnRefresh);
|
|
m_ui.breakpointList->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
|
|
}
|
|
});
|
|
|
|
// Continue-on-hit breakpoints update their hit counts without ever pausing the VM,
|
|
// so onVMActuallyPaused never fires. Refresh periodically off the debugger's
|
|
// refresh timer so the UI can update regularly. We don't refresh if the VM is paused
|
|
// or if a child of the breakpoint list is being edited.
|
|
receiveEvent<DebuggerEvents::Refresh>([this](const DebuggerEvents::Refresh& event) -> bool {
|
|
// If focus is on a child of the breakpoint list and not the list itself,
|
|
// we assume an inline editor is active and we should not refresh.
|
|
bool isEditing = m_ui.breakpointList->isAncestorOf(QApplication::focusWidget())
|
|
&& QApplication::focusWidget() != m_ui.breakpointList;
|
|
|
|
if (!QtHost::IsVMPaused() && !isEditing)
|
|
m_model->refreshData();
|
|
|
|
return true;
|
|
});
|
|
}
|
|
|
|
void BreakpointView::onDoubleClicked(const QModelIndex& index)
|
|
{
|
|
if (index.isValid() && index.column() == BreakpointModel::OFFSET)
|
|
goToInDisassembler(m_model->data(index, BreakpointModel::DataRole).toUInt(), true);
|
|
}
|
|
|
|
void BreakpointView::openContextMenu(QPoint pos)
|
|
{
|
|
QMenu* menu = new QMenu(m_ui.breakpointList);
|
|
menu->setAttribute(Qt::WA_DeleteOnClose);
|
|
|
|
if (cpu().isAlive())
|
|
{
|
|
QAction* newAction = menu->addAction(tr("New"));
|
|
connect(newAction, &QAction::triggered, this, &BreakpointView::contextNew);
|
|
|
|
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
|
|
|
|
if (selModel->hasSelection())
|
|
{
|
|
QAction* editAction = menu->addAction(tr("Edit"));
|
|
connect(editAction, &QAction::triggered, this, &BreakpointView::contextEdit);
|
|
|
|
if (selModel->selectedIndexes().count() == 1)
|
|
{
|
|
QAction* copyAction = menu->addAction(tr("Copy"));
|
|
connect(copyAction, &QAction::triggered, this, &BreakpointView::contextCopy);
|
|
}
|
|
|
|
QAction* deleteAction = menu->addAction(tr("Delete"));
|
|
connect(deleteAction, &QAction::triggered, this, &BreakpointView::contextDelete);
|
|
}
|
|
}
|
|
|
|
menu->addSeparator();
|
|
if (m_model->rowCount() > 0)
|
|
{
|
|
QAction* actionExport = menu->addAction(tr("Copy all as CSV"));
|
|
connect(actionExport, &QAction::triggered, [this]() {
|
|
// It's important to use the Export Role here to allow pasting to be translation agnostic
|
|
QGuiApplication::clipboard()->setText(
|
|
QtUtils::AbstractItemModelToCSV(m_model, BreakpointModel::ExportRole, true));
|
|
});
|
|
}
|
|
|
|
if (cpu().isAlive())
|
|
{
|
|
QAction* actionImport = menu->addAction(tr("Paste from CSV"));
|
|
connect(actionImport, &QAction::triggered, this, &BreakpointView::contextPasteCSV);
|
|
|
|
if (cpu().getCpuType() == BREAKPOINT_EE)
|
|
{
|
|
QAction* actionLoad = menu->addAction(tr("Load from Settings"));
|
|
connect(actionLoad, &QAction::triggered, [this]() {
|
|
m_model->clear();
|
|
DebuggerSettingsManager::loadGameSettings(m_model);
|
|
});
|
|
|
|
QAction* actionSave = menu->addAction(tr("Save to Settings"));
|
|
connect(actionSave, &QAction::triggered, this, &BreakpointView::saveBreakpointsToDebuggerSettings);
|
|
}
|
|
}
|
|
|
|
menu->popup(m_ui.breakpointList->viewport()->mapToGlobal(pos));
|
|
}
|
|
|
|
void BreakpointView::contextCopy()
|
|
{
|
|
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
|
|
|
|
if (!selModel->hasSelection())
|
|
return;
|
|
|
|
QGuiApplication::clipboard()->setText(m_model->data(selModel->currentIndex()).toString());
|
|
}
|
|
|
|
void BreakpointView::contextDelete()
|
|
{
|
|
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
|
|
|
|
if (!selModel->hasSelection())
|
|
return;
|
|
|
|
QModelIndexList indices = selModel->selectedIndexes();
|
|
|
|
std::set<int> rows;
|
|
for (QModelIndex index : indices)
|
|
rows.emplace(index.row());
|
|
|
|
for (auto row = rows.rbegin(); row != rows.rend(); row++)
|
|
m_model->removeRows(*row, 1);
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
void BreakpointView::contextEdit()
|
|
{
|
|
const QItemSelectionModel* selModel = m_ui.breakpointList->selectionModel();
|
|
|
|
if (!selModel->hasSelection())
|
|
return;
|
|
|
|
const int selectedRow = selModel->selectedIndexes().first().row();
|
|
|
|
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();
|
|
}
|
|
|
|
void BreakpointView::contextPasteCSV()
|
|
{
|
|
QString csv = QGuiApplication::clipboard()->text();
|
|
// Skip header
|
|
csv = csv.mid(csv.indexOf('\n') + 1);
|
|
|
|
for (const QString& line : csv.split('\n'))
|
|
{
|
|
QStringList fields;
|
|
// In order to handle text with commas in them we must wrap values in quotes to mark
|
|
// where a value starts and end so that text commas aren't identified as delimiters.
|
|
// So matches each quote pair, parse it out, and removes the quotes to get the value.
|
|
QRegularExpression eachQuotePair(R"("([^"]|\\.)*")");
|
|
QRegularExpressionMatchIterator it = eachQuotePair.globalMatch(line);
|
|
while (it.hasNext())
|
|
{
|
|
QRegularExpressionMatch match = it.next();
|
|
QString matchedValue = match.captured(0);
|
|
fields << matchedValue.mid(1, matchedValue.length() - 2);
|
|
}
|
|
m_model->loadBreakpointFromFieldList(fields);
|
|
}
|
|
}
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
#include "moc_BreakpointView.cpp"
|