yuzu: Make hotkeys configurable via the GUI

* Adds a new Hotkeys tab in the Controls group.
* Double-click a Hotkey to rebind it.
This commit is contained in:
Adityarup Laha 2019-02-16 16:19:29 +01:00 committed by fearlessTobi
parent 06ac6460d3
commit 57a4a2ae0f
23 changed files with 426 additions and 208 deletions

View file

@ -31,6 +31,8 @@ add_executable(yuzu
configuration/configure_general.h configuration/configure_general.h
configuration/configure_graphics.cpp configuration/configure_graphics.cpp
configuration/configure_graphics.h configuration/configure_graphics.h
configuration/configure_hotkeys.cpp
configuration/configure_hotkeys.h
configuration/configure_input.cpp configuration/configure_input.cpp
configuration/configure_input.h configuration/configure_input.h
configuration/configure_input_player.cpp configuration/configure_input_player.cpp
@ -78,6 +80,8 @@ add_executable(yuzu
ui_settings.h ui_settings.h
util/limitable_input_dialog.cpp util/limitable_input_dialog.cpp
util/limitable_input_dialog.h util/limitable_input_dialog.h
util/sequence_dialog/sequence_dialog.cpp
util/sequence_dialog/sequence_dialog.h
util/spinbox.cpp util/spinbox.cpp
util/spinbox.h util/spinbox.h
util/util.cpp util/util.cpp
@ -95,6 +99,7 @@ set(UIS
configuration/configure_gamelist.ui configuration/configure_gamelist.ui
configuration/configure_general.ui configuration/configure_general.ui
configuration/configure_graphics.ui configuration/configure_graphics.ui
configuration/configure_hotkeys.ui
configuration/configure_input.ui configuration/configure_input.ui
configuration/configure_input_player.ui configuration/configure_input_player.ui
configuration/configure_input_simple.ui configuration/configure_input_simple.ui
@ -105,7 +110,6 @@ set(UIS
configuration/configure_touchscreen_advanced.ui configuration/configure_touchscreen_advanced.ui
configuration/configure_web.ui configuration/configure_web.ui
compatdb.ui compatdb.ui
hotkeys.ui
loading_screen.ui loading_screen.ui
main.ui main.ui
) )

View file

@ -4,6 +4,7 @@
#include <mutex> #include <mutex>
#include <QDialogButtonBox> #include <QDialogButtonBox>
#include <QHeaderView>
#include <QLabel> #include <QLabel>
#include <QLineEdit> #include <QLineEdit>
#include <QScrollArea> #include <QScrollArea>

View file

@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include <QDialog> #include <QDialog>
#include <QList> #include <QList>
#include <QTreeView>
#include "core/frontend/applets/profile_select.h" #include "core/frontend/applets/profile_select.h"
class GMainWindow; class GMainWindow;
@ -16,7 +17,6 @@ class QLabel;
class QScrollArea; class QScrollArea;
class QStandardItem; class QStandardItem;
class QStandardItemModel; class QStandardItemModel;
class QTreeView;
class QVBoxLayout; class QVBoxLayout;
class QtProfileSelectionDialog final : public QDialog { class QtProfileSelectionDialog final : public QDialog {

View file

@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <array>
#include <QKeySequence>
#include <QSettings> #include <QSettings>
#include "common/file_util.h" #include "common/file_util.h"
#include "configure_input_simple.h" #include "configure_input_simple.h"
@ -9,7 +11,6 @@
#include "core/hle/service/hid/controllers/npad.h" #include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h" #include "input_common/main.h"
#include "yuzu/configuration/config.h" #include "yuzu/configuration/config.h"
#include "yuzu/ui_settings.h"
Config::Config() { Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files. // TODO: Don't hardcode the path; let the frontend decide where to put the config files.
@ -17,7 +18,6 @@ Config::Config() {
FileUtil::CreateFullPath(qt_config_loc); FileUtil::CreateFullPath(qt_config_loc);
qt_config = qt_config =
std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat); std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
Reload(); Reload();
} }
@ -205,6 +205,27 @@ const std::array<int, Settings::NativeKeyboard::NumKeyboardMods> Config::default
Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight, Qt::Key_Control, Qt::Key_Shift, Qt::Key_AltGr, Qt::Key_ApplicationRight,
}; };
// This shouldn't have anything except static initializers (no functions). So
// QKeySequnce(...).toString() is NOT ALLOWED HERE.
// This must be in alphabetical order according to action name as it must have the same order as
// UISetting::values.shortcuts, which is alphabetically ordered.
const std::array<UISettings::Shortcut, 15> Config::default_hotkeys{
{{"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}},
{"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}},
{"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}},
{"Exit yuzu", "Main Window", {"Ctrl+Q", Qt::WindowShortcut}},
{"Exit Fullscreen", "Main Window", {"Esc", Qt::WindowShortcut}},
{"Fullscreen", "Main Window", {"F11", Qt::WindowShortcut}},
{"Increase Speed Limit", "Main Window", {"+", Qt::ApplicationShortcut}},
{"Load Amiibo", "Main Window", {"F2", Qt::ApplicationShortcut}},
{"Load File", "Main Window", {"Ctrl+O", Qt::WindowShortcut}},
{"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}},
{"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}},
{"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}},
{"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}},
{"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}},
{"Change Docked Mode", "Main Window", {"F10", Qt::ApplicationShortcut}}}};
void Config::ReadPlayerValues() { void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) { for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
auto& player = Settings::values.players[p]; auto& player = Settings::values.players[p];
@ -509,20 +530,15 @@ void Config::ReadValues() {
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("Shortcuts"); qt_config->beginGroup("Shortcuts");
QStringList groups = qt_config->childGroups(); for (auto [name, group, shortcut] : default_hotkeys) {
for (auto group : groups) { auto [keyseq, context] = shortcut;
qt_config->beginGroup(group); qt_config->beginGroup(group);
qt_config->beginGroup(name);
QStringList hotkeys = qt_config->childGroups(); UISettings::values.shortcuts.push_back(
for (auto hotkey : hotkeys) { {name,
qt_config->beginGroup(hotkey); group,
UISettings::values.shortcuts.emplace_back(UISettings::Shortcut( {ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}});
group + "/" + hotkey,
UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
ReadSetting("Context").toInt())));
qt_config->endGroup(); qt_config->endGroup();
}
qt_config->endGroup(); qt_config->endGroup();
} }
qt_config->endGroup(); qt_config->endGroup();
@ -760,9 +776,16 @@ void Config::SaveValues() {
qt_config->endGroup(); qt_config->endGroup();
qt_config->beginGroup("Shortcuts"); qt_config->beginGroup("Shortcuts");
for (auto shortcut : UISettings::values.shortcuts) { // Lengths of UISettings::values.shortcuts & default_hotkeys are same.
WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first); // However, their ordering must also be the same.
WriteSetting(shortcut.first + "/Context", shortcut.second.second); for (std::size_t i = 0; i < default_hotkeys.size(); i++) {
auto [name, group, shortcut] = UISettings::values.shortcuts[i];
qt_config->beginGroup(group);
qt_config->beginGroup(name);
WriteSetting("KeySeq", shortcut.first, default_hotkeys[i].shortcut.first);
WriteSetting("Context", shortcut.second, default_hotkeys[i].shortcut.second);
qt_config->endGroup();
qt_config->endGroup();
} }
qt_config->endGroup(); qt_config->endGroup();

View file

@ -9,6 +9,7 @@
#include <string> #include <string>
#include <QVariant> #include <QVariant>
#include "core/settings.h" #include "core/settings.h"
#include "yuzu/ui_settings.h"
class QSettings; class QSettings;
@ -47,6 +48,8 @@ private:
void WriteSetting(const QString& name, const QVariant& value); void WriteSetting(const QString& name, const QVariant& value);
void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
static const std::array<UISettings::Shortcut, 15> default_hotkeys;
std::unique_ptr<QSettings> qt_config; std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc; std::string qt_config_loc;
}; };

View file

@ -7,9 +7,15 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>382</width> <width>382</width>
<height>241</height> <height>650</height>
</rect> </rect>
</property> </property>
<property name="minimumSize">
<size>
<width>0</width>
<height>650</height>
</size>
</property>
<property name="windowTitle"> <property name="windowTitle">
<string>yuzu Configuration</string> <string>yuzu Configuration</string>
</property> </property>
@ -62,6 +68,11 @@
<string>Input</string> <string>Input</string>
</attribute> </attribute>
</widget> </widget>
<widget class="ConfigureHotkeys" name="hotkeysTab">
<attribute name="title">
<string>Hotkeys</string>
</attribute>
</widget>
<widget class="ConfigureGraphics" name="graphicsTab"> <widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title"> <attribute name="title">
<string>Graphics</string> <string>Graphics</string>
@ -150,6 +161,12 @@
<header>configuration/configure_input_simple.h</header> <header>configuration/configure_input_simple.h</header>
<container>1</container> <container>1</container>
</customwidget> </customwidget>
<customwidget>
<class>ConfigureHotkeys</class>
<extends>QWidget</extends>
<header>configuration/configure_hotkeys.h</header>
<container>1</container>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections> <connections>

View file

@ -8,20 +8,22 @@
#include "ui_configure.h" #include "ui_configure.h"
#include "yuzu/configuration/config.h" #include "yuzu/configuration/config.h"
#include "yuzu/configuration/configure_dialog.h" #include "yuzu/configuration/configure_dialog.h"
#include "yuzu/configuration/configure_input_player.h"
#include "yuzu/hotkeys.h" #include "yuzu/hotkeys.h"
ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry) ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry)
: QDialog(parent), ui(new Ui::ConfigureDialog) { : QDialog(parent), registry(registry), ui(new Ui::ConfigureDialog) {
ui->setupUi(this); ui->setupUi(this);
ui->generalTab->PopulateHotkeyList(registry); ui->hotkeysTab->Populate(registry);
this->setConfiguration(); this->setConfiguration();
this->PopulateSelectionList(); this->PopulateSelectionList();
connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, connect(ui->selectorList, &QListWidget::itemSelectionChanged, this,
&ConfigureDialog::UpdateVisibleTabs); &ConfigureDialog::UpdateVisibleTabs);
adjustSize(); adjustSize();
ui->selectorList->setCurrentRow(0); ui->selectorList->setCurrentRow(0);
// Synchronise lists upon initialisation
ui->hotkeysTab->EmitHotkeysChanged();
} }
ConfigureDialog::~ConfigureDialog() = default; ConfigureDialog::~ConfigureDialog() = default;
@ -34,6 +36,7 @@ void ConfigureDialog::applyConfiguration() {
ui->systemTab->applyConfiguration(); ui->systemTab->applyConfiguration();
ui->profileManagerTab->applyConfiguration(); ui->profileManagerTab->applyConfiguration();
ui->inputTab->applyConfiguration(); ui->inputTab->applyConfiguration();
ui->hotkeysTab->applyConfiguration(registry);
ui->graphicsTab->applyConfiguration(); ui->graphicsTab->applyConfiguration();
ui->audioTab->applyConfiguration(); ui->audioTab->applyConfiguration();
ui->debugTab->applyConfiguration(); ui->debugTab->applyConfiguration();
@ -47,7 +50,7 @@ void ConfigureDialog::PopulateSelectionList() {
{{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}},
{tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}},
{tr("Graphics"), {tr("Graphics")}}, {tr("Graphics"), {tr("Graphics")}},
{tr("Controls"), {tr("Input")}}}}; {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}};
for (const auto& entry : items) { for (const auto& entry : items) {
auto* const item = new QListWidgetItem(entry.first); auto* const item = new QListWidgetItem(entry.first);
@ -66,6 +69,7 @@ void ConfigureDialog::UpdateVisibleTabs() {
{tr("System"), ui->systemTab}, {tr("System"), ui->systemTab},
{tr("Profiles"), ui->profileManagerTab}, {tr("Profiles"), ui->profileManagerTab},
{tr("Input"), ui->inputTab}, {tr("Input"), ui->inputTab},
{tr("Hotkeys"), ui->hotkeysTab},
{tr("Graphics"), ui->graphicsTab}, {tr("Graphics"), ui->graphicsTab},
{tr("Audio"), ui->audioTab}, {tr("Audio"), ui->audioTab},
{tr("Debug"), ui->debugTab}, {tr("Debug"), ui->debugTab},

View file

@ -17,7 +17,7 @@ class ConfigureDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry); explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry);
~ConfigureDialog() override; ~ConfigureDialog() override;
void applyConfiguration(); void applyConfiguration();
@ -28,4 +28,5 @@ private:
void PopulateSelectionList(); void PopulateSelectionList();
std::unique_ptr<Ui::ConfigureDialog> ui; std::unique_ptr<Ui::ConfigureDialog> ui;
HotkeyRegistry& registry;
}; };

View file

@ -36,10 +36,6 @@ void ConfigureGeneral::setConfiguration() {
ui->enable_nfc->setChecked(Settings::values.enable_nfc); ui->enable_nfc->setChecked(Settings::values.enable_nfc);
} }
void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
ui->widget->Populate(registry);
}
void ConfigureGeneral::applyConfiguration() { void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked(); UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();

View file

@ -20,7 +20,6 @@ public:
explicit ConfigureGeneral(QWidget* parent = nullptr); explicit ConfigureGeneral(QWidget* parent = nullptr);
~ConfigureGeneral() override; ~ConfigureGeneral() override;
void PopulateHotkeyList(const HotkeyRegistry& registry);
void applyConfiguration(); void applyConfiguration();
private: private:

View file

@ -117,22 +117,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<widget class="QGroupBox" name="HotKeysGroupBox">
<property name="title">
<string>Hotkeys</string>
</property>
<layout class="QHBoxLayout" name="HotKeysHorizontalLayout">
<item>
<layout class="QVBoxLayout" name="HotKeysVerticalLayout">
<item>
<widget class="GHotkeysDialog" name="widget" native="true"/>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
@ -150,14 +134,6 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<customwidgets>
<customwidget>
<class>GHotkeysDialog</class>
<extends>QWidget</extends>
<header>hotkeys.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View file

@ -0,0 +1,121 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QMessageBox>
#include <QStandardItemModel>
#include "core/settings.h"
#include "ui_configure_hotkeys.h"
#include "yuzu/configuration/configure_hotkeys.h"
#include "yuzu/hotkeys.h"
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
ConfigureHotkeys::ConfigureHotkeys(QWidget* parent)
: QWidget(parent), ui(std::make_unique<Ui::ConfigureHotkeys>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
model = new QStandardItemModel(this);
model->setColumnCount(3);
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure);
ui->hotkey_list->setModel(model);
// TODO(Kloen): Make context configurable as well (hiding the column for now)
ui->hotkey_list->hideColumn(2);
ui->hotkey_list->setColumnWidth(0, 200);
ui->hotkey_list->resizeColumnToContents(1);
}
ConfigureHotkeys::~ConfigureHotkeys() = default;
void ConfigureHotkeys::EmitHotkeysChanged() {
emit HotkeysChanged(GetUsedKeyList());
}
QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const {
QList<QKeySequence> list;
for (int r = 0; r < model->rowCount(); r++) {
const QStandardItem* parent = model->item(r, 0);
for (int r2 = 0; r2 < parent->rowCount(); r2++) {
const QStandardItem* keyseq = parent->child(r2, 1);
list << QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText);
}
}
return list;
}
void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) {
for (const auto& group : registry.hotkey_groups) {
auto* parent_item = new QStandardItem(group.first);
parent_item->setEditable(false);
for (const auto& hotkey : group.second) {
auto* action = new QStandardItem(hotkey.first);
auto* keyseq =
new QStandardItem(hotkey.second.keyseq.toString(QKeySequence::NativeText));
action->setEditable(false);
keyseq->setEditable(false);
parent_item->appendRow({action, keyseq});
}
model->appendRow(parent_item);
}
ui->hotkey_list->expandAll();
}
void ConfigureHotkeys::Configure(QModelIndex index) {
if (index.parent() == QModelIndex())
return;
index = index.sibling(index.row(), 1);
auto* model = ui->hotkey_list->model();
auto previous_key = model->data(index);
auto* hotkey_dialog = new SequenceDialog;
int return_code = hotkey_dialog->exec();
auto key_sequence = hotkey_dialog->GetSequence();
if (return_code == QDialog::Rejected || key_sequence.isEmpty())
return;
if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) {
QMessageBox::critical(this, tr("Error in inputted key"),
tr("You're using a key that's already bound."));
} else {
model->setData(index, key_sequence.toString(QKeySequence::NativeText));
EmitHotkeysChanged();
}
}
bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) {
return GetUsedKeyList().contains(key_sequence);
}
void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) {
for (int key_id = 0; key_id < model->rowCount(); key_id++) {
const QStandardItem* parent = model->item(key_id, 0);
for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) {
const QStandardItem* action = parent->child(key_column_id, 0);
const QStandardItem* keyseq = parent->child(key_column_id, 1);
for (auto& [group, sub_actions] : registry.hotkey_groups) {
if (group != parent->text())
continue;
for (auto& [action_name, hotkey] : sub_actions) {
if (action_name != action->text())
continue;
hotkey.keyseq = QKeySequence(keyseq->text());
}
}
}
}
registry.SaveHotkeys();
Settings::Apply();
}
void ConfigureHotkeys::retranslateUi() {
ui->retranslateUi(this);
}

View file

@ -0,0 +1,48 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QWidget>
#include "core/settings.h"
namespace Ui {
class ConfigureHotkeys;
}
class HotkeyRegistry;
class QStandardItemModel;
class ConfigureHotkeys : public QWidget {
Q_OBJECT
public:
explicit ConfigureHotkeys(QWidget* parent = nullptr);
~ConfigureHotkeys() override;
void applyConfiguration(HotkeyRegistry& registry);
void retranslateUi();
void EmitHotkeysChanged();
/**
* Populates the hotkey list widget using data from the provided registry.
* Called everytime the Configure dialog is opened.
* @param registry The HotkeyRegistry whose data is used to populate the list.
*/
void Populate(const HotkeyRegistry& registry);
signals:
void HotkeysChanged(QList<QKeySequence> new_key_list);
private:
void Configure(QModelIndex index);
bool IsUsedKey(QKeySequence key_sequence);
QList<QKeySequence> GetUsedKeyList() const;
std::unique_ptr<Ui::ConfigureHotkeys> ui;
QStandardItemModel* model;
};

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConfigureHotkeys</class>
<widget class="QWidget" name="ConfigureHotkeys">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>363</width>
<height>388</height>
</rect>
</property>
<property name="windowTitle">
<string>Hotkey Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Double-click on a binding to change it.</string>
</property>
</widget>
</item>
<item>
<widget class="QTreeView" name="hotkey_list">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="sortingEnabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <map>
#include <QKeySequence> #include <QKeySequence>
#include <QShortcut> #include <QShortcut>
#include <QTreeWidgetItem> #include <QTreeWidgetItem>
@ -13,47 +12,32 @@
HotkeyRegistry::HotkeyRegistry() = default; HotkeyRegistry::HotkeyRegistry() = default;
HotkeyRegistry::~HotkeyRegistry() = default; HotkeyRegistry::~HotkeyRegistry() = default;
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) {
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 = static_cast<Qt::ShortcutContext>(shortcut.second.second);
}
if (hk.shortcut)
hk.shortcut->setKey(hk.keyseq);
}
}
void HotkeyRegistry::SaveHotkeys() { void HotkeyRegistry::SaveHotkeys() {
UISettings::values.shortcuts.clear(); UISettings::values.shortcuts.clear();
for (const auto& group : hotkey_groups) { for (const auto& group : hotkey_groups) {
for (const auto& hotkey : group.second) { for (const auto& hotkey : group.second) {
UISettings::values.shortcuts.emplace_back( UISettings::values.shortcuts.push_back(
UISettings::Shortcut(group.first + '/' + hotkey.first, {hotkey.first, group.first,
UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), UISettings::ContextualShortcut(hotkey.second.keyseq.toString(),
hotkey.second.context))); hotkey.second.context)});
} }
} }
} }
void HotkeyRegistry::RegisterHotkey(const QString& group, const QString& action, void HotkeyRegistry::LoadHotkeys() {
const QKeySequence& default_keyseq, // Make sure NOT to use a reference here because it would become invalid once we call
Qt::ShortcutContext default_context) { // beginGroup()
auto& hotkey_group = hotkey_groups[group]; for (auto shortcut : UISettings::values.shortcuts) {
if (hotkey_group.find(action) != hotkey_group.end()) { Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name];
return; if (!shortcut.shortcut.first.isEmpty()) {
hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText);
hk.context = static_cast<Qt::ShortcutContext>(shortcut.shortcut.second);
}
if (hk.shortcut) {
hk.shortcut->disconnect();
hk.shortcut->setKey(hk.keyseq);
}
} }
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) { QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action, QWidget* widget) {
@ -65,24 +49,11 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action
return hk.shortcut; return hk.shortcut;
} }
GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) { QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) {
ui.setupUi(this); return hotkey_groups[group][action].keyseq;
} }
void GHotkeysDialog::Populate(const HotkeyRegistry& registry) { Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group,
for (const auto& group : registry.hotkey_groups) { const QString& action) {
QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first)); return hotkey_groups[group][action].context;
for (const auto& hotkey : group.second) {
QStringList columns;
columns << hotkey.first << hotkey.second.keyseq.toString();
QTreeWidgetItem* item = new QTreeWidgetItem(columns);
toplevel_item->addChild(item);
}
ui.treeWidget->addTopLevelItem(toplevel_item);
}
// TODO: Make context configurable as well (hiding the column for now)
ui.treeWidget->setColumnCount(2);
ui.treeWidget->resizeColumnToContents(0);
ui.treeWidget->resizeColumnToContents(1);
} }

View file

@ -5,7 +5,6 @@
#pragma once #pragma once
#include <map> #include <map>
#include "ui_hotkeys.h"
class QDialog; class QDialog;
class QKeySequence; class QKeySequence;
@ -14,7 +13,7 @@ class QShortcut;
class HotkeyRegistry final { class HotkeyRegistry final {
public: public:
friend class GHotkeysDialog; friend class ConfigureHotkeys;
explicit HotkeyRegistry(); explicit HotkeyRegistry();
~HotkeyRegistry(); ~HotkeyRegistry();
@ -49,22 +48,27 @@ public:
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget); QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
/** /**
* Register a hotkey. * Returns a QKeySequence object whose signal can be connected to QAction::setShortcut.
* *
* @param group General group this hotkey belongs to (e.g. "Main Window", "Debugger") * @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 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, QKeySequence GetKeySequence(const QString& group, const QString& action);
const QKeySequence& default_keyseq = {},
Qt::ShortcutContext default_context = Qt::WindowShortcut); /**
* Returns a Qt::ShortcutContext object who can be connected to other
* QAction::setShortcutContext.
*
* @param group General group this shortcut context belongs to (e.g. "Main Window",
* "Debugger").
* @param action Name of the action (e.g. "Start Emulation", "Load Image").
*/
Qt::ShortcutContext GetShortcutContext(const QString& group, const QString& action);
private: private:
struct Hotkey { struct Hotkey {
Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {}
QKeySequence keyseq; QKeySequence keyseq;
QShortcut* shortcut = nullptr; QShortcut* shortcut = nullptr;
Qt::ShortcutContext context = Qt::WindowShortcut; Qt::ShortcutContext context = Qt::WindowShortcut;
@ -75,15 +79,3 @@ private:
HotkeyGroupMap hotkey_groups; HotkeyGroupMap hotkey_groups;
}; };
class GHotkeysDialog : public QWidget {
Q_OBJECT
public:
explicit GHotkeysDialog(QWidget* parent = nullptr);
void Populate(const HotkeyRegistry& registry);
private:
Ui::hotkeys ui;
};

View file

@ -1,46 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>hotkeys</class>
<widget class="QWidget" name="hotkeys">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>363</width>
<height>388</height>
</rect>
</property>
<property name="windowTitle">
<string>Hotkey Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTreeWidget" name="treeWidget">
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectItems</enum>
</property>
<property name="headerHidden">
<bool>false</bool>
</property>
<column>
<property name="text">
<string>Action</string>
</property>
</column>
<column>
<property name="text">
<string>Hotkey</string>
</property>
</column>
<column>
<property name="text">
<string>Context</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -511,33 +511,34 @@ void GMainWindow::InitializeRecentFileMenuActions() {
} }
void GMainWindow::InitializeHotkeys() { void GMainWindow::InitializeHotkeys() {
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", "Restart", QKeySequence(Qt::Key_F5));
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.RegisterHotkey("Main Window", "Increase Speed Limit", QKeySequence("+"),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Decrease Speed Limit", QKeySequence("-"),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2),
Qt::ApplicationShortcut);
hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot",
QKeySequence(QKeySequence::Print));
hotkey_registry.RegisterHotkey("Main Window", "Change Docked Mode", QKeySequence(Qt::Key_F10));
hotkey_registry.LoadHotkeys(); hotkey_registry.LoadHotkeys();
ui.action_Load_File->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Load File"));
ui.action_Load_File->setShortcutContext(
hotkey_registry.GetShortcutContext("Main Window", "Load File"));
ui.action_Exit->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Exit yuzu"));
ui.action_Exit->setShortcutContext(
hotkey_registry.GetShortcutContext("Main Window", "Exit yuzu"));
ui.action_Stop->setShortcut(hotkey_registry.GetKeySequence("Main Window", "Stop Emulation"));
ui.action_Stop->setShortcutContext(
hotkey_registry.GetShortcutContext("Main Window", "Stop Emulation"));
ui.action_Show_Filter_Bar->setShortcut(
hotkey_registry.GetKeySequence("Main Window", "Toggle Filter Bar"));
ui.action_Show_Filter_Bar->setShortcutContext(
hotkey_registry.GetShortcutContext("Main Window", "Toggle Filter Bar"));
ui.action_Show_Status_Bar->setShortcut(
hotkey_registry.GetKeySequence("Main Window", "Toggle Status Bar"));
ui.action_Show_Status_Bar->setShortcutContext(
hotkey_registry.GetShortcutContext("Main Window", "Toggle Status Bar"));
connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated, connect(hotkey_registry.GetHotkey("Main Window", "Load File", this), &QShortcut::activated,
this, &GMainWindow::OnMenuLoadFile); this, &GMainWindow::OnMenuLoadFile);
connect(hotkey_registry.GetHotkey("Main Window", "Start Emulation", this), connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause Emulation", this),
&QShortcut::activated, this, &GMainWindow::OnStartGame); &QShortcut::activated, this, [&] {
connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause", this), &QShortcut::activated,
this, [&] {
if (emulation_running) { if (emulation_running) {
if (emu_thread->IsRunning()) { if (emu_thread->IsRunning()) {
OnPauseGame(); OnPauseGame();
@ -546,8 +547,8 @@ void GMainWindow::InitializeHotkeys() {
} }
} }
}); });
connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this, connect(hotkey_registry.GetHotkey("Main Window", "Restart Emulation", this),
[this] { &QShortcut::activated, this, [this] {
if (!Core::System::GetInstance().IsPoweredOn()) if (!Core::System::GetInstance().IsPoweredOn())
return; return;
BootGame(QString(game_path)); BootGame(QString(game_path));
@ -692,7 +693,6 @@ void GMainWindow::ConnectMenuEvents() {
&GMainWindow::ToggleWindowMode); &GMainWindow::ToggleWindowMode);
connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this,
&GMainWindow::OnDisplayTitleBars); &GMainWindow::OnDisplayTitleBars);
ui.action_Show_Filter_Bar->setShortcut(tr("CTRL+F"));
connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar); connect(ui.action_Show_Filter_Bar, &QAction::triggered, this, &GMainWindow::OnToggleFilterBar);
connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); connect(ui.action_Show_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible);
@ -1624,6 +1624,7 @@ void GMainWindow::OnConfigure() {
auto result = configureDialog.exec(); auto result = configureDialog.exec();
if (result == QDialog::Accepted) { if (result == QDialog::Accepted) {
configureDialog.applyConfiguration(); configureDialog.applyConfiguration();
InitializeHotkeys();
if (UISettings::values.theme != old_theme) if (UISettings::values.theme != old_theme)
UpdateUITheme(); UpdateUITheme();
if (UISettings::values.enable_discord_presence != old_discord_presence) if (UISettings::values.enable_discord_presence != old_discord_presence)

View file

@ -120,7 +120,6 @@ private:
void InitializeWidgets(); void InitializeWidgets();
void InitializeDebugWidgets(); void InitializeDebugWidgets();
void InitializeRecentFileMenuActions(); void InitializeRecentFileMenuActions();
void InitializeHotkeys();
void SetDefaultUIGeometry(); void SetDefaultUIGeometry();
void RestoreUIState(); void RestoreUIState();
@ -195,6 +194,7 @@ private slots:
void OnAbout(); void OnAbout();
void OnToggleFilterBar(); void OnToggleFilterBar();
void OnDisplayTitleBars(bool); void OnDisplayTitleBars(bool);
void InitializeHotkeys();
void ToggleFullscreen(); void ToggleFullscreen();
void ShowFullscreen(); void ShowFullscreen();
void HideFullscreen(); void HideFullscreen();

View file

@ -12,5 +12,4 @@ const Themes themes{{
}}; }};
Values values = {}; Values values = {};
} // namespace UISettings } // namespace UISettings

View file

@ -15,7 +15,12 @@
namespace UISettings { namespace UISettings {
using ContextualShortcut = std::pair<QString, int>; using ContextualShortcut = std::pair<QString, int>;
using Shortcut = std::pair<QString, ContextualShortcut>;
struct Shortcut {
QString name;
QString group;
ContextualShortcut shortcut;
};
using Themes = std::array<std::pair<const char*, const char*>, 2>; using Themes = std::array<std::pair<const char*, const char*>, 2>;
extern const Themes themes; extern const Themes themes;

View file

@ -0,0 +1,37 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDialogButtonBox>
#include <QKeySequenceEdit>
#include <QVBoxLayout>
#include "yuzu/util/sequence_dialog/sequence_dialog.h"
SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) {
setWindowTitle(tr("Enter a hotkey"));
auto* layout = new QVBoxLayout(this);
key_sequence = new QKeySequenceEdit;
layout->addWidget(key_sequence);
auto* buttons =
new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal);
buttons->setCenterButtons(true);
layout->addWidget(buttons);
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
}
SequenceDialog::~SequenceDialog() = default;
QKeySequence SequenceDialog::GetSequence() const {
// Only the first key is returned. The other 3, if present, are ignored.
return QKeySequence(key_sequence->keySequence()[0]);
}
bool SequenceDialog::focusNextPrevChild(bool next) {
return false;
}
void SequenceDialog::closeEvent(QCloseEvent*) {
reject();
}

View file

@ -0,0 +1,24 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <QDialog>
class QKeySequenceEdit;
class SequenceDialog : public QDialog {
Q_OBJECT
public:
explicit SequenceDialog(QWidget* parent = nullptr);
~SequenceDialog() override;
QKeySequence GetSequence() const;
void closeEvent(QCloseEvent*) override;
private:
QKeySequenceEdit* key_sequence;
bool focusNextPrevChild(bool next) override;
};