diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 9eab29b3e..0cc4d1c68 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -41,6 +41,8 @@ add_executable(citra-qt configuration/configure_general.h configuration/configure_graphics.cpp configuration/configure_graphics.h + configuration/configure_hotkeys.cpp + configuration/configure_hotkeys.h configuration/configure_input.cpp configuration/configure_input.h configuration/configure_motion_touch.cpp @@ -109,8 +111,10 @@ add_executable(citra-qt updater/updater.cpp updater/updater.h updater/updater_p.h - util/clickable_label.h util/clickable_label.cpp + util/clickable_label.h + util/sequence_dialog/sequence_dialog.cpp + util/sequence_dialog/sequence_dialog.h util/spinbox.cpp util/spinbox.h util/util.cpp @@ -126,6 +130,7 @@ set(UIS configuration/configure_debug.ui configuration/configure_general.ui configuration/configure_graphics.ui + configuration/configure_hotkeys.ui configuration/configure_input.ui configuration/configure_motion_touch.ui configuration/configure_system.ui @@ -140,7 +145,6 @@ set(UIS multiplayer/moderation_dialog.ui aboutdialog.ui cheats.ui - hotkeys.ui main.ui compatdb.ui ) diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 51510291c..9fe88e892 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -3,10 +3,11 @@ // Refer to the license.txt file included. #include +#include #include +#include #include #include "citra_qt/configuration/config.h" -#include "citra_qt/ui_settings.h" #include "common/file_util.h" #include "core/hle/service/service.h" #include "input_common/main.h" @@ -19,7 +20,6 @@ Config::Config() { FileUtil::CreateFullPath(qt_config_loc); qt_config = std::make_unique(QString::fromStdString(qt_config_loc), QSettings::IniFormat); - Reload(); } @@ -50,6 +50,31 @@ const std::array, Settings::NativeAnalog::NumAnalogs> Config: }, }}; +// 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 Config::default_hotkeys{ + {{"Advance Frame", "Main Window", {"\\", Qt::ApplicationShortcut}}, + {"Capture Screenshot", "Main Window", {"Ctrl+P", Qt::ApplicationShortcut}}, + {"Continue/Pause Emulation", "Main Window", {"F4", Qt::WindowShortcut}}, + {"Decrease Speed Limit", "Main Window", {"-", Qt::ApplicationShortcut}}, + {"Exit Citra", "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}}, + {"Remove Amiibo", "Main Window", {"F3", Qt::ApplicationShortcut}}, + {"Restart Emulation", "Main Window", {"F6", Qt::WindowShortcut}}, + {"Stop Emulation", "Main Window", {"F5", Qt::WindowShortcut}}, + {"Swap Screens", "Main Window", {"F9", Qt::WindowShortcut}}, + {"Toggle Filter Bar", "Main Window", {"Ctrl+F", Qt::WindowShortcut}}, + {"Toggle Frame Advancing", "Main Window", {"Ctrl+A", Qt::ApplicationShortcut}}, + {"Toggle Screen Layout", "Main Window", {"F10", Qt::WindowShortcut}}, + {"Toggle Speed Limit", "Main Window", {"Ctrl+Z", Qt::ApplicationShortcut}}, + {"Toggle Status Bar", "Main Window", {"Ctrl+S", Qt::WindowShortcut}}}}; + void Config::ReadValues() { qt_config->beginGroup("Controls"); @@ -318,20 +343,15 @@ void Config::ReadValues() { qt_config->endGroup(); qt_config->beginGroup("Shortcuts"); - QStringList groups = qt_config->childGroups(); - for (auto group : groups) { + for (auto [name, group, shortcut] : default_hotkeys) { + auto [keyseq, context] = shortcut; qt_config->beginGroup(group); - - QStringList hotkeys = qt_config->childGroups(); - for (auto hotkey : hotkeys) { - qt_config->beginGroup(hotkey); - UISettings::values.shortcuts.emplace_back(UISettings::Shortcut( - group + "/" + hotkey, - UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(), - ReadSetting("Context").toInt()))); - qt_config->endGroup(); - } - + qt_config->beginGroup(name); + UISettings::values.shortcuts.push_back( + {name, + group, + {ReadSetting("KeySeq", keyseq).toString(), ReadSetting("Context", context).toInt()}}); + qt_config->endGroup(); qt_config->endGroup(); } qt_config->endGroup(); @@ -563,9 +583,16 @@ void Config::SaveValues() { qt_config->endGroup(); qt_config->beginGroup("Shortcuts"); - for (auto shortcut : UISettings::values.shortcuts) { - WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first); - WriteSetting(shortcut.first + "/Context", shortcut.second.second); + // Lengths of UISettings::values.shortcuts & default_hotkeys are same. + // However, their ordering must also be the same. + 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(); diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h index fdb161c23..70aab4f8c 100644 --- a/src/citra_qt/configuration/config.h +++ b/src/citra_qt/configuration/config.h @@ -8,6 +8,7 @@ #include #include #include +#include "citra_qt/ui_settings.h" #include "core/settings.h" class QSettings; @@ -31,6 +32,8 @@ private: void WriteSetting(const QString& name, const QVariant& value); void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value); + static const std::array default_hotkeys; + std::unique_ptr qt_config; std::string qt_config_loc; }; diff --git a/src/citra_qt/configuration/configure.ui b/src/citra_qt/configuration/configure.ui index 70796a035..8b625d8c3 100644 --- a/src/citra_qt/configuration/configure.ui +++ b/src/citra_qt/configuration/configure.ui @@ -38,6 +38,11 @@ Input + + + Hotkeys + + Graphics @@ -118,6 +123,12 @@
configuration/configure_input.h
1 + + ConfigureHotkeys + QWidget +
configuration/configure_hotkeys.h
+ 1 +
ConfigureGraphics QWidget diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 3527b8589..a0ef52d2e 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -10,18 +10,27 @@ #include "core/settings.h" #include "ui_configure.h" -ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry) - : QDialog(parent), ui(new Ui::ConfigureDialog) { +ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry) + : QDialog(parent), registry(registry), ui(new Ui::ConfigureDialog) { ui->setupUi(this); - ui->generalTab->PopulateHotkeyList(registry); + ui->hotkeysTab->Populate(registry); + this->PopulateSelectionList(); connect(ui->uiTab, &ConfigureUi::languageChanged, this, &ConfigureDialog::onLanguageChanged); connect(ui->selectorList, &QListWidget::itemSelectionChanged, this, &ConfigureDialog::UpdateVisibleTabs); - adjustSize(); - ui->selectorList->setCurrentRow(0); + + // Set up used key list synchronisation + connect(ui->inputTab, &ConfigureInput::InputKeysChanged, ui->hotkeysTab, + &ConfigureHotkeys::OnInputKeysChanged); + connect(ui->hotkeysTab, &ConfigureHotkeys::HotkeysChanged, ui->inputTab, + &ConfigureInput::OnHotkeysChanged); + + // Synchronise lists upon initialisation + ui->inputTab->EmitInputKeysChanged(); + ui->hotkeysTab->EmitHotkeysChanged(); } ConfigureDialog::~ConfigureDialog() = default; @@ -43,6 +52,7 @@ void ConfigureDialog::applyConfiguration() { ui->systemTab->applyConfiguration(); ui->inputTab->applyConfiguration(); ui->inputTab->ApplyProfile(); + ui->hotkeysTab->applyConfiguration(registry); ui->graphicsTab->applyConfiguration(); ui->audioTab->applyConfiguration(); ui->cameraTab->applyConfiguration(); @@ -61,7 +71,7 @@ void ConfigureDialog::PopulateSelectionList() { {QT_TR_NOOP("General"), QT_TR_NOOP("Web"), QT_TR_NOOP("Debug"), QT_TR_NOOP("UI")}}, {tr("System"), {QT_TR_NOOP("System"), QT_TR_NOOP("Audio"), QT_TR_NOOP("Camera")}}, {tr("Graphics"), {QT_TR_NOOP("Graphics")}}, - {tr("Controls"), {QT_TR_NOOP("Input")}}}}; + {tr("Controls"), {QT_TR_NOOP("Input"), QT_TR_NOOP("Hotkeys")}}}}; for (const auto& entry : items) { auto* item = new QListWidgetItem(entry.first); @@ -91,6 +101,7 @@ void ConfigureDialog::retranslateUi() { ui->generalTab->retranslateUi(); ui->systemTab->retranslateUi(); ui->inputTab->retranslateUi(); + ui->hotkeysTab->retranslateUi(); ui->graphicsTab->retranslateUi(); ui->audioTab->retranslateUi(); ui->cameraTab->retranslateUi(); @@ -105,9 +116,11 @@ void ConfigureDialog::UpdateVisibleTabs() { return; const QHash widgets = { - {"General", ui->generalTab}, {"System", ui->systemTab}, {"Input", ui->inputTab}, - {"Graphics", ui->graphicsTab}, {"Audio", ui->audioTab}, {"Camera", ui->cameraTab}, - {"Debug", ui->debugTab}, {"Web", ui->webTab}, {"UI", ui->uiTab}}; + {"General", ui->generalTab}, {"System", ui->systemTab}, + {"Input", ui->inputTab}, {"Hotkeys", ui->hotkeysTab}, + {"Graphics", ui->graphicsTab}, {"Audio", ui->audioTab}, + {"Camera", ui->cameraTab}, {"Debug", ui->debugTab}, + {"Web", ui->webTab}, {"UI", ui->uiTab}}; ui->tabWidget->clear(); diff --git a/src/citra_qt/configuration/configure_dialog.h b/src/citra_qt/configuration/configure_dialog.h index b60b4207b..c902bf702 100644 --- a/src/citra_qt/configuration/configure_dialog.h +++ b/src/citra_qt/configuration/configure_dialog.h @@ -17,7 +17,7 @@ class ConfigureDialog : public QDialog { Q_OBJECT public: - explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry); + explicit ConfigureDialog(QWidget* parent, HotkeyRegistry& registry); ~ConfigureDialog() override; void applyConfiguration(); @@ -35,4 +35,5 @@ private: void retranslateUi(); std::unique_ptr ui; + HotkeyRegistry& registry; }; diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp index cd47b135e..98cd8f97f 100644 --- a/src/citra_qt/configuration/configure_general.cpp +++ b/src/citra_qt/configuration/configure_general.cpp @@ -32,10 +32,6 @@ void ConfigureGeneral::setConfiguration() { ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1); } -void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { - ui->hotkeysDialog->Populate(registry); -} - void ConfigureGeneral::ResetDefaults() { QMessageBox::StandardButton answer = QMessageBox::question( this, tr("Citra"), @@ -60,5 +56,4 @@ void ConfigureGeneral::applyConfiguration() { void ConfigureGeneral::retranslateUi() { ui->retranslateUi(this); - ui->hotkeysDialog->retranslateUi(); } diff --git a/src/citra_qt/configuration/configure_general.h b/src/citra_qt/configuration/configure_general.h index 9f518155b..f7b2209b0 100644 --- a/src/citra_qt/configuration/configure_general.h +++ b/src/citra_qt/configuration/configure_general.h @@ -20,7 +20,6 @@ public: explicit ConfigureGeneral(QWidget* parent = nullptr); ~ConfigureGeneral() override; - void PopulateHotkeyList(const HotkeyRegistry& registry); void ResetDefaults(); void applyConfiguration(); void retranslateUi(); diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui index 5979dd95b..f33fb9d98 100644 --- a/src/citra_qt/configuration/configure_general.ui +++ b/src/citra_qt/configuration/configure_general.ui @@ -7,7 +7,7 @@ 0 0 345 - 504 + 357 @@ -21,17 +21,13 @@ General - + - - - - - Confirm exit while emulation is running - - - - + + + Confirm exit while emulation is running + +
@@ -41,24 +37,20 @@ Updates - + - - - - - Check for updates on start - - - - - - - Silently auto update after closing - - - - + + + Check for updates on start + + + + + + + Silently auto update after closing + + @@ -68,81 +60,57 @@ Emulation - - - - - - - - - Region: - - - - - - - - Auto-select - - - - - JPN - - - - - USA - - - - - EUR - - - - - AUS - - - - - CHN - - - - - KOR - - - - - TWN - - - - - - - + + + + + Region: + + - - - - - - - Hotkeys - - - - + + - + + Auto-select + - + + + JPN + + + + + USA + + + + + EUR + + + + + AUS + + + + + CHN + + + + + KOR + + + + + TWN + + + @@ -154,18 +122,23 @@ + + + + Qt::Vertical + + + + 20 + 40 + + + + - - - GHotkeysDialog - QWidget -
hotkeys.h
- 1 -
-
diff --git a/src/citra_qt/configuration/configure_hotkeys.cpp b/src/citra_qt/configuration/configure_hotkeys.cpp new file mode 100644 index 000000000..aae212b5d --- /dev/null +++ b/src/citra_qt/configuration/configure_hotkeys.cpp @@ -0,0 +1,125 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include "citra_qt/configuration/configure_hotkeys.h" +#include "citra_qt/hotkeys.h" +#include "citra_qt/util/sequence_dialog/sequence_dialog.h" +#include "core/settings.h" +#include "ui_configure_hotkeys.h" + +ConfigureHotkeys::ConfigureHotkeys(QWidget* parent) + : QWidget(parent), ui(std::make_unique()) { + 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 ConfigureHotkeys::GetUsedKeyList() { + QList list; + for (int r = 0; r < model->rowCount(); r++) { + QStandardItem* parent = model->item(r, 0); + for (int r2 = 0; r2 < parent->rowCount(); r2++) { + 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) { + QStandardItem* parent_item = new QStandardItem(group.first); + parent_item->setEditable(false); + for (const auto& hotkey : group.second) { + QStandardItem* action = new QStandardItem(hotkey.first); + QStandardItem* 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::OnInputKeysChanged(QList new_key_list) { + input_keys_list = new_key_list; +} + +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 input_keys_list.contains(key_sequence) || GetUsedKeyList().contains(key_sequence); +} + +void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) { + for (int key_id = 0; key_id < model->rowCount(); key_id++) { + QStandardItem* parent = model->item(key_id, 0); + for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { + QStandardItem* action = parent->child(key_column_id, 0); + 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); +} diff --git a/src/citra_qt/configuration/configure_hotkeys.h b/src/citra_qt/configuration/configure_hotkeys.h new file mode 100644 index 000000000..bd2c1542c --- /dev/null +++ b/src/citra_qt/configuration/configure_hotkeys.h @@ -0,0 +1,58 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#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(); + + 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); + +public slots: + void OnInputKeysChanged(QList new_key_list); + +signals: + void HotkeysChanged(QList new_key_list); + +private: + void Configure(QModelIndex index); + bool IsUsedKey(QKeySequence key_sequence); + QList GetUsedKeyList(); + + std::unique_ptr ui; + + /** + * List of keyboard keys currently registered to any of the 3DS inputs. + * These can't be bound to any hotkey. + * Synchronised with ConfigureInput via signal-slot. + */ + QList input_keys_list; + + QStandardItemModel* model; +}; diff --git a/src/citra_qt/configuration/configure_hotkeys.ui b/src/citra_qt/configuration/configure_hotkeys.ui new file mode 100644 index 000000000..56a2e53fa --- /dev/null +++ b/src/citra_qt/configuration/configure_hotkeys.ui @@ -0,0 +1,42 @@ + + + ConfigureHotkeys + + + + 0 + 0 + 363 + 388 + + + + Hotkey Settings + + + + + + + + Double-click on a binding to change it. + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + + + + + + + + diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index c0df64bc9..da5f64271 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -276,6 +276,30 @@ void ConfigureInput::ApplyProfile() { Settings::values.current_input_profile_index = ui->profile->currentIndex(); } +void ConfigureInput::EmitInputKeysChanged() { + emit InputKeysChanged(GetUsedKeyboardKeys()); +} + +void ConfigureInput::OnHotkeysChanged(QList new_key_list) { + hotkey_list = new_key_list; +} + +QList ConfigureInput::GetUsedKeyboardKeys() { + QList list; + for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { + auto button_param = buttons_param[button]; + + if (button_param.Get("engine", "") == "keyboard") { + list << QKeySequence(button_param.Get("code", 0)); + } + } + + // TODO(adityaruplaha): Add home button to list when we finally emulate it + // Button ID of home button is 14: Referred from citra_qt/configuration/config.cpp + list.removeOne(list.indexOf(QKeySequence(buttons_param[14].Get("code", 0)))); + return list; +} + void ConfigureInput::loadConfiguration() { std::transform(Settings::values.current_input_profile.buttons.begin(), Settings::values.current_input_profile.buttons.end(), buttons_param.begin(), @@ -332,11 +356,14 @@ void ConfigureInput::updateButtonLabels() { } analog_map_stick[analog_id]->setText(tr("Set Analog Stick")); } + + EmitInputKeysChanged(); } void ConfigureInput::handleClick(QPushButton* button, std::function new_input_setter, InputCommon::Polling::DeviceType type) { + previous_key_code = QKeySequence(button->text())[0]; button->setText(tr("[press key]")); button->setFocus(); @@ -378,16 +405,26 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) { if (!input_setter || !event) return; - if (event->key() != Qt::Key_Escape) { + if (event->key() != Qt::Key_Escape && event->key() != previous_key_code) { if (want_keyboard_keys) { + // Check if key is already bound + if (hotkey_list.contains(QKeySequence(event->key())) || + GetUsedKeyboardKeys().contains(QKeySequence(event->key()))) { + setPollingResult({}, true); + QMessageBox::critical(this, tr("Error!"), + tr("You're using a key that's already bound.")); + return; + } setPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())}, false); } else { - // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop polling + // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop + // polling return; } } setPollingResult({}, true); + previous_key_code = 0; } void ConfigureInput::retranslateUi() { diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index 1e55fd272..36e44d52b 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "common/param_package.h" #include "core/settings.h" @@ -38,9 +39,15 @@ public: /// Load configuration settings. void loadConfiguration(); + void EmitInputKeysChanged(); - // Save the current input profile index + /// Save the current input profile index void ApplyProfile(); +public slots: + void OnHotkeysChanged(QList new_key_list); + +signals: + void InputKeysChanged(QList new_key_list); private: std::unique_ptr ui; @@ -72,10 +79,20 @@ private: std::vector> device_pollers; + /** + * List of keys currently registered to hotkeys. + * These can't be bound to any input key. + * Synchronised with ConfigureHotkeys via signal-slot. + */ + QList hotkey_list; + /// A flag to indicate if keyboard keys are okay when configuring an input. If this is false, /// keyboard events are ignored. bool want_keyboard_keys = false; + /// Generates list of all used keys + QList GetUsedKeyboardKeys(); + /// Restore all buttons to their default values. void restoreDefaults(); /// Clear all input configuration @@ -89,6 +106,9 @@ private: std::function new_input_setter, InputCommon::Polling::DeviceType type); + /// The key code of the previous state of the key being currently bound. + int previous_key_code; + /// Finish polling and configure input using the input_setter void setPollingResult(const Common::ParamPackage& params, bool abort); diff --git a/src/citra_qt/hotkeys.cpp b/src/citra_qt/hotkeys.cpp index 5c74f694f..5542f6dd4 100644 --- a/src/citra_qt/hotkeys.cpp +++ b/src/citra_qt/hotkeys.cpp @@ -2,7 +2,6 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include #include #include #include @@ -12,47 +11,32 @@ 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(shortcut.second.second); - } - if (hk.shortcut) - hk.shortcut->setKey(hk.keyseq); - } -} - 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))); + UISettings::values.shortcuts.push_back( + {hotkey.first, group.first, + UISettings::ContextualShortcut(hotkey.second.keyseq.toString(), + hotkey.second.context)}); } } } -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; +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) { + Hotkey& hk = hotkey_groups[shortcut.group][shortcut.name]; + if (!shortcut.shortcut.first.isEmpty()) { + hk.keyseq = QKeySequence::fromString(shortcut.shortcut.first, QKeySequence::NativeText); + hk.context = static_cast(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) { @@ -64,28 +48,13 @@ QShortcut* HotkeyRegistry::GetHotkey(const QString& group, const QString& action return hk.shortcut; } -GHotkeysDialog::GHotkeysDialog(QWidget* parent) : QWidget(parent) { - ui.setupUi(this); +QKeySequence HotkeyRegistry::GetKeySequence(const QString& group, const QString& action) { + Hotkey& hk = hotkey_groups[group][action]; + return hk.keyseq; } -void GHotkeysDialog::Populate(const HotkeyRegistry& registry) { - for (const auto& group : registry.hotkey_groups) { - QTreeWidgetItem* toplevel_item = new QTreeWidgetItem(QStringList(group.first)); - 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); -} - -void GHotkeysDialog::retranslateUi() { - ui.retranslateUi(this); +Qt::ShortcutContext HotkeyRegistry::GetShortcutContext(const QString& group, + const QString& action) { + Hotkey& hk = hotkey_groups[group][action]; + return hk.context; } diff --git a/src/citra_qt/hotkeys.h b/src/citra_qt/hotkeys.h index 045cb98c6..4f526dc7e 100644 --- a/src/citra_qt/hotkeys.h +++ b/src/citra_qt/hotkeys.h @@ -5,7 +5,6 @@ #pragma once #include -#include "ui_hotkeys.h" class QDialog; class QKeySequence; @@ -14,7 +13,7 @@ class QShortcut; class HotkeyRegistry final { public: - friend class GHotkeysDialog; + friend class ConfigureHotkeys; explicit HotkeyRegistry(); ~HotkeyRegistry(); @@ -49,22 +48,27 @@ public: 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 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 + * @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"). */ - void RegisterHotkey(const QString& group, const QString& action, - const QKeySequence& default_keyseq = {}, - Qt::ShortcutContext default_context = Qt::WindowShortcut); + QKeySequence GetKeySequence(const QString& group, const QString& action); + + /** + * 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: struct Hotkey { + Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {} + QKeySequence keyseq; QShortcut* shortcut = nullptr; Qt::ShortcutContext context = Qt::WindowShortcut; @@ -75,16 +79,3 @@ private: HotkeyGroupMap hotkey_groups; }; - -class GHotkeysDialog : public QWidget { - Q_OBJECT - -public: - explicit GHotkeysDialog(QWidget* parent = nullptr); - void retranslateUi(); - - void Populate(const HotkeyRegistry& registry); - -private: - Ui::hotkeys ui; -}; diff --git a/src/citra_qt/hotkeys.ui b/src/citra_qt/hotkeys.ui deleted file mode 100644 index 050fe064e..000000000 --- a/src/citra_qt/hotkeys.ui +++ /dev/null @@ -1,46 +0,0 @@ - - - hotkeys - - - - 0 - 0 - 363 - 388 - - - - Hotkey Settings - - - - - - QAbstractItemView::SelectItems - - - false - - - - Action - - - - - Hotkey - - - - - Context - - - - - - - - - diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 5c90ed92e..5fb010f6d 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -344,40 +344,35 @@ void GMainWindow::InitializeRecentFileMenuActions() { } 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", "Swap Screens", QKeySequence(Qt::Key_F9)); - hotkey_registry.RegisterHotkey("Main Window", "Toggle Screen Layout", - QKeySequence(Qt::Key_F10)); - 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", "Toggle Frame Advancing", QKeySequence("CTRL+A"), - Qt::ApplicationShortcut); - hotkey_registry.RegisterHotkey("Main Window", "Advance Frame", QKeySequence(Qt::Key_Backslash), - Qt::ApplicationShortcut); - hotkey_registry.RegisterHotkey("Main Window", "Load Amiibo", QKeySequence(Qt::Key_F2), - Qt::ApplicationShortcut); - hotkey_registry.RegisterHotkey("Main Window", "Remove Amiibo", QKeySequence(Qt::Key_F3), - Qt::ApplicationShortcut); - hotkey_registry.RegisterHotkey("Main Window", "Capture Screenshot", QKeySequence(tr("CTRL+P"))); - 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 Citra")); + ui.action_Exit->setShortcutContext( + hotkey_registry.GetShortcutContext("Main Window", "Exit Citra")); + + 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, 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, [&] { + + connect(hotkey_registry.GetHotkey("Main Window", "Continue/Pause Emulation", this), + &QShortcut::activated, this, [&] { if (emulation_running) { if (emu_thread->IsRunning()) { OnPauseGame(); @@ -386,8 +381,8 @@ void GMainWindow::InitializeHotkeys() { } } }); - connect(hotkey_registry.GetHotkey("Main Window", "Restart", this), &QShortcut::activated, this, - [this] { + connect(hotkey_registry.GetHotkey("Main Window", "Restart Emulation", this), + &QShortcut::activated, this, [this] { if (!Core::System::GetInstance().IsPoweredOn()) return; BootGame(QString(game_path)); @@ -546,7 +541,6 @@ void GMainWindow::ConnectMenuEvents() { &GMainWindow::ToggleWindowMode); connect(ui.action_Display_Dock_Widget_Headers, &QAction::triggered, this, &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_Status_Bar, &QAction::triggered, statusBar(), &QStatusBar::setVisible); @@ -1332,6 +1326,7 @@ void GMainWindow::OnConfigure() { auto result = configureDialog.exec(); if (result == QDialog::Accepted) { configureDialog.applyConfiguration(); + InitializeHotkeys(); if (UISettings::values.theme != old_theme) UpdateUITheme(); if (UISettings::values.enable_discord_presence != old_discord_presence) diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h index 0d561508a..720bdedb6 100644 --- a/src/citra_qt/main.h +++ b/src/citra_qt/main.h @@ -96,7 +96,6 @@ private: void InitializeWidgets(); void InitializeDebugWidgets(); void InitializeRecentFileMenuActions(); - void InitializeHotkeys(); void SetDefaultUIGeometry(); void SyncMenuUISettings(); @@ -171,6 +170,7 @@ private slots: void OnOpenCitraFolder(); void OnToggleFilterBar(); void OnDisplayTitleBars(bool); + void InitializeHotkeys(); void ToggleFullscreen(); void ChangeScreenLayout(); void ToggleScreenLayout(); diff --git a/src/citra_qt/ui_settings.cpp b/src/citra_qt/ui_settings.cpp index b27fc7661..39156fb73 100644 --- a/src/citra_qt/ui_settings.cpp +++ b/src/citra_qt/ui_settings.cpp @@ -14,5 +14,4 @@ const Themes themes{{ }}; Values values = {}; - } // namespace UISettings diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h index d623b14da..47e4b1b32 100644 --- a/src/citra_qt/ui_settings.h +++ b/src/citra_qt/ui_settings.h @@ -17,7 +17,12 @@ namespace UISettings { using ContextualShortcut = std::pair; -using Shortcut = std::pair; + +struct Shortcut { + QString name; + QString group; + ContextualShortcut shortcut; +}; using Themes = std::array, 4>; extern const Themes themes; diff --git a/src/citra_qt/util/sequence_dialog/sequence_dialog.cpp b/src/citra_qt/util/sequence_dialog/sequence_dialog.cpp new file mode 100644 index 000000000..8eff35621 --- /dev/null +++ b/src/citra_qt/util/sequence_dialog/sequence_dialog.cpp @@ -0,0 +1,37 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "citra_qt/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() { + // 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(); +} diff --git a/src/citra_qt/util/sequence_dialog/sequence_dialog.h b/src/citra_qt/util/sequence_dialog/sequence_dialog.h new file mode 100644 index 000000000..ba8843d92 --- /dev/null +++ b/src/citra_qt/util/sequence_dialog/sequence_dialog.h @@ -0,0 +1,25 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +class QKeySequenceEdit; + +class SequenceDialog : public QDialog { + Q_OBJECT + +public: + explicit SequenceDialog(QWidget* parent = nullptr); + ~SequenceDialog(); + + QKeySequence GetSequence(); + void closeEvent(QCloseEvent*) override; + +private: + QKeySequenceEdit* key_sequence; + bool focusNextPrevChild(bool next) override; +};