/* PCSX2 - PS2 Emulator for PCs * Copyright (C) 2023 PCSX2 Dev Team * * PCSX2 is free software: you can redistribute it and/or modify it under the terms * of the GNU Lesser General Public License as published by the Free Software Found- * ation, either version 3 of the License, or (at your option) any later version. * * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with PCSX2. * If not, see . */ #include "PrecompiledHeader.h" #include "MainWindow.h" #include "QtHost.h" #include "QtUtils.h" #include "SettingWidgetBinder.h" #include "Settings/GameCheatSettingsWidget.h" #include "Settings/SettingsDialog.h" #include "pcsx2/GameList.h" #include "pcsx2/Patch.h" #include "common/HeterogeneousContainers.h" GameCheatSettingsWidget::GameCheatSettingsWidget(SettingsDialog* dialog, QWidget* parent) : m_dialog(dialog) { m_ui.setupUi(this); QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {300, 100, -1}); reloadList(); SettingsInterface* sif = m_dialog->getSettingsInterface(); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCheats, "EmuCore", "EnableCheats", false); updateListEnabled(); connect(m_ui.enableCheats, &QCheckBox::stateChanged, 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::reloadList); connect(m_ui.enableAll, &QPushButton::clicked, this, [this]() { setStateForAll(true); }); connect(m_ui.disableAll, &QPushButton::clicked, this, [this]() { setStateForAll(false); }); } 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); } 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; m_patches = Patch::GetPatchInfo(m_dialog->getSerial(), m_dialog->getDiscCRC(), true, &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)); }