From c8f6754417628d696f81513e34951193f8e5d023 Mon Sep 17 00:00:00 2001
From: Lioncash <mathew1800@gmail.com>
Date: Tue, 7 Aug 2018 00:43:07 -0400
Subject: [PATCH] qt/hotkey: Get rid of global hotkey map instance

Instead, we make a proper registry class and house it within the main
window, then pass it to whatever needs access to the loaded hotkeys.

This way, we avoid a global variable, and don't need to initialize a
std::map instance before the program can do anything.
---
 src/yuzu/configuration/configure_dialog.cpp  |   5 +-
 src/yuzu/configuration/configure_dialog.h    |   4 +-
 src/yuzu/configuration/configure_general.cpp |   4 +
 src/yuzu/configuration/configure_general.h   |   4 +-
 src/yuzu/hotkeys.cpp                         |  67 ++++++------
 src/yuzu/hotkeys.h                           | 101 ++++++++++++-------
 src/yuzu/main.cpp                            |  82 ++++++++-------
 src/yuzu/main.h                              |   3 +
 8 files changed, 155 insertions(+), 115 deletions(-)

diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 1ca7e876ca..cc4b326aef 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -6,9 +6,12 @@
 #include "ui_configure.h"
 #include "yuzu/configuration/config.h"
 #include "yuzu/configuration/configure_dialog.h"
+#include "yuzu/hotkeys.h"
 
-ConfigureDialog::ConfigureDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ConfigureDialog) {
+ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry)
+    : QDialog(parent), ui(new Ui::ConfigureDialog) {
     ui->setupUi(this);
+    ui->generalTab->PopulateHotkeyList(registry);
     this->setConfiguration();
 }
 
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 21fa1f5019..bbbdacc29c 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -7,6 +7,8 @@
 #include <memory>
 #include <QDialog>
 
+class HotkeyRegistry;
+
 namespace Ui {
 class ConfigureDialog;
 }
@@ -15,7 +17,7 @@ class ConfigureDialog : public QDialog {
     Q_OBJECT
 
 public:
-    explicit ConfigureDialog(QWidget* parent);
+    explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry);
     ~ConfigureDialog();
 
     void applyConfiguration();
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 04afc8724b..d8caee1e8e 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -35,6 +35,10 @@ void ConfigureGeneral::setConfiguration() {
     ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
 }
 
+void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
+    ui->widget->Populate(registry);
+}
+
 void ConfigureGeneral::applyConfiguration() {
     UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
     UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index 447552d8c6..4770034cc0 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -7,6 +7,8 @@
 #include <memory>
 #include <QWidget>
 
+class HotkeyRegistry;
+
 namespace Ui {
 class ConfigureGeneral;
 }
@@ -18,11 +20,11 @@ public:
     explicit ConfigureGeneral(QWidget* parent = nullptr);
     ~ConfigureGeneral();
 
+    void PopulateHotkeyList(const HotkeyRegistry& registry);
     void applyConfiguration();
 
 private:
     void setConfiguration();
 
-private:
     std::unique_ptr<Ui::ConfigureGeneral> ui;
 };
diff --git a/src/yuzu/hotkeys.cpp b/src/yuzu/hotkeys.cpp
index 61acb38eea..dce3997746 100644
--- a/src/yuzu/hotkeys.cpp
+++ b/src/yuzu/hotkeys.cpp
@@ -10,58 +10,53 @@
 #include "yuzu/hotkeys.h"
 #include "yuzu/ui_settings.h"
 
-struct Hotkey {
-    Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
+HotkeyRegistry::HotkeyRegistry() = default;
+HotkeyRegistry::~HotkeyRegistry() = default;
 
-    QKeySequence keyseq;
-    QShortcut* shortcut;
-    Qt::ShortcutContext context;
-};
-
-typedef std::map<QString, Hotkey> HotkeyMap;
-typedef std::map<QString, HotkeyMap> HotkeyGroupMap;
-
-HotkeyGroupMap hotkey_groups;
-
-void SaveHotkeys() {
-    UISettings::values.shortcuts.clear();
-    for (auto group : hotkey_groups) {
-        for (auto hotkey : group.second) {
-            UISettings::values.shortcuts.emplace_back(
-                UISettings::Shortcut(group.first + "/" + hotkey.first,
-                                     UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
-                                                                    hotkey.second.context)));
-        }
-    }
-}
-
-void LoadHotkeys() {
+void HotkeyRegistry::LoadHotkeys() {
     // Make sure NOT to use a reference here because it would become invalid once we call
     // beginGroup()
     for (auto shortcut : UISettings::values.shortcuts) {
-        QStringList cat = shortcut.first.split("/");
+        const QStringList cat = shortcut.first.split('/');
         Q_ASSERT(cat.size() >= 2);
 
         // RegisterHotkey assigns default keybindings, so use old values as default parameters
         Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
         if (!shortcut.second.first.isEmpty()) {
             hk.keyseq = QKeySequence::fromString(shortcut.second.first);
-            hk.context = (Qt::ShortcutContext)shortcut.second.second;
+            hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second);
         }
         if (hk.shortcut)
             hk.shortcut->setKey(hk.keyseq);
     }
 }
 
-void RegisterHotkey(const QString& group, const QString& action, const QKeySequence& default_keyseq,
-                    Qt::ShortcutContext default_context) {
-    if (hotkey_groups[group].find(action) == hotkey_groups[group].end()) {
-        hotkey_groups[group][action].keyseq = default_keyseq;
-        hotkey_groups[group][action].context = default_context;
+void HotkeyRegistry::SaveHotkeys() {
+    UISettings::values.shortcuts.clear();
+    for (const auto& group : hotkey_groups) {
+        for (const auto& hotkey : group.second) {
+            UISettings::values.shortcuts.emplace_back(
+                UISettings::Shortcut(group.first + '/' + hotkey.first,
+                                     UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
+                                                                    hotkey.second.context)));
+        }
     }
 }
 
-QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget) {
+void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action,
+                                    const QKeySequence& default_keyseq,
+                                    Qt::ShortcutContext default_context) {
+    auto& hotkey_group = hotkey_groups[group];
+    if (hotkey_group.find(action) != hotkey_group.end()) {
+        return;
+    }
+
+    auto& hotkey_action = hotkey_groups[group][action];
+    hotkey_action.keyseq = default_keyseq;
+    hotkey_action.context = default_context;
+}
+
+QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
     Hotkey& hk = hotkey_groups[group][action];
 
     if (!hk.shortcut)
@@ -72,10 +67,12 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge
 
 GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) {
     ui.setupUi(this);
+}
 
-    for (auto group : hotkey_groups) {
+void GHotkeysDialog::Populate(const HotkeyRegistry& registry) {
+    for (const auto& group : registry.hotkey_groups) {
         QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first));
-        for (auto hotkey : group.second) {
+        for (const auto& hotkey : group.second) {
             QStringList columns;
             columns << hotkey.first << hotkey.second.keyseq.toString();
             QTreeWidgetItem* item = new QTreeWidgetItem(columns);
diff --git a/src/yuzu/hotkeys.h b/src/yuzu/hotkeys.h
index a4ccc193ba..f38e6c0028 100644
--- a/src/yuzu/hotkeys.h
+++ b/src/yuzu/hotkeys.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <map>
 #include "ui_hotkeys.h"
 
 class QDialog;
@@ -11,47 +12,69 @@ class QKeySequence;
 class QSettings;
 class QShortcut;
 
-/**
- * Register a hotkey.
- *
- * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
- * @param action Name of the action (e.g. "Start Emulation", "Load Image")
- * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the settings
- * file before
- * @param default_context Default context to assign if the hotkey wasn't present in the settings
- * file before
- * @warning Both the group and action strings will be displayed in the hotkey settings dialog
- */
-void RegisterHotkey(const QString& group, const QString& action,
-                    const QKeySequence& default_keyseq = QKeySequence(),
-                    Qt::ShortcutContext default_context = Qt::WindowShortcut);
+class HotkeyRegistry final {
+public:
+    friend class GHotkeysDialog;
 
-/**
- * Returns a QShortcut object whose activated() signal can be connected to other QObjects' slots.
- *
- * @param group  General group this hotkey belongs to (e.g. "Main Window", "Debugger").
- * @param action Name of the action (e.g. "Start Emulation", "Load Image").
- * @param widget Parent widget of the returned QShortcut.
- * @warning If multiple QWidgets' call this function for the same action, the returned QShortcut
- * will be the same. Thus, you shouldn't rely on the caller really being the QShortcut's parent.
- */
-QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
+    explicit HotkeyRegistry();
+    ~HotkeyRegistry();
 
-/**
- * Saves all registered hotkeys to the settings file.
- *
- * @note Each hotkey group will be stored a settings group; For each hotkey inside that group, a
- * settings group will be created to store the key sequence and the hotkey context.
- */
-void SaveHotkeys();
+    /**
+     * Loads hotkeys from the settings file.
+     *
+     * @note Yet unregistered hotkeys which are present in the settings will automatically be
+     *       registered.
+     */
+    void LoadHotkeys();
 
-/**
- * Loads hotkeys from the settings file.
- *
- * @note Yet unregistered hotkeys which are present in the settings will automatically be
- * registered.
- */
-void LoadHotkeys();
+    /**
+     * Saves all registered hotkeys to the settings file.
+     *
+     * @note Each hotkey group will be stored a settings group; For each hotkey inside that group, a
+     *       settings group will be created to store the key sequence and the hotkey context.
+     */
+    void SaveHotkeys();
+
+    /**
+     * Returns a QShortcut object whose activated() signal can be connected to other QObjects'
+     * slots.
+     *
+     * @param group  General group this hotkey belongs to (e.g. "Main Window", "Debugger").
+     * @param action Name of the action (e.g. "Start Emulation", "Load Image").
+     * @param widget Parent widget of the returned QShortcut.
+     * @warning If multiple QWidgets' call this function for the same action, the returned QShortcut
+     *          will be the same. Thus, you shouldn't rely on the caller really being the
+     *          QShortcut's parent.
+     */
+    QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
+
+    /**
+     * Register a hotkey.
+     *
+     * @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger")
+     * @param action Name of the action (e.g. "Start Emulation", "Load Image")
+     * @param default_keyseq Default key sequence to assign if the hotkey wasn't present in the
+     *                       settings file before
+     * @param default_context Default context to assign if the hotkey wasn't present in the settings
+     *                        file before
+     * @warning Both the group and action strings will be displayed in the hotkey settings dialog
+     */
+    void RegisterHotkey(const QString& group, const QString& action,
+                        const QKeySequence& default_keyseq = {},
+                        Qt::ShortcutContext default_context = Qt::WindowShortcut);
+
+private:
+    struct Hotkey {
+        QKeySequence keyseq;
+        QShortcut* shortcut = nullptr;
+        Qt::ShortcutContext context = Qt::WindowShortcut;
+    };
+
+    using HotkeyMap = std::map<QString, Hotkey>;
+    using HotkeyGroupMap = std::map<QString, HotkeyMap>;
+
+    HotkeyGroupMap hotkey_groups;
+};
 
 class GHotkeysDialog : public QWidget {
     Q_OBJECT
@@ -59,6 +82,8 @@ class GHotkeysDialog : public QWidget {
 public:
     explicit GHotkeysDialog(QWidget* parent = nullptr);
 
+    void Populate(const HotkeyRegistry& registry);
+
 private:
     Ui::hotkeys ui;
 };
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index dd71bd7636..17ed62c727 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -208,43 +208,46 @@ void GMainWindow::InitializeRecentFileMenuActions() {
 }
 
 void GMainWindow::InitializeHotkeys() {
-    RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
-    RegisterHotkey("Main Window", "Start Emulation");
-    RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
-    RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
-    RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
-                   Qt::ApplicationShortcut);
-    RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
-                   Qt::ApplicationShortcut);
-    LoadHotkeys();
+    hotkey_registry.RegisterHotkey("Main Window", "Load File", QKeySequence::Open);
+    hotkey_registry.RegisterHotkey("Main Window", "Start Emulation");
+    hotkey_registry.RegisterHotkey("Main Window", "Continue/Pause", QKeySequence(Qt::Key_F4));
+    hotkey_registry.RegisterHotkey("Main Window", "Fullscreen", QKeySequence::FullScreen);
+    hotkey_registry.RegisterHotkey("Main Window", "Exit Fullscreen", QKeySequence(Qt::Key_Escape),
+                                   Qt::ApplicationShortcut);
+    hotkey_registry.RegisterHotkey("Main Window", "Toggle Speed Limit", QKeySequence("CTRL+Z"),
+                                   Qt::ApplicationShortcut);
+    hotkey_registry.LoadHotkeys();
 
-    connect(GetHotkey("Main Window", "Load File", this), &QShortcut::activated, this,
-            &GMainWindow::OnMenuLoadFile);
-    connect(GetHotkey("Main Window", "Start Emulation", this), &QShortcut::activated, this,
-            &GMainWindow::OnStartGame);
-    connect(GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated, this, [&] {
-        if (emulation_running) {
-            if (emu_thread->IsRunning()) {
-                OnPauseGame();
-            } else {
-                OnStartGame();
-            }
-        }
-    });
-    connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activated,
-            ui.action_Fullscreen, &QAction::trigger);
-    connect(GetHotkey("Main Window", "Fullscreen", render_window), &QShortcut::activatedAmbiguously,
-            ui.action_Fullscreen, &QAction::trigger);
-    connect(GetHotkey("Main Window", "Exit Fullscreen", this), &QShortcut::activated, this, [&] {
-        if (emulation_running) {
-            ui.action_Fullscreen->setChecked(false);
-            ToggleFullscreen();
-        }
-    });
-    connect(GetHotkey("Main Window", "Toggle Speed Limit", this), &QShortcut::activated, this, [&] {
-        Settings::values.toggle_framelimit = !Settings::values.toggle_framelimit;
-        UpdateStatusBar();
-    });
+    connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
+            this, &GMainWindow::OnMenuLoadFile);
+    connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this),
+            &QShortcut::activated, this, &GMainWindow::OnStartGame);
+    connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
+            this, [&] {
+                if (emulation_running) {
+                    if (emu_thread->IsRunning()) {
+                        OnPauseGame();
+                    } else {
+                        OnStartGame();
+                    }
+                }
+            });
+    connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
+            &QShortcut::activated, ui.action_Fullscreen, &QAction::trigger);
+    connect(hotkey_registry.GetHotkey("Main Window", "Fullscreen", render_window),
+            &QShortcut::activatedAmbiguously, ui.action_Fullscreen, &QAction::trigger);
+    connect(hotkey_registry.GetHotkey("Main Window", "Exit Fullscreen", this),
+            &QShortcut::activated, this, [&] {
+                if (emulation_running) {
+                    ui.action_Fullscreen->setChecked(false);
+                    ToggleFullscreen();
+                }
+            });
+    connect(hotkey_registry.GetHotkey("Main Window", "Toggle Speed Limit", this),
+            &QShortcut::activated, this, [&] {
+                Settings::values.toggle_framelimit = !Settings::values.toggle_framelimit;
+                UpdateStatusBar();
+            });
 }
 
 void GMainWindow::SetDefaultUIGeometry() {
@@ -323,7 +326,8 @@ void GMainWindow::ConnectMenuEvents() {
     connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
 
     // Fullscreen
-    ui.action_Fullscreen->setShortcut(GetHotkey("Main Window", "Fullscreen", this)->key());
+    ui.action_Fullscreen->setShortcut(
+        hotkey_registry.GetHotkey("Main Window", "Fullscreen", this)->key());
     connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
 
     // Help
@@ -757,7 +761,7 @@ void GMainWindow::ToggleWindowMode() {
 }
 
 void GMainWindow::OnConfigure() {
-    ConfigureDialog configureDialog(this);
+    ConfigureDialog configureDialog(this, hotkey_registry);
     auto old_theme = UISettings::values.theme;
     auto result = configureDialog.exec();
     if (result == QDialog::Accepted) {
@@ -896,7 +900,7 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
     UISettings::values.first_start = false;
 
     game_list->SaveInterfaceLayout();
-    SaveHotkeys();
+    hotkey_registry.SaveHotkeys();
 
     // Shutdown session if the emu thread is active...
     if (emu_thread != nullptr)
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index a60d831b93..6e335b8f80 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -9,6 +9,7 @@
 #include <QTimer>
 #include "core/core.h"
 #include "ui_main.h"
+#include "yuzu/hotkeys.h"
 
 class Config;
 class EmuThread;
@@ -172,6 +173,8 @@ private:
     // stores default icon theme search paths for the platform
     QStringList default_theme_paths;
 
+    HotkeyRegistry hotkey_registry;
+
 protected:
     void dropEvent(QDropEvent* event) override;
     void dragEnterEvent(QDragEnterEvent* event) override;