@@ -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,13 +24,15 @@ 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);

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" />
@@ -227,6 +231,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" />