mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-11 00:29:11 +01:00
fef1b84f0a
QStringLiterals generate a buffer so that during runtime there's very little cost to constructing a QString. However, this also means that duplicated strings cannot be optimized out into a single entry that gets referenced everywhere, taking up space in the binary. Rather than use QStringLiteral(""), we can just use QString{} (the default constructor) to signify the empty string. This gets rid of an unnecessary string buffer from being created, saving a tiny bit of space. While we're at it, we can just use the character overloads of particular functions when they're available instead of using a QString overload. The characters in this case are Latin-1 to begin with, so we can just specify the characters as QLatin1Char instances to use those overloads. These will automatically convert to QChar if needed, so this is safe.
354 lines
9.8 KiB
C++
354 lines
9.8 KiB
C++
// Copyright 2017 Dolphin Emulator Project
|
|
// Licensed under GPLv2+
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "DolphinQt/Debugger/BreakpointWidget.h"
|
|
|
|
#include <QHeaderView>
|
|
#include <QTableWidget>
|
|
#include <QToolBar>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "Common/FileUtil.h"
|
|
#include "Common/IniFile.h"
|
|
#include "Core/ConfigManager.h"
|
|
#include "Core/Core.h"
|
|
#include "Core/PowerPC/BreakPoints.h"
|
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
|
#include "Core/PowerPC/PowerPC.h"
|
|
|
|
#include "DolphinQt/Debugger/NewBreakpointDialog.h"
|
|
#include "DolphinQt/Resources.h"
|
|
#include "DolphinQt/Settings.h"
|
|
|
|
BreakpointWidget::BreakpointWidget(QWidget* parent) : QDockWidget(parent)
|
|
{
|
|
setWindowTitle(tr("Breakpoints"));
|
|
setObjectName(QStringLiteral("breakpoints"));
|
|
|
|
setHidden(!Settings::Instance().IsBreakpointsVisible() ||
|
|
!Settings::Instance().IsDebugModeEnabled());
|
|
|
|
setAllowedAreas(Qt::AllDockWidgetAreas);
|
|
|
|
auto& settings = Settings::GetQSettings();
|
|
|
|
restoreGeometry(settings.value(QStringLiteral("breakpointwidget/geometry")).toByteArray());
|
|
// macOS: setHidden() needs to be evaluated before setFloating() for proper window presentation
|
|
// according to Settings
|
|
setFloating(settings.value(QStringLiteral("breakpointwidget/floating")).toBool());
|
|
|
|
CreateWidgets();
|
|
|
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, [this](Core::State state) {
|
|
UpdateButtonsEnabled();
|
|
if (state == Core::State::Uninitialized)
|
|
{
|
|
PowerPC::breakpoints.Clear();
|
|
PowerPC::memchecks.Clear();
|
|
Update();
|
|
}
|
|
});
|
|
|
|
connect(&Settings::Instance(), &Settings::BreakpointsVisibilityChanged,
|
|
[this](bool visible) { setHidden(!visible); });
|
|
|
|
connect(&Settings::Instance(), &Settings::DebugModeToggled, [this](bool enabled) {
|
|
setHidden(!enabled || !Settings::Instance().IsBreakpointsVisible());
|
|
});
|
|
|
|
connect(&Settings::Instance(), &Settings::ThemeChanged, this, &BreakpointWidget::UpdateIcons);
|
|
UpdateIcons();
|
|
}
|
|
|
|
BreakpointWidget::~BreakpointWidget()
|
|
{
|
|
auto& settings = Settings::GetQSettings();
|
|
|
|
settings.setValue(QStringLiteral("breakpointwidget/geometry"), saveGeometry());
|
|
settings.setValue(QStringLiteral("breakpointwidget/floating"), isFloating());
|
|
}
|
|
|
|
void BreakpointWidget::CreateWidgets()
|
|
{
|
|
m_toolbar = new QToolBar;
|
|
m_toolbar->setContentsMargins(0, 0, 0, 0);
|
|
m_toolbar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
|
|
|
m_table = new QTableWidget;
|
|
m_table->setContentsMargins(0, 0, 0, 0);
|
|
m_table->setColumnCount(5);
|
|
m_table->setSelectionMode(QAbstractItemView::SingleSelection);
|
|
m_table->setSelectionBehavior(QAbstractItemView::SelectRows);
|
|
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
|
m_table->verticalHeader()->hide();
|
|
|
|
connect(m_table, &QTableWidget::itemClicked, [this](QTableWidgetItem* item) {
|
|
if (m_table->selectedItems()[0]->row() == item->row() &&
|
|
Core::GetState() == Core::State::Paused)
|
|
{
|
|
auto address = m_table->selectedItems()[0]->data(Qt::UserRole).toUInt();
|
|
emit SelectedBreakpoint(address);
|
|
}
|
|
});
|
|
|
|
auto* layout = new QVBoxLayout;
|
|
|
|
layout->addWidget(m_toolbar);
|
|
layout->addWidget(m_table);
|
|
layout->setContentsMargins(2, 2, 2, 2);
|
|
layout->setSpacing(0);
|
|
|
|
m_new = m_toolbar->addAction(tr("New"), this, &BreakpointWidget::OnNewBreakpoint);
|
|
m_delete = m_toolbar->addAction(tr("Delete"), this, &BreakpointWidget::OnDelete);
|
|
m_clear = m_toolbar->addAction(tr("Clear"), this, &BreakpointWidget::OnClear);
|
|
|
|
m_load = m_toolbar->addAction(tr("Load"), this, &BreakpointWidget::OnLoad);
|
|
m_save = m_toolbar->addAction(tr("Save"), this, &BreakpointWidget::OnSave);
|
|
|
|
m_new->setEnabled(false);
|
|
m_load->setEnabled(false);
|
|
m_save->setEnabled(false);
|
|
|
|
QWidget* widget = new QWidget;
|
|
widget->setLayout(layout);
|
|
|
|
setWidget(widget);
|
|
}
|
|
|
|
void BreakpointWidget::UpdateIcons()
|
|
{
|
|
m_new->setIcon(Resources::GetScaledThemeIcon("debugger_add_breakpoint"));
|
|
m_delete->setIcon(Resources::GetScaledThemeIcon("debugger_delete"));
|
|
m_clear->setIcon(Resources::GetScaledThemeIcon("debugger_clear"));
|
|
m_load->setIcon(Resources::GetScaledThemeIcon("debugger_load"));
|
|
m_save->setIcon(Resources::GetScaledThemeIcon("debugger_save"));
|
|
}
|
|
|
|
void BreakpointWidget::closeEvent(QCloseEvent*)
|
|
{
|
|
Settings::Instance().SetBreakpointsVisible(false);
|
|
}
|
|
|
|
void BreakpointWidget::showEvent(QShowEvent* event)
|
|
{
|
|
UpdateButtonsEnabled();
|
|
Update();
|
|
}
|
|
|
|
void BreakpointWidget::UpdateButtonsEnabled()
|
|
{
|
|
if (!isVisible())
|
|
return;
|
|
|
|
const bool is_initialised = Core::GetState() != Core::State::Uninitialized;
|
|
m_new->setEnabled(is_initialised);
|
|
m_load->setEnabled(is_initialised);
|
|
m_save->setEnabled(is_initialised);
|
|
}
|
|
|
|
void BreakpointWidget::Update()
|
|
{
|
|
if (!isVisible())
|
|
return;
|
|
|
|
m_table->clear();
|
|
|
|
m_table->setHorizontalHeaderLabels(
|
|
{tr("Active"), tr("Type"), tr("Function"), tr("Address"), tr("Flags")});
|
|
|
|
int i = 0;
|
|
m_table->setRowCount(i);
|
|
|
|
const auto create_item = [](const QString string = {}) {
|
|
QTableWidgetItem* item = new QTableWidgetItem(string);
|
|
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
|
|
return item;
|
|
};
|
|
|
|
// Breakpoints
|
|
for (const auto& bp : PowerPC::breakpoints.GetBreakPoints())
|
|
{
|
|
m_table->setRowCount(i + 1);
|
|
|
|
auto* active = create_item(bp.is_enabled ? tr("on") : QString());
|
|
|
|
active->setData(Qt::UserRole, bp.address);
|
|
|
|
m_table->setItem(i, 0, active);
|
|
m_table->setItem(i, 1, create_item(QStringLiteral("BP")));
|
|
|
|
if (g_symbolDB.GetSymbolFromAddr(bp.address))
|
|
{
|
|
m_table->setItem(i, 2,
|
|
create_item(QString::fromStdString(g_symbolDB.GetDescription(bp.address))));
|
|
}
|
|
|
|
m_table->setItem(i, 3,
|
|
create_item(QStringLiteral("%1").arg(bp.address, 8, 16, QLatin1Char('0'))));
|
|
|
|
m_table->setItem(i, 4, create_item());
|
|
|
|
i++;
|
|
}
|
|
|
|
// Memory Breakpoints
|
|
for (const auto& mbp : PowerPC::memchecks.GetMemChecks())
|
|
{
|
|
m_table->setRowCount(i + 1);
|
|
auto* active = create_item(mbp.break_on_hit || mbp.log_on_hit ? tr("on") : QString());
|
|
active->setData(Qt::UserRole, mbp.start_address);
|
|
|
|
m_table->setItem(i, 0, active);
|
|
m_table->setItem(i, 1, create_item(QStringLiteral("MBP")));
|
|
|
|
if (g_symbolDB.GetSymbolFromAddr(mbp.start_address))
|
|
{
|
|
m_table->setItem(
|
|
i, 2, create_item(QString::fromStdString(g_symbolDB.GetDescription(mbp.start_address))));
|
|
}
|
|
|
|
if (mbp.is_ranged)
|
|
{
|
|
m_table->setItem(i, 3,
|
|
create_item(QStringLiteral("%1 - %2")
|
|
.arg(mbp.start_address, 8, 16, QLatin1Char('0'))
|
|
.arg(mbp.end_address, 8, 16, QLatin1Char('0'))));
|
|
}
|
|
else
|
|
{
|
|
m_table->setItem(
|
|
i, 3, create_item(QStringLiteral("%1").arg(mbp.start_address, 8, 16, QLatin1Char('0'))));
|
|
}
|
|
|
|
QString flags;
|
|
|
|
if (mbp.is_break_on_read)
|
|
flags.append(QLatin1Char{'r'});
|
|
|
|
if (mbp.is_break_on_write)
|
|
flags.append(QLatin1Char{'w'});
|
|
|
|
m_table->setItem(i, 4, create_item(flags));
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
void BreakpointWidget::OnDelete()
|
|
{
|
|
if (m_table->selectedItems().empty())
|
|
return;
|
|
|
|
auto address = m_table->selectedItems()[0]->data(Qt::UserRole).toUInt();
|
|
|
|
PowerPC::breakpoints.Remove(address);
|
|
Settings::Instance().blockSignals(true);
|
|
PowerPC::memchecks.Remove(address);
|
|
Settings::Instance().blockSignals(false);
|
|
|
|
Update();
|
|
}
|
|
|
|
void BreakpointWidget::OnClear()
|
|
{
|
|
PowerPC::debug_interface.ClearAllBreakpoints();
|
|
Settings::Instance().blockSignals(true);
|
|
PowerPC::debug_interface.ClearAllMemChecks();
|
|
Settings::Instance().blockSignals(false);
|
|
|
|
m_table->setRowCount(0);
|
|
Update();
|
|
}
|
|
|
|
void BreakpointWidget::OnNewBreakpoint()
|
|
{
|
|
NewBreakpointDialog* dialog = new NewBreakpointDialog(this);
|
|
dialog->exec();
|
|
}
|
|
|
|
void BreakpointWidget::OnLoad()
|
|
{
|
|
IniFile ini;
|
|
if (!ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
|
|
false))
|
|
{
|
|
return;
|
|
}
|
|
|
|
BreakPoints::TBreakPointsStr new_bps;
|
|
if (ini.GetLines("BreakPoints", &new_bps, false))
|
|
{
|
|
PowerPC::breakpoints.Clear();
|
|
PowerPC::breakpoints.AddFromStrings(new_bps);
|
|
}
|
|
|
|
MemChecks::TMemChecksStr new_mcs;
|
|
if (ini.GetLines("MemoryBreakPoints", &new_mcs, false))
|
|
{
|
|
PowerPC::memchecks.Clear();
|
|
Settings::Instance().blockSignals(true);
|
|
PowerPC::memchecks.AddFromStrings(new_mcs);
|
|
Settings::Instance().blockSignals(false);
|
|
}
|
|
|
|
Update();
|
|
}
|
|
|
|
void BreakpointWidget::OnSave()
|
|
{
|
|
IniFile ini;
|
|
ini.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini",
|
|
false);
|
|
ini.SetLines("BreakPoints", PowerPC::breakpoints.GetStrings());
|
|
ini.SetLines("MemoryBreakPoints", PowerPC::memchecks.GetStrings());
|
|
ini.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + SConfig::GetInstance().GetGameID() + ".ini");
|
|
}
|
|
|
|
void BreakpointWidget::AddBP(u32 addr)
|
|
{
|
|
PowerPC::breakpoints.Add(addr);
|
|
|
|
Update();
|
|
}
|
|
|
|
void BreakpointWidget::AddAddressMBP(u32 addr, bool on_read, bool on_write, bool do_log,
|
|
bool do_break)
|
|
{
|
|
TMemCheck check;
|
|
|
|
check.start_address = addr;
|
|
check.end_address = addr;
|
|
check.is_ranged = false;
|
|
check.is_break_on_read = on_read;
|
|
check.is_break_on_write = on_write;
|
|
check.log_on_hit = do_log;
|
|
check.break_on_hit = do_break;
|
|
|
|
Settings::Instance().blockSignals(true);
|
|
PowerPC::memchecks.Add(check);
|
|
Settings::Instance().blockSignals(false);
|
|
|
|
Update();
|
|
}
|
|
|
|
void BreakpointWidget::AddRangedMBP(u32 from, u32 to, bool on_read, bool on_write, bool do_log,
|
|
bool do_break)
|
|
{
|
|
TMemCheck check;
|
|
|
|
check.start_address = from;
|
|
check.end_address = to;
|
|
check.is_ranged = true;
|
|
check.is_break_on_read = on_read;
|
|
check.is_break_on_write = on_write;
|
|
check.log_on_hit = do_log;
|
|
check.break_on_hit = do_break;
|
|
|
|
Settings::Instance().blockSignals(true);
|
|
PowerPC::memchecks.Add(check);
|
|
Settings::Instance().blockSignals(false);
|
|
|
|
Update();
|
|
}
|