pcsx2/pcsx2-qt/Settings/GameCheatSettingsWidget.cpp

239 lines
8.1 KiB
C++

// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "MainWindow.h"
#include "QtHost.h"
#include "QtUtils.h"
#include "SettingWidgetBinder.h"
#include "Settings/GameCheatSettingsWidget.h"
#include "Settings/SettingsWindow.h"
#include "pcsx2/GameList.h"
#include "pcsx2/Patch.h"
#include "common/HeterogeneousContainers.h"
GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsWindow* dialog, QWidget* parent)
: m_dialog(dialog)
{
m_ui.setupUi(this);
reloadList();
SettingsInterface* sif = m_dialog->getSettingsInterface();
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCheats, "EmuCore", "EnableCheats", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.allCRCsCheckbox, "EmuCore", "ShowCheatsForAllCRCs", false);
updateListEnabled();
connect(m_ui.enableCheats, &QCheckBox::checkStateChanged, this, &GameCheatSettingsWidget::updateListEnabled);
connect(m_ui.cheatList, &QTreeWidget::itemDoubleClicked, this, &GameCheatSettingsWidget::onCheatListItemDoubleClicked);
connect(m_ui.cheatList, &QTreeWidget::itemChanged, this, &GameCheatSettingsWidget::onCheatListItemChanged);
connect(m_ui.reloadCheats, &QPushButton::clicked, this, &GameCheatSettingsWidget::onReloadClicked);
connect(m_ui.enableAll, &QPushButton::clicked, this, [this]() { setStateForAll(true); });
connect(m_ui.disableAll, &QPushButton::clicked, this, [this]() { setStateForAll(false); });
connect(m_ui.allCRCsCheckbox, &QCheckBox::checkStateChanged, this, &GameCheatSettingsWidget::onReloadClicked);
dialog->registerWidgetHelp(m_ui.allCRCsCheckbox, tr("Show Cheats For All CRCs"), tr("Checked"),
tr("Toggles scanning patch files for all CRCs of the game. With this enabled available patches for the game serial with different CRCs will also be loaded."));
}
GameCheatSettingsWidget::~GameCheatSettingsWidget() = default;
void GameCheatSettingsWidget::onCheatListItemDoubleClicked(QTreeWidgetItem* item, int column)
{
QVariant data = item->data(0, Qt::UserRole);
if (!data.isValid())
return;
std::string cheat_name = data.toString().toStdString();
const bool new_state = !(item->checkState(0) == Qt::Checked);
item->setCheckState(0, new_state ? Qt::Checked : Qt::Unchecked);
setCheatEnabled(std::move(cheat_name), new_state, true);
}
void GameCheatSettingsWidget::onCheatListItemChanged(QTreeWidgetItem* item, int column)
{
QVariant data = item->data(0, Qt::UserRole);
if (!data.isValid())
return;
std::string cheat_name = data.toString().toStdString();
const bool current_enabled =
(std::find(m_enabled_patches.begin(), m_enabled_patches.end(), cheat_name) != m_enabled_patches.end());
const bool current_checked = (item->checkState(0) == Qt::Checked);
if (current_enabled == current_checked)
return;
setCheatEnabled(std::move(cheat_name), current_checked, true);
}
void GameCheatSettingsWidget::onReloadClicked()
{
reloadList();
// reload it on the emu thread too, so it picks up any changes
g_emu_thread->reloadPatches();
}
void GameCheatSettingsWidget::updateListEnabled()
{
const bool cheats_enabled = m_dialog->getEffectiveBoolValue("EmuCore", "EnableCheats", false);
m_ui.cheatList->setEnabled(cheats_enabled);
m_ui.enableAll->setEnabled(cheats_enabled);
m_ui.disableAll->setEnabled(cheats_enabled);
m_ui.reloadCheats->setEnabled(cheats_enabled);
m_ui.allCRCsCheckbox->setEnabled(cheats_enabled);
}
void GameCheatSettingsWidget::disableAllCheats()
{
SettingsInterface* si = m_dialog->getSettingsInterface();
si->ClearSection(Patch::CHEATS_CONFIG_SECTION);
si->Save();
}
void GameCheatSettingsWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {320, 100, -1});
}
void GameCheatSettingsWidget::setCheatEnabled(std::string name, bool enabled, bool save_and_reload_settings)
{
SettingsInterface* si = m_dialog->getSettingsInterface();
auto it = std::find(m_enabled_patches.begin(), m_enabled_patches.end(), name);
if (enabled)
{
si->AddToStringList(Patch::CHEATS_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY, name.c_str());
if (it == m_enabled_patches.end())
m_enabled_patches.push_back(std::move(name));
}
else
{
si->RemoveFromStringList(Patch::CHEATS_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY, name.c_str());
if (it != m_enabled_patches.end())
m_enabled_patches.erase(it);
}
if (save_and_reload_settings)
{
si->Save();
g_emu_thread->reloadGameSettings();
}
}
void GameCheatSettingsWidget::setStateForAll(bool enabled)
{
QSignalBlocker sb(m_ui.cheatList);
setStateRecursively(nullptr, enabled);
m_dialog->getSettingsInterface()->Save();
g_emu_thread->reloadGameSettings();
}
void GameCheatSettingsWidget::setStateRecursively(QTreeWidgetItem* parent, bool enabled)
{
const int count = parent ? parent->childCount() : m_ui.cheatList->topLevelItemCount();
for (int i = 0; i < count; i++)
{
QTreeWidgetItem* item = parent ? parent->child(i) : m_ui.cheatList->topLevelItem(i);
QVariant data = item->data(0, Qt::UserRole);
if (data.isValid())
{
if ((item->checkState(0) == Qt::Checked) != enabled)
{
item->setCheckState(0, enabled ? Qt::Checked : Qt::Unchecked);
setCheatEnabled(data.toString().toStdString(), enabled, false);
}
}
else
{
setStateRecursively(item, enabled);
}
}
}
void GameCheatSettingsWidget::reloadList()
{
u32 num_unlabelled_codes = 0;
bool showAllCRCS = m_ui.allCRCsCheckbox->isChecked();
m_patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), true, showAllCRCS, & num_unlabelled_codes);
m_enabled_patches =
m_dialog->getSettingsInterface()->GetStringList(Patch::CHEATS_CONFIG_SECTION, Patch::PATCH_ENABLE_CONFIG_KEY);
m_parent_map.clear();
while (m_ui.cheatList->topLevelItemCount() > 0)
delete m_ui.cheatList->takeTopLevelItem(0);
for (const Patch::PatchInfo& pi : m_patches)
{
const bool enabled =
(std::find(m_enabled_patches.begin(), m_enabled_patches.end(), pi.name) != m_enabled_patches.end());
const std::string_view parent_part = pi.GetNameParentPart();
QTreeWidgetItem* parent = getTreeWidgetParent(parent_part);
QTreeWidgetItem* item = new QTreeWidgetItem();
populateTreeWidgetItem(item, pi, enabled);
if (parent)
parent->addChild(item);
else
m_ui.cheatList->addTopLevelItem(item);
}
// Hide root indicator when there's no groups, frees up some whitespace.
m_ui.cheatList->setRootIsDecorated(!m_parent_map.empty());
if (num_unlabelled_codes > 0)
{
QTreeWidgetItem* item = new QTreeWidgetItem();
item->setText(0, tr("%1 unlabelled patch codes will automatically activate.").arg(num_unlabelled_codes));
m_ui.cheatList->addTopLevelItem(item);
}
}
QTreeWidgetItem* GameCheatSettingsWidget::getTreeWidgetParent(const std::string_view parent)
{
if (parent.empty())
return nullptr;
auto it = m_parent_map.find(parent);
if (it != m_parent_map.end())
return it->second;
std::string_view this_part = parent;
QTreeWidgetItem* parent_to_this = nullptr;
const std::string_view::size_type pos = parent.rfind('\\');
if (pos != std::string::npos && pos != (parent.size() - 1))
{
// go up the chain until we find the real parent, then back down
parent_to_this = getTreeWidgetParent(parent.substr(0, pos));
this_part = parent.substr(pos + 1);
}
QTreeWidgetItem* item = new QTreeWidgetItem();
item->setText(0, QString::fromUtf8(this_part.data(), this_part.length()));
if (parent_to_this)
parent_to_this->addChild(item);
else
m_ui.cheatList->addTopLevelItem(item);
// Must be called after adding.
item->setExpanded(true);
m_parent_map.emplace(parent, item);
return item;
}
void GameCheatSettingsWidget::populateTreeWidgetItem(QTreeWidgetItem* item, const Patch::PatchInfo& pi, bool enabled)
{
const std::string_view name_part = pi.GetNamePart();
item->setFlags(item->flags() | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren);
item->setCheckState(0, enabled ? Qt::Checked : Qt::Unchecked);
item->setData(0, Qt::UserRole, QString::fromStdString(pi.name));
if (!name_part.empty())
item->setText(0, QString::fromUtf8(name_part.data(), name_part.length()));
item->setText(1, QString::fromStdString(pi.author));
item->setText(2, QString::fromStdString(pi.description));
}