From a3625b544fe8a98067d18d27955e403821eb7354 Mon Sep 17 00:00:00 2001
From: zhupengfei <zhupengfei321@sina.cn>
Date: Sun, 26 Aug 2018 16:23:12 +0800
Subject: [PATCH] citra_qt/configuration: misc input tab improvements

* Added a context menu on the buttons including Clear & Restore Default

* Allow clearing (unsetting) inputs. Added a Clear All button

* Allow restoring a single input to default (instead of all)
---
 .../configuration/configure_input.cpp         | 87 +++++++++++++++----
 src/citra_qt/configuration/configure_input.h  |  3 +
 src/citra_qt/configuration/configure_input.ui | 28 ++++++
 src/common/param_package.cpp                  | 18 +++-
 src/common/param_package.h                    |  2 +
 5 files changed, 119 insertions(+), 19 deletions(-)

diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp
index 510da3848..e3504f23e 100644
--- a/src/citra_qt/configuration/configure_input.cpp
+++ b/src/citra_qt/configuration/configure_input.cpp
@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <memory>
 #include <utility>
+#include <QMenu>
 #include <QMessageBox>
 #include <QTimer>
 #include "citra_qt/configuration/config.h"
@@ -126,28 +127,63 @@ ConfigureInput::ConfigureInput(QWidget* parent)
     analog_map_stick = {ui->buttonCircleAnalog, ui->buttonCStickAnalog};
 
     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
-        if (button_map[button_id])
-            connect(button_map[button_id], &QPushButton::released, [=]() {
-                handleClick(
-                    button_map[button_id],
-                    [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
-                    InputCommon::Polling::DeviceType::Button);
-            });
+        if (!button_map[button_id])
+            continue;
+        button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
+        connect(button_map[button_id], &QPushButton::released, [=]() {
+            handleClick(
+                button_map[button_id],
+                [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
+                InputCommon::Polling::DeviceType::Button);
+        });
+        connect(button_map[button_id], &QPushButton::customContextMenuRequested,
+                [=](const QPoint& menu_location) {
+                    QMenu context_menu;
+                    context_menu.addAction(tr("Clear"), [&] {
+                        buttons_param[button_id].Clear();
+                        button_map[button_id]->setText(tr("[not set]"));
+                    });
+                    context_menu.addAction(tr("Restore Default"), [&] {
+                        buttons_param[button_id] = Common::ParamPackage{
+                            InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+                        button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
+                    });
+                    context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
+                });
     }
 
     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
         for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
-            if (analog_map_buttons[analog_id][sub_button_id] != nullptr) {
-                connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released,
-                        [=]() {
-                            handleClick(analog_map_buttons[analog_id][sub_button_id],
-                                        [=](const Common::ParamPackage& params) {
-                                            SetAnalogButton(params, analogs_param[analog_id],
-                                                            analog_sub_buttons[sub_button_id]);
-                                        },
-                                        InputCommon::Polling::DeviceType::Button);
+            if (!analog_map_buttons[analog_id][sub_button_id])
+                continue;
+            analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
+                Qt::CustomContextMenu);
+            connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
+                handleClick(analog_map_buttons[analog_id][sub_button_id],
+                            [=](const Common::ParamPackage& params) {
+                                SetAnalogButton(params, analogs_param[analog_id],
+                                                analog_sub_buttons[sub_button_id]);
+                            },
+                            InputCommon::Polling::DeviceType::Button);
+            });
+            connect(analog_map_buttons[analog_id][sub_button_id],
+                    &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
+                        QMenu context_menu;
+                        context_menu.addAction(tr("Clear"), [&] {
+                            analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
+                            analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
                         });
-            }
+                        context_menu.addAction(tr("Restore Default"), [&] {
+                            Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
+                                Config::default_analogs[analog_id][sub_button_id])};
+                            SetAnalogButton(params, analogs_param[analog_id],
+                                            analog_sub_buttons[sub_button_id]);
+                            analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
+                                analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
+                        });
+                        context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
+                            menu_location));
+                    });
         }
         connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
             QMessageBox::information(
@@ -164,6 +200,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
         QDialog* motion_touch_dialog = new ConfigureMotionTouch(this);
         return motion_touch_dialog->exec();
     });
+    connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
     connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
 
     timeout_timer->setSingleShot(true);
@@ -217,7 +254,21 @@ void ConfigureInput::restoreDefaults() {
         }
     }
     updateButtonLabels();
-    applyConfiguration();
+}
+
+void ConfigureInput::ClearAll() {
+    for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+        if (button_map[button_id] && button_map[button_id]->isEnabled())
+            buttons_param[button_id].Clear();
+    }
+    for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
+        for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+            if (analog_map_buttons[analog_id][sub_button_id] &&
+                analog_map_buttons[analog_id][sub_button_id]->isEnabled())
+                analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
+        }
+    }
+    updateButtonLabels();
 }
 
 void ConfigureInput::updateButtonLabels() {
diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h
index 2efebf20d..0e61dc00d 100644
--- a/src/citra_qt/configuration/configure_input.h
+++ b/src/citra_qt/configuration/configure_input.h
@@ -73,6 +73,9 @@ private:
     void loadConfiguration();
     /// Restore all buttons to their default values.
     void restoreDefaults();
+    /// Clear all input configuration
+    void ClearAll();
+
     /// Update UI to reflect current configuration.
     void updateButtonLabels();
 
diff --git a/src/citra_qt/configuration/configure_input.ui b/src/citra_qt/configuration/configure_input.ui
index 5812a02f0..8421adf09 100644
--- a/src/citra_qt/configuration/configure_input.ui
+++ b/src/citra_qt/configuration/configure_input.ui
@@ -597,6 +597,34 @@
        </property>
       </spacer>
      </item>
+     <item>
+      <widget class="QPushButton" name="buttonClearAll">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="sizeIncrement">
+        <size>
+         <width>0</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="baseSize">
+        <size>
+         <width>0</width>
+         <height>0</height>
+        </size>
+       </property>
+       <property name="layoutDirection">
+        <enum>Qt::LeftToRight</enum>
+       </property>
+       <property name="text">
+        <string>Clear All</string>
+       </property>
+      </widget>
+     </item>
      <item>
       <widget class="QPushButton" name="buttonRestoreDefaults">
        <property name="sizePolicy">
diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
index d17486976..433b34b36 100644
--- a/src/common/param_package.cpp
+++ b/src/common/param_package.cpp
@@ -19,7 +19,15 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
 constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
 constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
 
+/// A placeholder for empty param packages to avoid empty strings
+/// (they may be recognized as "not set" by some frontend libraries like qt)
+constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
+
 ParamPackage::ParamPackage(const std::string& serialized) {
+    if (serialized == EMPTY_PLACEHOLDER) {
+        return;
+    }
+
     std::vector<std::string> pairs;
     Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
 
@@ -45,7 +53,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
 
 std::string ParamPackage::Serialize() const {
     if (data.empty())
-        return "";
+        return EMPTY_PLACEHOLDER;
 
     std::string result;
 
@@ -119,4 +127,12 @@ bool ParamPackage::Has(const std::string& key) const {
     return data.find(key) != data.end();
 }
 
+void ParamPackage::Erase(const std::string& key) {
+    data.erase(key);
+}
+
+void ParamPackage::Clear() {
+    data.clear();
+}
+
 } // namespace Common
diff --git a/src/common/param_package.h b/src/common/param_package.h
index 7842cd4ef..6a0a9b656 100644
--- a/src/common/param_package.h
+++ b/src/common/param_package.h
@@ -32,6 +32,8 @@ public:
     void Set(const std::string& key, int value);
     void Set(const std::string& key, float value);
     bool Has(const std::string& key) const;
+    void Erase(const std::string& key);
+    void Clear();
 
 private:
     DataType data;