diff --git a/src/citra_qt/cheats.cpp b/src/citra_qt/cheats.cpp index 720609205..fc6f102a2 100644 --- a/src/citra_qt/cheats.cpp +++ b/src/citra_qt/cheats.cpp @@ -3,10 +3,12 @@ // Refer to the license.txt file included. #include <QCheckBox> +#include <QMessageBox> #include <QTableWidgetItem> #include "citra_qt/cheats.h" #include "core/cheats/cheat_base.h" #include "core/cheats/cheats.h" +#include "core/cheats/gateway_cheat.h" #include "core/core.h" #include "core/hle/kernel/process.h" #include "ui_cheats.h" @@ -21,14 +23,23 @@ CheatDialog::CheatDialog(QWidget* parent) ui->tableCheats->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); ui->tableCheats->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); ui->tableCheats->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); - ui->textDetails->setEnabled(false); + ui->lineName->setEnabled(false); + ui->textCode->setEnabled(false); ui->textNotes->setEnabled(false); const auto game_id = fmt::format( "{:016X}", Core::System::GetInstance().Kernel().GetCurrentProcess()->codeset->program_id); ui->labelTitle->setText(tr("Title ID: %1").arg(QString::fromStdString(game_id))); connect(ui->buttonClose, &QPushButton::released, this, &CheatDialog::OnCancel); + connect(ui->buttonAddCheat, &QPushButton::released, this, &CheatDialog::OnAddCheat); connect(ui->tableCheats, &QTableWidget::cellClicked, this, &CheatDialog::OnRowSelected); + connect(ui->lineName, &QLineEdit::textEdited, this, &CheatDialog::OnTextEdited); + connect(ui->textNotes, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited); + connect(ui->textCode, &QPlainTextEdit::textChanged, this, &CheatDialog::OnTextEdited); + + connect(ui->buttonSave, &QPushButton::released, + [this] { SaveCheat(ui->tableCheats->currentRow()); }); + connect(ui->buttonDelete, &QPushButton::released, this, &CheatDialog::OnDeleteCheat); LoadCheats(); } @@ -36,7 +47,7 @@ CheatDialog::CheatDialog(QWidget* parent) CheatDialog::~CheatDialog() = default; void CheatDialog::LoadCheats() { - const auto& cheats = Core::System::GetInstance().CheatEngine().GetCheats(); + cheats = Core::System::GetInstance().CheatEngine().GetCheats(); ui->tableCheats->setRowCount(cheats.size()); @@ -56,20 +67,184 @@ void CheatDialog::LoadCheats() { } } +bool CheatDialog::CheckSaveCheat() { + auto answer = QMessageBox::warning( + this, tr("Cheats"), tr("Would you like to save the current cheat?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel); + + if (answer == QMessageBox::Yes) { + return SaveCheat(last_row); + } else { + return answer != QMessageBox::Cancel; + } +} + +bool CheatDialog::SaveCheat(int row) { + if (ui->lineName->text().isEmpty()) { + QMessageBox::critical(this, tr("Save Cheat"), tr("Please enter a cheat name.")); + return false; + } + if (ui->textCode->toPlainText().isEmpty()) { + QMessageBox::critical(this, tr("Save Cheat"), tr("Please enter the cheat code.")); + return false; + } + + // Check if the cheat lines are valid + auto code_lines = ui->textCode->toPlainText().split("\n", QString::SkipEmptyParts); + for (int i = 0; i < code_lines.size(); ++i) { + Cheats::GatewayCheat::CheatLine cheat_line(code_lines[i].toStdString()); + if (cheat_line.valid) + continue; + + auto answer = QMessageBox::warning( + this, tr("Save Cheat"), + tr("Cheat code line %1 is not valid.\nWould you like to ignore the error and continue?") + .arg(i + 1), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No); + if (answer == QMessageBox::No) + return false; + } + + auto cheat = std::make_shared<Cheats::GatewayCheat>(ui->lineName->text().toStdString(), + ui->textCode->toPlainText().toStdString(), + ui->textNotes->toPlainText().toStdString()); + + if (newly_created) { + Core::System::GetInstance().CheatEngine().AddCheat(cheat); + newly_created = false; + } else { + Core::System::GetInstance().CheatEngine().UpdateCheat(row, cheat); + } + Core::System::GetInstance().CheatEngine().SaveCheatFile(); + + int previous_row = ui->tableCheats->currentRow(); + int previous_col = ui->tableCheats->currentColumn(); + LoadCheats(); + ui->tableCheats->setCurrentCell(previous_row, previous_col); + + edited = false; + ui->buttonSave->setEnabled(false); + ui->buttonAddCheat->setEnabled(true); + return true; +} + +void CheatDialog::closeEvent(QCloseEvent* event) { + if (edited && !CheckSaveCheat()) { + event->ignore(); + return; + } + event->accept(); +} + void CheatDialog::OnCancel() { close(); } void CheatDialog::OnRowSelected(int row, int column) { - ui->textDetails->setEnabled(true); + if (row == last_row) { + return; + } + if (edited && !CheckSaveCheat()) { + ui->tableCheats->setCurrentCell(last_row, last_col); + return; + } + if (row < cheats.size()) { + if (newly_created) { + // Remove the newly created dummy item + newly_created = false; + ui->tableCheats->setRowCount(ui->tableCheats->rowCount() - 1); + } + + const auto& current_cheat = cheats[row]; + ui->lineName->setText(QString::fromStdString(current_cheat->GetName())); + ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments())); + ui->textCode->setPlainText(QString::fromStdString(current_cheat->GetCode())); + } + + edited = false; + ui->buttonSave->setEnabled(false); + ui->buttonDelete->setEnabled(true); + ui->buttonAddCheat->setEnabled(true); + ui->lineName->setEnabled(true); + ui->textCode->setEnabled(true); ui->textNotes->setEnabled(true); - const auto& current_cheat = Core::System::GetInstance().CheatEngine().GetCheats()[row]; - ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments())); - ui->textDetails->setPlainText(QString::fromStdString(current_cheat->ToString())); + + last_row = row; + last_col = column; } void CheatDialog::OnCheckChanged(int state) { const QCheckBox* checkbox = qobject_cast<QCheckBox*>(sender()); int row = static_cast<int>(checkbox->property("row").toInt()); - Core::System::GetInstance().CheatEngine().GetCheats()[row]->SetEnabled(state); + cheats[row]->SetEnabled(state); + Core::System::GetInstance().CheatEngine().SaveCheatFile(); +} + +void CheatDialog::OnTextEdited() { + edited = true; + ui->buttonSave->setEnabled(true); +} + +void CheatDialog::OnDeleteCheat() { + if (newly_created) { + newly_created = false; + } else { + Core::System::GetInstance().CheatEngine().RemoveCheat(ui->tableCheats->currentRow()); + Core::System::GetInstance().CheatEngine().SaveCheatFile(); + } + + LoadCheats(); + if (cheats.empty()) { + ui->lineName->setText(""); + ui->textCode->setPlainText(""); + ui->textNotes->setPlainText(""); + ui->lineName->setEnabled(false); + ui->textCode->setEnabled(false); + ui->textNotes->setEnabled(false); + ui->buttonDelete->setEnabled(false); + last_row = last_col = -1; + } else { + if (last_row >= ui->tableCheats->rowCount()) { + last_row = ui->tableCheats->rowCount() - 1; + } + ui->tableCheats->setCurrentCell(last_row, last_col); + + const auto& current_cheat = cheats[last_row]; + ui->lineName->setText(QString::fromStdString(current_cheat->GetName())); + ui->textNotes->setPlainText(QString::fromStdString(current_cheat->GetComments())); + ui->textCode->setPlainText(QString::fromStdString(current_cheat->GetCode())); + } + + edited = false; + ui->buttonSave->setEnabled(false); + ui->buttonAddCheat->setEnabled(true); +} + +void CheatDialog::OnAddCheat() { + if (edited && !CheckSaveCheat()) { + return; + } + + int row = ui->tableCheats->rowCount(); + ui->tableCheats->setRowCount(row + 1); + ui->tableCheats->setCurrentCell(row, 1); + + // create a dummy item + ui->tableCheats->setItem(row, 1, new QTableWidgetItem(tr("[new cheat]"))); + ui->tableCheats->setItem(row, 2, new QTableWidgetItem("")); + ui->lineName->setText(""); + ui->lineName->setPlaceholderText(tr("[new cheat]")); + ui->textCode->setPlainText(""); + ui->textNotes->setPlainText(""); + ui->lineName->setEnabled(true); + ui->textCode->setEnabled(true); + ui->textNotes->setEnabled(true); + ui->buttonSave->setEnabled(true); + ui->buttonDelete->setEnabled(true); + ui->buttonAddCheat->setEnabled(false); + + edited = false; + newly_created = true; + last_row = row; + last_col = 1; } diff --git a/src/citra_qt/cheats.h b/src/citra_qt/cheats.h index d532175ab..89c08b1c5 100644 --- a/src/citra_qt/cheats.h +++ b/src/citra_qt/cheats.h @@ -7,6 +7,10 @@ #include <memory> #include <QDialog> +namespace Cheats { +class CheatBase; +} + namespace Ui { class CheatDialog; } // namespace Ui @@ -19,12 +23,38 @@ public: ~CheatDialog(); private: - std::unique_ptr<Ui::CheatDialog> ui; - + /** + * Loads the cheats from the CheatEngine, and populates the table. + */ void LoadCheats(); + /** + * Pops up a message box asking if the user wants to save the current cheat. + * If the user selected Yes, attempts to save the current cheat. + * @return true if the user selected No, or if the cheat was saved successfully + * false if the user selected Cancel, or if the user selected Yes but saving failed + */ + bool CheckSaveCheat(); + + /** + * Saves the current cheat as the row-th cheat in the cheat list. + * @return true if the cheat is saved successfully, false otherwise + */ + bool SaveCheat(int row); + + void closeEvent(QCloseEvent* event) override; + private slots: void OnCancel(); void OnRowSelected(int row, int column); void OnCheckChanged(int state); + void OnTextEdited(); + void OnDeleteCheat(); + void OnAddCheat(); + +private: + std::unique_ptr<Ui::CheatDialog> ui; + std::vector<std::shared_ptr<Cheats::CheatBase>> cheats; + bool edited = false, newly_created = false; + int last_row = -1, last_col = -1; }; diff --git a/src/citra_qt/cheats.ui b/src/citra_qt/cheats.ui index 08c0da19f..b6bb35b9e 100644 --- a/src/citra_qt/cheats.ui +++ b/src/citra_qt/cheats.ui @@ -22,182 +22,192 @@ <property name="windowTitle"> <string>Cheats</string> </property> - <widget class="QLabel" name="labelTitle"> - <property name="geometry"> - <rect> - <x>10</x> - <y>10</y> - <width>300</width> - <height>31</height> - </rect> - </property> - <property name="font"> - <font> - <pointsize>10</pointsize> - </font> - </property> - <property name="text"> - <string>Title ID:</string> - </property> - </widget> - <widget class="QWidget" name="horizontalLayoutWidget"> - <property name="geometry"> - <rect> - <x>10</x> - <y>570</y> - <width>841</width> - <height>41</height> - </rect> - </property> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <spacer name="horizontalSpacer"> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>40</width> - <height>20</height> - </size> - </property> - </spacer> - </item> - <item> - <widget class="QPushButton" name="buttonClose"> - <property name="text"> - <string>Close</string> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QWidget" name="verticalLayoutWidget"> - <property name="geometry"> - <rect> - <x>10</x> - <y>80</y> - <width>551</width> - <height>471</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QTableWidget" name="tableCheats"> - <property name="editTriggers"> - <set>QAbstractItemView::NoEditTriggers</set> - </property> - <property name="selectionBehavior"> - <enum>QAbstractItemView::SelectRows</enum> - </property> - <property name="showGrid"> - <bool>false</bool> - </property> - <property name="columnCount"> - <number>3</number> - </property> - <attribute name="horizontalHeaderVisible"> - <bool>true</bool> - </attribute> - <attribute name="verticalHeaderVisible"> - <bool>false</bool> - </attribute> - <column> - <property name="text"> - <string/> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="labelTitle"> + <property name="font"> + <font> + <pointsize>10</pointsize> + </font> </property> - </column> - <column> <property name="text"> - <string>Name</string> + <string>Title ID:</string> </property> - </column> - <column> + </widget> + </item> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonAddCheat"> <property name="text"> - <string>Type</string> + <string>Add Cheat</string> </property> - </column> - </widget> - </item> - </layout> - </widget> - <widget class="QLabel" name="labelAvailableCheats"> - <property name="geometry"> - <rect> - <x>10</x> - <y>60</y> - <width>121</width> - <height>16</height> - </rect> - </property> - <property name="text"> - <string>Available Cheats:</string> - </property> - </widget> - <widget class="QWidget" name="verticalLayoutWidget_2"> - <property name="geometry"> - <rect> - <x>580</x> - <y>440</y> - <width>271</width> - <height>111</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <widget class="QPlainTextEdit" name="textNotes"> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QLabel" name="labelNotes"> - <property name="geometry"> - <rect> - <x>580</x> - <y>420</y> - <width>111</width> - <height>16</height> - </rect> - </property> - <property name="text"> - <string>Notes:</string> - </property> - </widget> - <widget class="QWidget" name="verticalLayoutWidget_3"> - <property name="geometry"> - <rect> - <x>580</x> - <y>80</y> - <width>271</width> - <height>311</height> - </rect> - </property> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <item> - <widget class="QPlainTextEdit" name="textDetails"> - <property name="readOnly"> - <bool>true</bool> - </property> - </widget> - </item> - </layout> - </widget> - <widget class="QLabel" name="labelDetails"> - <property name="geometry"> - <rect> - <x>580</x> - <y>60</y> - <width>55</width> - <height>16</height> - </rect> - </property> - <property name="text"> - <string>Code:</string> - </property> - </widget> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <item> + <layout class="QVBoxLayout"> + <item> + <widget class="QLabel" name="labelAvailableCheats"> + <property name="geometry"> + <rect> + <x>10</x> + <y>60</y> + <width>121</width> + <height>16</height> + </rect> + </property> + <property name="text"> + <string>Available Cheats:</string> + </property> + </widget> + </item> + <item> + <widget class="QTableWidget" name="tableCheats"> + <property name="editTriggers"> + <set>QAbstractItemView::NoEditTriggers</set> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="selectionMode"> + <enum>QAbstractItemView::SingleSelection</enum> + </property> + <property name="showGrid"> + <bool>false</bool> + </property> + <property name="columnCount"> + <number>3</number> + </property> + <attribute name="horizontalHeaderVisible"> + <bool>true</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string/> + </property> + </column> + <column> + <property name="text"> + <string>Name</string> + </property> + </column> + <column> + <property name="text"> + <string>Type</string> + </property> + </column> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonSave"> + <property name="text"> + <string>Save</string> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="buttonDelete"> + <property name="text"> + <string>Delete</string> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout"> + <item> + <layout class="QHBoxLayout"> + <item> + <widget class="QLabel" name="labelName"> + <property name="text"> + <string>Name:</string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lineName"/> + </item> + </layout> + </item> + <item> + <widget class="QLabel" name="labelNotes"> + <property name="text"> + <string>Notes:</string> + </property> + </widget> + </item> + <item> + <widget class="QPlainTextEdit" name="textNotes"/> + </item> + <item> + <widget class="QLabel" name="labelCode"> + <property name="text"> + <string>Code:</string> + </property> + </widget> + </item> + <item> + <widget class="QPlainTextEdit" name="textCode"/> + </item> + </layout> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout"> + <item> + <spacer> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="buttonClose"> + <property name="text"> + <string>Close</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> </widget> <resources/> <connections/>