From 433176a9b9270f637b12f8a8e5ab971c07838db9 Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupf321@gmail.com>
Date: Wed, 30 Jan 2019 23:06:01 +0800
Subject: [PATCH] citra_qt: Implement UI for adding/editing/deleting cheats

The UI file is rewritten, to better make use of Qt's layouts (instead of depending on abstract geometry). "Add Cheat", "Save", "Delete" buttons are also added.

The UI logic should be rather easy and usable (IMO), but the code may seem a bit dirty. If anyone has a better idea regarding UI logic design or code implementation, feel free to tell me about it.
---
 src/citra_qt/cheats.cpp | 189 ++++++++++++++++++++-
 src/citra_qt/cheats.h   |  34 +++-
 src/citra_qt/cheats.ui  | 352 +++++++++++++++++++++-------------------
 3 files changed, 395 insertions(+), 180 deletions(-)

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/>