From 690f326613998ea9cc0737c2bef99d1ba2cb911c 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)
---
 src/common/param_package.cpp               | 18 ++++-
 src/common/param_package.h                 |  2 +
 src/yuzu/configuration/configure_input.cpp | 87 +++++++++++++++++-----
 src/yuzu/configuration/configure_input.h   |  3 +
 src/yuzu/configuration/configure_input.ui  | 28 +++++++
 5 files changed, 119 insertions(+), 19 deletions(-)

diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
index 9526ca0c64..b916b48668 100644
--- a/src/common/param_package.cpp
+++ b/src/common/param_package.cpp
@@ -20,7 +20,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);
 
@@ -46,7 +54,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;
 
@@ -120,4 +128,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 7842cd4ef5..6a0a9b6564 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;
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 473937ea9c..94789c0641 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -5,6 +5,7 @@
 #include <algorithm>
 #include <memory>
 #include <utility>
+#include <QMenu>
 #include <QMessageBox>
 #include <QTimer>
 #include "common/param_package.h"
@@ -128,28 +129,63 @@ ConfigureInput::ConfigureInput(QWidget* parent)
     analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
 
     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(this, tr("Information"),
@@ -162,6 +198,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
         });
     }
 
+    connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
     connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
 
     timeout_timer->setSingleShot(true);
@@ -215,7 +252,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/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index a0bef86d56..d1198db811 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -72,6 +72,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/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index 8bfa5df62a..8a019a6939 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -694,6 +694,34 @@ Capture:</string>
        </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">