Qt/PropertiesDialog: Implement "Patches" tab
This commit is contained in:
parent
6a609e6e3c
commit
4b54f6b1c7
|
@ -71,6 +71,8 @@ set(SRCS
|
|||
Config/Mapping/WiimoteEmuExtension.cpp
|
||||
Config/Mapping/WiimoteEmuGeneral.cpp
|
||||
Config/Mapping/WiimoteEmuMotionControl.cpp
|
||||
Config/NewPatchDialog.cpp
|
||||
Config/PatchesWidget.cpp
|
||||
Config/PropertiesDialog.cpp
|
||||
Config/SettingsWindow.cpp
|
||||
Debugger/BreakpointWidget.cpp
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt2/Config/NewPatchDialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
NewPatchDialog::NewPatchDialog(PatchEngine::Patch& patch) : m_patch(patch)
|
||||
{
|
||||
setWindowTitle(tr("Patch Editor"));
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
for (size_t i = 0; i < m_patch.entries.size(); i++)
|
||||
{
|
||||
m_entry_layout->addWidget(CreateEntry(static_cast<int>(i)));
|
||||
}
|
||||
|
||||
if (m_patch.entries.empty())
|
||||
{
|
||||
AddEntry();
|
||||
m_patch.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
void NewPatchDialog::CreateWidgets()
|
||||
{
|
||||
m_name_edit = new QLineEdit;
|
||||
m_name_edit->setPlaceholderText(tr("Patch name"));
|
||||
m_name_edit->setText(QString::fromStdString(m_patch.name));
|
||||
|
||||
m_entry_widget = new QWidget;
|
||||
m_entry_layout = new QVBoxLayout;
|
||||
|
||||
auto* scroll_area = new QScrollArea;
|
||||
m_entry_widget->setLayout(m_entry_layout);
|
||||
scroll_area->setWidget(m_entry_widget);
|
||||
scroll_area->setWidgetResizable(true);
|
||||
|
||||
m_add_button = new QPushButton(tr("Add"));
|
||||
|
||||
m_button_box = new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel);
|
||||
|
||||
auto* layout = new QGridLayout;
|
||||
|
||||
layout->addWidget(m_name_edit, 0, 0);
|
||||
layout->addWidget(scroll_area, 1, 0);
|
||||
layout->addWidget(m_add_button, 2, 0);
|
||||
layout->addWidget(m_button_box, 3, 0);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void NewPatchDialog::ConnectWidgets()
|
||||
{
|
||||
connect(m_name_edit, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
|
||||
[this](const QString& name) { m_patch.name = name.toStdString(); });
|
||||
|
||||
connect(m_add_button, &QPushButton::pressed, this, &NewPatchDialog::AddEntry);
|
||||
|
||||
connect(m_button_box, &QDialogButtonBox::accepted, this, &NewPatchDialog::accept);
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
void NewPatchDialog::AddEntry()
|
||||
{
|
||||
PatchEngine::PatchEntry entry;
|
||||
entry.type = PatchEngine::PATCH_8BIT;
|
||||
entry.address = entry.value = 0;
|
||||
|
||||
m_patch.entries.push_back(entry);
|
||||
|
||||
m_entry_layout->addWidget(CreateEntry(static_cast<int>(m_patch.entries.size() - 1)));
|
||||
}
|
||||
|
||||
QGroupBox* NewPatchDialog::CreateEntry(int index)
|
||||
{
|
||||
QGroupBox* box = new QGroupBox();
|
||||
|
||||
auto* type = new QGroupBox(tr("Type"));
|
||||
auto* type_layout = new QHBoxLayout;
|
||||
auto* remove = new QPushButton(tr("Remove"));
|
||||
|
||||
auto* byte = new QRadioButton(tr("byte"));
|
||||
auto* word = new QRadioButton(tr("word"));
|
||||
auto* dword = new QRadioButton(tr("dword"));
|
||||
|
||||
type_layout->addWidget(byte);
|
||||
type_layout->addWidget(word);
|
||||
type_layout->addWidget(dword);
|
||||
type->setLayout(type_layout);
|
||||
|
||||
auto* offset = new QLineEdit;
|
||||
auto* value = new QLineEdit;
|
||||
|
||||
m_edits.push_back(offset);
|
||||
m_edits.push_back(value);
|
||||
|
||||
auto* layout = new QGridLayout;
|
||||
layout->addWidget(type, 0, 0, 1, -1);
|
||||
layout->addWidget(new QLabel(tr("Offset:")), 1, 0);
|
||||
layout->addWidget(offset, 1, 1);
|
||||
layout->addWidget(new QLabel(tr("Value:")), 2, 0);
|
||||
layout->addWidget(value, 2, 1);
|
||||
layout->addWidget(remove, 3, 0, 1, -1);
|
||||
box->setLayout(layout);
|
||||
|
||||
connect(offset, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
|
||||
[this, index, offset](const QString& text) {
|
||||
bool okay = true;
|
||||
m_patch.entries[index].address = text.toUInt(&okay, 16);
|
||||
|
||||
QFont font;
|
||||
QPalette palette;
|
||||
|
||||
font.setBold(!okay);
|
||||
|
||||
if (!okay)
|
||||
palette.setColor(QPalette::Text, Qt::red);
|
||||
|
||||
offset->setFont(font);
|
||||
offset->setPalette(palette);
|
||||
});
|
||||
|
||||
connect(value, static_cast<void (QLineEdit::*)(const QString&)>(&QLineEdit::textEdited),
|
||||
[this, index, value](const QString& text) {
|
||||
bool okay;
|
||||
m_patch.entries[index].value = text.toUInt(&okay, 16);
|
||||
|
||||
QFont font;
|
||||
QPalette palette;
|
||||
|
||||
font.setBold(!okay);
|
||||
|
||||
if (!okay)
|
||||
palette.setColor(QPalette::Text, Qt::red);
|
||||
|
||||
value->setFont(font);
|
||||
value->setPalette(palette);
|
||||
});
|
||||
|
||||
connect(remove, &QPushButton::pressed, [this, box, index] {
|
||||
if (m_patch.entries.size() > 1)
|
||||
{
|
||||
m_entry_layout->removeWidget(box);
|
||||
m_patch.entries.erase(m_patch.entries.begin() + index);
|
||||
m_id--;
|
||||
}
|
||||
});
|
||||
|
||||
connect(byte, &QRadioButton::toggled, [this, index](bool checked) {
|
||||
if (checked)
|
||||
m_patch.entries[index].type = PatchEngine::PATCH_8BIT;
|
||||
});
|
||||
|
||||
connect(word, &QRadioButton::toggled, [this, index](bool checked) {
|
||||
if (checked)
|
||||
m_patch.entries[index].type = PatchEngine::PATCH_16BIT;
|
||||
});
|
||||
|
||||
connect(dword, &QRadioButton::toggled, [this, index](bool checked) {
|
||||
if (checked)
|
||||
m_patch.entries[index].type = PatchEngine::PATCH_32BIT;
|
||||
});
|
||||
|
||||
auto entry_type = m_patch.entries[index].type;
|
||||
|
||||
byte->setChecked(entry_type == PatchEngine::PATCH_8BIT);
|
||||
word->setChecked(entry_type == PatchEngine::PATCH_16BIT);
|
||||
dword->setChecked(entry_type == PatchEngine::PATCH_32BIT);
|
||||
|
||||
offset->setText(
|
||||
QStringLiteral("%1").arg(m_patch.entries[index].address, 10, 16, QLatin1Char('0')));
|
||||
value->setText(QStringLiteral("%1").arg(m_patch.entries[index].value, 10, 16, QLatin1Char('0')));
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
void NewPatchDialog::accept()
|
||||
{
|
||||
if (m_name_edit->text().isEmpty())
|
||||
{
|
||||
QMessageBox::critical(this, tr("Error"), tr("You have to enter a name."));
|
||||
return;
|
||||
}
|
||||
|
||||
bool valid = true;
|
||||
|
||||
for (const auto* edit : m_edits)
|
||||
{
|
||||
edit->text().toUInt(&valid, 16);
|
||||
if (!valid)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Some values you provided are invalid.\nPlease check the highlighted values."));
|
||||
return;
|
||||
}
|
||||
|
||||
QDialog::accept();
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QDialog>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "DolphinQt2/GameList/GameFile.h"
|
||||
|
||||
class QDialogButtonBox;
|
||||
class QGroupBox;
|
||||
class QLineEdit;
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
|
||||
class NewPatchDialog : public QDialog
|
||||
{
|
||||
public:
|
||||
explicit NewPatchDialog(PatchEngine::Patch& patch);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void AddEntry();
|
||||
|
||||
void accept() override;
|
||||
|
||||
QGroupBox* CreateEntry(int index);
|
||||
|
||||
QLineEdit* m_name_edit;
|
||||
QWidget* m_entry_widget;
|
||||
QVBoxLayout* m_entry_layout;
|
||||
QPushButton* m_add_button;
|
||||
QDialogButtonBox* m_button_box;
|
||||
|
||||
std::vector<QLineEdit*> m_edits;
|
||||
|
||||
PatchEngine::Patch& m_patch;
|
||||
int m_id = 0;
|
||||
};
|
|
@ -0,0 +1,175 @@
|
|||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt2/Config/PatchesWidget.h"
|
||||
|
||||
#include <QGridLayout>
|
||||
#include <QListWidget>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/IniFile.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "DolphinQt2/Config/NewPatchDialog.h"
|
||||
|
||||
PatchesWidget::PatchesWidget(const GameFile& game)
|
||||
: m_game(game), m_game_id(game.GetGameID().toStdString()), m_game_revision(game.GetRevision())
|
||||
{
|
||||
IniFile game_ini_local;
|
||||
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
|
||||
IniFile game_ini_default = SConfig::GetInstance().LoadDefaultGameIni(m_game_id, m_game_revision);
|
||||
|
||||
PatchEngine::LoadPatchSection("OnFrame", m_patches, game_ini_default, game_ini_local);
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
Update();
|
||||
|
||||
UpdateActions();
|
||||
}
|
||||
|
||||
void PatchesWidget::CreateWidgets()
|
||||
{
|
||||
m_list = new QListWidget;
|
||||
m_add_button = new QPushButton(tr("&Add..."));
|
||||
m_edit_button = new QPushButton();
|
||||
m_remove_button = new QPushButton(tr("&Remove"));
|
||||
|
||||
auto* layout = new QGridLayout;
|
||||
|
||||
layout->addWidget(m_list, 0, 0, 1, -1);
|
||||
layout->addWidget(m_add_button, 1, 0);
|
||||
layout->addWidget(m_edit_button, 1, 2);
|
||||
layout->addWidget(m_remove_button, 1, 1);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void PatchesWidget::ConnectWidgets()
|
||||
{
|
||||
connect(m_list, &QListWidget::itemSelectionChanged, this, &PatchesWidget::UpdateActions);
|
||||
connect(m_list, &QListWidget::itemChanged, this, &PatchesWidget::OnItemChanged);
|
||||
connect(m_remove_button, &QPushButton::pressed, this, &PatchesWidget::OnRemove);
|
||||
connect(m_add_button, &QPushButton::pressed, this, &PatchesWidget::OnAdd);
|
||||
connect(m_edit_button, &QPushButton::pressed, this, &PatchesWidget::OnEdit);
|
||||
}
|
||||
|
||||
void PatchesWidget::OnItemChanged(QListWidgetItem* item)
|
||||
{
|
||||
m_patches[m_list->row(item)].active = (item->checkState() == Qt::Checked);
|
||||
SavePatches();
|
||||
}
|
||||
|
||||
void PatchesWidget::OnAdd()
|
||||
{
|
||||
PatchEngine::Patch patch;
|
||||
patch.user_defined = true;
|
||||
|
||||
if (NewPatchDialog(patch).exec())
|
||||
{
|
||||
m_patches.push_back(patch);
|
||||
SavePatches();
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchesWidget::OnEdit()
|
||||
{
|
||||
if (m_list->selectedItems().isEmpty())
|
||||
return;
|
||||
|
||||
auto* item = m_list->selectedItems()[0];
|
||||
|
||||
auto patch = m_patches[m_list->row(item)];
|
||||
|
||||
if (!patch.user_defined)
|
||||
patch.name += tr(" (Copy)").toStdString();
|
||||
|
||||
if (NewPatchDialog(patch).exec())
|
||||
{
|
||||
if (patch.user_defined)
|
||||
{
|
||||
m_patches[m_list->row(item)] = patch;
|
||||
}
|
||||
else
|
||||
{
|
||||
patch.user_defined = true;
|
||||
m_patches.push_back(patch);
|
||||
}
|
||||
|
||||
SavePatches();
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
void PatchesWidget::OnRemove()
|
||||
{
|
||||
if (m_list->selectedItems().isEmpty())
|
||||
return;
|
||||
|
||||
m_patches.erase(m_patches.begin() + m_list->row(m_list->selectedItems()[0]));
|
||||
SavePatches();
|
||||
Update();
|
||||
}
|
||||
|
||||
void PatchesWidget::SavePatches()
|
||||
{
|
||||
std::vector<std::string> lines;
|
||||
std::vector<std::string> lines_enabled;
|
||||
|
||||
for (const auto& patch : m_patches)
|
||||
{
|
||||
if (patch.active)
|
||||
lines_enabled.push_back("$" + patch.name);
|
||||
|
||||
if (!patch.user_defined)
|
||||
continue;
|
||||
|
||||
lines.push_back("$" + patch.name);
|
||||
|
||||
for (const auto& entry : patch.entries)
|
||||
{
|
||||
lines.push_back(StringFromFormat("0x%08X:%s:0x%08X", entry.address,
|
||||
PatchEngine::PatchTypeStrings[entry.type], entry.value));
|
||||
}
|
||||
}
|
||||
|
||||
IniFile game_ini_local;
|
||||
game_ini_local.Load(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
|
||||
game_ini_local.SetLines("OnFrame_Enabled", lines_enabled);
|
||||
game_ini_local.SetLines("OnFrame", lines);
|
||||
|
||||
game_ini_local.Save(File::GetUserPath(D_GAMESETTINGS_IDX) + m_game_id + ".ini");
|
||||
}
|
||||
|
||||
void PatchesWidget::Update()
|
||||
{
|
||||
m_list->clear();
|
||||
|
||||
for (const auto& patch : m_patches)
|
||||
{
|
||||
auto* item = new QListWidgetItem(QString::fromStdString(patch.name));
|
||||
item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
|
||||
item->setCheckState(patch.active ? Qt::Checked : Qt::Unchecked);
|
||||
item->setData(Qt::UserRole, patch.user_defined);
|
||||
|
||||
m_list->addItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
void PatchesWidget::UpdateActions()
|
||||
{
|
||||
bool selected = !m_list->selectedItems().isEmpty();
|
||||
auto* item = m_list->selectedItems()[0];
|
||||
|
||||
bool user_defined = selected ? item->data(Qt::UserRole).toBool() : true;
|
||||
|
||||
m_edit_button->setEnabled(selected);
|
||||
m_edit_button->setText(user_defined ? tr("&Edit...") : tr("&Clone..."));
|
||||
m_remove_button->setEnabled(selected && user_defined);
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "Core/PatchEngine.h"
|
||||
#include "DolphinQt2/GameList/GameFile.h"
|
||||
|
||||
class QListWidget;
|
||||
class QListWidgetItem;
|
||||
class QPushButton;
|
||||
|
||||
class PatchesWidget : public QWidget
|
||||
{
|
||||
public:
|
||||
explicit PatchesWidget(const GameFile& game);
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
void SavePatches();
|
||||
void Update();
|
||||
void UpdateActions();
|
||||
|
||||
void OnItemChanged(QListWidgetItem*);
|
||||
void OnAdd();
|
||||
void OnRemove();
|
||||
void OnEdit();
|
||||
|
||||
QListWidget* m_list;
|
||||
QPushButton* m_add_button;
|
||||
QPushButton* m_edit_button;
|
||||
QPushButton* m_remove_button;
|
||||
|
||||
std::vector<PatchEngine::Patch> m_patches;
|
||||
const GameFile& m_game;
|
||||
std::string m_game_id;
|
||||
u16 m_game_revision;
|
||||
};
|
|
@ -10,6 +10,7 @@
|
|||
#include "DolphinQt2/Config/FilesystemWidget.h"
|
||||
#include "DolphinQt2/Config/GeckoCodeWidget.h"
|
||||
#include "DolphinQt2/Config/InfoWidget.h"
|
||||
#include "DolphinQt2/Config/PatchesWidget.h"
|
||||
#include "DolphinQt2/Config/PropertiesDialog.h"
|
||||
|
||||
PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDialog(parent)
|
||||
|
@ -23,6 +24,7 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia
|
|||
|
||||
ARCodeWidget* ar = new ARCodeWidget(game);
|
||||
GeckoCodeWidget* gecko = new GeckoCodeWidget(game);
|
||||
PatchesWidget* patches = new PatchesWidget(game);
|
||||
|
||||
connect(gecko, &GeckoCodeWidget::OpenGeneralSettings, this,
|
||||
&PropertiesDialog::OpenGeneralSettings);
|
||||
|
@ -30,6 +32,7 @@ PropertiesDialog::PropertiesDialog(QWidget* parent, const GameFile& game) : QDia
|
|||
connect(ar, &ARCodeWidget::OpenGeneralSettings, this, &PropertiesDialog::OpenGeneralSettings);
|
||||
|
||||
tab_widget->addTab(info, tr("Info"));
|
||||
tab_widget->addTab(patches, tr("Patches"));
|
||||
tab_widget->addTab(ar, tr("AR Codes"));
|
||||
tab_widget->addTab(gecko, tr("Gecko Codes"));
|
||||
|
||||
|
|
|
@ -72,6 +72,7 @@
|
|||
<QtMoc Include="Config\Mapping\MappingWindow.h" />
|
||||
<QtMoc Include="Config\LogConfigWidget.h" />
|
||||
<QtMoc Include="Config\LogWidget.h" />
|
||||
<QtMoc Include="Config\NewPatchDialog.h" />
|
||||
<QtMoc Include="Config\Graphics\AdvancedWidget.h" />
|
||||
<QtMoc Include="Config\Graphics\EnhancementsWidget.h" />
|
||||
<QtMoc Include="Config\Graphics\GeneralWidget.h" />
|
||||
|
@ -83,6 +84,7 @@
|
|||
<QtMoc Include="Config\Graphics\HacksWidget.h" />
|
||||
<QtMoc Include="Config\Graphics\SoftwareRendererWidget.h" />
|
||||
<QtMoc Include="Config\InfoWidget.h" />
|
||||
<QtMoc Include="Config\PatchesWidget.h" />
|
||||
<QtMoc Include="Config\PropertiesDialog.h" />
|
||||
<QtMoc Include="Config\SettingsWindow.h" />
|
||||
<QtMoc Include="FIFOPlayerWindow.h" />
|
||||
|
@ -175,7 +177,9 @@
|
|||
<ClCompile Include="$(QtMocOutPrefix)NetPlayDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)NetPlaySetupDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)NewBreakpointDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)NewPatchDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)PadMappingDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)PatchesWidget.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)PropertiesDialog.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)DoubleClickEventFilter.cpp" />
|
||||
<ClCompile Include="$(QtMocOutPrefix)RegisterWidget.cpp" />
|
||||
|
@ -226,6 +230,8 @@
|
|||
<ClCompile Include="Config\Mapping\WiimoteEmuMotionControl.cpp" />
|
||||
<ClCompile Include="Config\LogConfigWidget.cpp" />
|
||||
<ClCompile Include="Config\LogWidget.cpp" />
|
||||
<ClCompile Include="Config\NewPatchDialog.cpp" />
|
||||
<ClCompile Include="Config\PatchesWidget.cpp" />
|
||||
<ClCompile Include="Config\PropertiesDialog.cpp" />
|
||||
<ClCompile Include="Config\SettingsWindow.cpp" />
|
||||
<ClCompile Include="FIFOPlayerWindow.cpp" />
|
||||
|
|
Loading…
Reference in New Issue