diff --git a/src/citra_qt/configuration/configure_dialog.cpp b/src/citra_qt/configuration/configure_dialog.cpp index 0f0fe2e71..bb65effc5 100644 --- a/src/citra_qt/configuration/configure_dialog.cpp +++ b/src/citra_qt/configuration/configure_dialog.cpp @@ -75,7 +75,10 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, Cor &ConfigureHotkeys::OnInputKeysChanged); connect(hotkeys_tab.get(), &ConfigureHotkeys::HotkeysChanged, input_tab.get(), &ConfigureInput::OnHotkeysChanged); - + connect(input_tab.get(), &ConfigureInput::ClearHotkey, hotkeys_tab.get(), + &ConfigureHotkeys::OnClearBinding); + connect(hotkeys_tab.get(), &ConfigureHotkeys::ClearInputBinding, input_tab.get(), + &ConfigureInput::OnClearBinding); // Synchronise lists upon initialisation input_tab->EmitInputKeysChanged(); hotkeys_tab->EmitHotkeysChanged(); diff --git a/src/citra_qt/configuration/configure_hotkeys.cpp b/src/citra_qt/configuration/configure_hotkeys.cpp index ecaed462b..ea513810b 100644 --- a/src/citra_qt/configuration/configure_hotkeys.cpp +++ b/src/citra_qt/configuration/configure_hotkeys.cpp @@ -43,13 +43,19 @@ void ConfigureHotkeys::EmitHotkeysChanged() { emit HotkeysChanged(GetUsedKeyList()); } -QList ConfigureHotkeys::GetUsedKeyList() const { - QList list; +QMap ConfigureHotkeys::GetUsedKeyList() const { + QMap 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); + auto seq = QKeySequence::fromString(keyseq->text(), QKeySequence::NativeText); + if (seq.count() == 1 && + seq[0].keyboardModifiers() == Qt::KeyboardModifier::NoModifier) { + auto binding = ConfigureInput::InputBinding( + "Hotkey", parent->child(r2, 0)->text().toStdString(), r2); + list[seq] = binding; + } } } return list; @@ -75,7 +81,8 @@ void ConfigureHotkeys::Populate(const HotkeyRegistry& registry) { ui->hotkey_list->expandAll(); } -void ConfigureHotkeys::OnInputKeysChanged(QList new_key_list) { +void ConfigureHotkeys::OnInputKeysChanged( + QMap new_key_list) { input_keys_list = new_key_list; } @@ -96,7 +103,7 @@ void ConfigureHotkeys::Configure(QModelIndex index) { if (return_code == QDialog::Rejected || key_sequence.isEmpty()) { return; } - const auto [key_sequence_used, used_action] = IsUsedKey(key_sequence); + const auto [key_sequence_used, current_binding] = IsUsedKey(key_sequence); // Check for turbo/per-game speed conflict. Needed to prevent the user from binding both hotkeys // to the same action. Which cuases problems resetting the frame limit.to the inititla value. @@ -131,22 +138,43 @@ void ConfigureHotkeys::Configure(QModelIndex index) { } if (key_sequence_used && key_sequence != QKeySequence(previous_key.toString())) { - QMessageBox::warning( - this, tr("Conflicting Key Sequence"), - tr("The entered key sequence is already assigned to: %1").arg(used_action)); - } else { - model->setData(index, key_sequence.toString(QKeySequence::NativeText)); - EmitHotkeysChanged(); + auto response = + QMessageBox::question(this, QStringLiteral("Duplicate mapping"), + QString::fromStdString("This will clear the mapping for " + + current_binding.name + ". Proceed?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + if (response == QMessageBox::No) { + return; + } else { + if (current_binding.binding_type == "Hotkey") { + model->setData(index.sibling(current_binding.index, hotkey_column), + QStringLiteral("")); + } else { + emit ClearInputBinding(current_binding); + } + } } + + model->setData(index, key_sequence.toString(QKeySequence::NativeText)); + EmitHotkeysChanged(); } -std::pair ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const { +void ConfigureHotkeys::OnClearBinding(ConfigureInput::InputBinding hotkey_to_clear) { + const auto index = ui->hotkey_list->currentIndex(); + if (hotkey_to_clear.binding_type == "Hotkey") { + model->setData(index.sibling(hotkey_to_clear.index, hotkey_column), QStringLiteral("")); + } + EmitHotkeysChanged(); +} + +std::pair ConfigureHotkeys::IsUsedKey( + QKeySequence key_sequence) const { if (key_sequence == QKeySequence::fromString(QStringLiteral(""), QKeySequence::NativeText)) { - return std::make_pair(false, QString()); + return std::make_pair(false, ConfigureInput::InputBinding{"", "", -1}); } if (input_keys_list.contains(key_sequence)) { - return std::make_pair(true, tr("A 3ds button")); + return std::make_pair(true, input_keys_list[key_sequence]); } for (int r = 0; r < model->rowCount(); ++r) { @@ -158,12 +186,14 @@ std::pair ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) const auto key_seq = QKeySequence::fromString(key_seq_str, QKeySequence::NativeText); if (key_sequence == key_seq) { - return std::make_pair(true, parent->child(r2, 0)->text()); + return std::make_pair( + true, ConfigureInput::InputBinding{ + "Hotkey", parent->child(r2, 0)->text().toStdString(), r2}); } } } - return std::make_pair(false, QString()); + return std::make_pair(false, ConfigureInput::InputBinding{"", "", -1}); } void ConfigureHotkeys::ApplyConfiguration(HotkeyRegistry& registry) { @@ -197,6 +227,7 @@ void ConfigureHotkeys::RestoreDefaults() { ->setText(QtConfig::default_hotkeys[r2].shortcut.keyseq); } } + EmitHotkeysChanged(); } void ConfigureHotkeys::ClearAll() { @@ -207,6 +238,7 @@ void ConfigureHotkeys::ClearAll() { model->item(r, 0)->child(r2, hotkey_column)->setText(QString{}); } } + EmitHotkeysChanged(); } void ConfigureHotkeys::PopupContextMenu(const QPoint& menu_location) { @@ -234,12 +266,13 @@ void ConfigureHotkeys::RestoreHotkey(QModelIndex index) { const auto [key_sequence_used, used_action] = IsUsedKey(default_key_sequence); if (key_sequence_used && default_key_sequence != QKeySequence(model->data(index).toString())) { - QMessageBox::warning( - this, tr("Conflicting Key Sequence"), - tr("The default key sequence is already assigned to: %1").arg(used_action)); + QMessageBox::warning(this, tr("Conflicting Key Sequence"), + tr("The default key sequence is already assigned to: %1") + .arg(QString::fromStdString(used_action.name))); } else { model->setData(index, default_key_sequence.toString(QKeySequence::NativeText)); } + EmitHotkeysChanged(); } void ConfigureHotkeys::RetranslateUI() { diff --git a/src/citra_qt/configuration/configure_hotkeys.h b/src/citra_qt/configuration/configure_hotkeys.h index 99d159217..8ed15cf05 100644 --- a/src/citra_qt/configuration/configure_hotkeys.h +++ b/src/citra_qt/configuration/configure_hotkeys.h @@ -6,6 +6,7 @@ #include #include +#include "citra_qt/configuration/configure_input.h" namespace Ui { class ConfigureHotkeys; @@ -34,15 +35,16 @@ public: void Populate(const HotkeyRegistry& registry); public slots: - void OnInputKeysChanged(QList new_key_list); - + void OnInputKeysChanged(QMap new_key_list); + void OnClearBinding(ConfigureInput::InputBinding hotkey_to_clear); signals: - void HotkeysChanged(QList new_key_list); + void HotkeysChanged(QMap new_key_list); + void ClearInputBinding(ConfigureInput::InputBinding binding); private: void Configure(QModelIndex index); - std::pair IsUsedKey(QKeySequence key_sequence) const; - QList GetUsedKeyList() const; + std::pair IsUsedKey(QKeySequence key_sequence) const; + QMap GetUsedKeyList() const; void RestoreDefaults(); void ClearAll(); @@ -54,7 +56,7 @@ private: * These can't be bound to any hotkey. * Synchronised with ConfigureInput via signal-slot. */ - QList input_keys_list; + QMap input_keys_list; std::unique_ptr ui; diff --git a/src/citra_qt/configuration/configure_input.cpp b/src/citra_qt/configuration/configure_input.cpp index 2cc319968..b0812c18f 100644 --- a/src/citra_qt/configuration/configure_input.cpp +++ b/src/citra_qt/configuration/configure_input.cpp @@ -174,6 +174,11 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) ui->buttonHome, ui->buttonPower, }; + button_names = {"A Button", "B Button", "X Button", "Y Button", "D-Pad Up", + "D-Pad Down", "D-Pad Left", "D-Pad Right", "L Button", "R Button", + "Start Button", "Select Button", "Debug Button", "GPIO4 Button", "ZL Button", + "ZR Button", "Home Button", "Power Button"}; + analog_map_buttons = {{ { ui->buttonCircleUp, @@ -198,7 +203,7 @@ ConfigureInput::ConfigureInput(Core::System& _system, QWidget* parent) nullptr, }, }}; - + analog_names = {"Circle Pad", "C Stick"}; analog_map_stick = {ui->buttonCircleAnalog, ui->buttonCStickAnalog}; analog_map_deadzone_and_modifier_slider = {ui->sliderCirclePadDeadzoneAndModifier, ui->sliderCStickDeadzoneAndModifier}; @@ -439,16 +444,72 @@ void ConfigureInput::EmitInputKeysChanged() { emit InputKeysChanged(GetUsedKeyboardKeys()); } -void ConfigureInput::OnHotkeysChanged(QList new_key_list) { +void ConfigureInput::OnHotkeysChanged( + QMap new_key_list) { hotkey_list = new_key_list; } -QList ConfigureInput::GetUsedKeyboardKeys() { - QList list; +void ConfigureInput::OnClearBinding(ConfigureInput::InputBinding binding) { + ClearBinding(binding); +} + +bool sameInput(const Common::ParamPackage& param1, const Common::ParamPackage& param2) { + return param1.Has("engine") && param2.Has("engine") && + param1.Get("engine", "") == param2.Get("engine", "") && + param1.Get("guid", "") == param2.Get("guid", "") && + (param1.Get("code", -1) == param2.Get("code", -2) || + param1.Get("button", -1) == param2.Get("button", -2) || + (param1.Get("axis", -1) == param2.Get("axis", -2) && + param1.Get("direction", "a") == param2.Get("direction", "b")) || + (param1.Get("axis_x", -1) == param2.Get("axis_x", -2) && + param1.Get("axis_y", -1) == param2.Get("axis_y", -2)) || + (param1.Get("hat", -1) == param2.Get("hat", -2) && + param1.Get("direction", "a") == param2.Get("direction", "b"))); +} + +ConfigureInput::InputBinding ConfigureInput::GetMapping(const Common::ParamPackage& param) { + if (!param.Has("engine")) + return {"", "", 0}; + // check for a button map + for (int button = 0; button < Settings::NativeButton::NumButtons; ++button) { + const auto& button_param = buttons_param[button]; + if (sameInput(param, button_param)) { + return {"NativeButton", button_names[button], button}; + } + } + // check for an analog map + for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) { + const auto& analog_param = analogs_param[analog_id]; + if (analog_param.Get("engine", "") == "analog_from_button") { + for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { + const Common::ParamPackage sub_button{ + analog_param.Get(analog_sub_buttons[sub_button_id], "")}; + if (sameInput(param, sub_button)) { + return {"AnalogButton", + analog_names[analog_id] + " " + analog_sub_buttons[sub_button_id], + analog_id, sub_button_id}; + } + } + } else if (sameInput(param, analog_param)) { + return {"Analog", analog_names[analog_id], analog_id}; + } + } + + if (param.Get("engine", "") == "keyboard" && + hotkey_list.contains(QKeySequence(param.Get("code", 0)))) { + return hotkey_list[QKeySequence(param.Get("code", 0))]; + } + + return {"", "", 0}; +} + +QMap ConfigureInput::GetUsedKeyboardKeys() { + QMap list; for (int button = 0; button < Settings::NativeButton::NumButtons; button++) { const auto& button_param = buttons_param[button]; if (button_param.Get("engine", "") == "keyboard") { - list << QKeySequence(button_param.Get("code", 0)); + list[QKeySequence(button_param.Get("code", 0))] = + ConfigureInput::InputBinding{"NativeButton", button_names[button], button}; } } @@ -458,7 +519,10 @@ QList ConfigureInput::GetUsedKeyboardKeys() { for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) { const Common::ParamPackage sub_button{ analog_param.Get(analog_sub_buttons[sub_button_id], "")}; - list << QKeySequence(sub_button.Get("code", 0)); + list[QKeySequence(sub_button.Get("code", 0))] = ConfigureInput::InputBinding{ + "AnalogButton", + analog_names[analog_id] + " " + analog_sub_buttons[sub_button_id], analog_id, + sub_button_id}; } } } @@ -498,6 +562,25 @@ void ConfigureInput::RestoreDefaults() { Settings::SaveProfile(Settings::values.current_input_profile_index); } +void ConfigureInput::ClearBinding(InputBinding binding) { + if (binding.binding_type == "NativeButton") { + buttons_param[binding.index].Clear(); + button_map[binding.index]->setText(tr("[not set]")); + } else if (binding.binding_type == "AnalogButton") { + const auto analog_id = binding.index; + const auto sub_button_id = binding.sub_index; + analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]); + analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]")); + } else if (binding.binding_type == "Analog") { + const auto analog_id = binding.index; + analogs_param[analog_id].Clear(); + UpdateButtonLabels(); + } else if (binding.binding_type == "Hotkey") { + emit ClearHotkey(binding); + } + ApplyConfiguration(); +} + void ConfigureInput::ClearAll() { for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) { if (button_map[button_id] && button_map[button_id]->isEnabled()) @@ -628,7 +711,7 @@ void ConfigureInput::HandleClick(QPushButton* button, poll_timer->start(200); // Check for new inputs every 200ms } -void ConfigureInput::SetPollingResult(const Common::ParamPackage& params, bool abort) { +void ConfigureInput::StopPolling() { releaseKeyboard(); releaseMouse(); timeout_timer->stop(); @@ -636,7 +719,24 @@ void ConfigureInput::SetPollingResult(const Common::ParamPackage& params, bool a for (auto& poller : device_pollers) { poller->Stop(); } +} +void ConfigureInput::SetPollingResult(const Common::ParamPackage& params, bool abort) { + auto const currentBinding = GetMapping(params); + StopPolling(); + if (!abort && currentBinding.binding_type != "") { + auto response = + QMessageBox::question(this, QStringLiteral("Duplicate mapping"), + QString::fromStdString("This will clear the mapping for " + + currentBinding.name + ". Proceed?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + if (response == QMessageBox::No) { + abort = true; + return; + } else { + ClearBinding(currentBinding); + } + } if (!abort && input_setter) { (*input_setter)(params); } @@ -651,16 +751,9 @@ void ConfigureInput::keyPressEvent(QKeyEvent* event) { 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); + auto param = Common::ParamPackage(InputCommon::GenerateKeyboardParam(event->key())); + previous_key_code = 0; + SetPollingResult(param, false); } else { // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop // polling diff --git a/src/citra_qt/configuration/configure_input.h b/src/citra_qt/configuration/configure_input.h index f70b32240..37fc1a62d 100644 --- a/src/citra_qt/configuration/configure_input.h +++ b/src/citra_qt/configuration/configure_input.h @@ -30,6 +30,12 @@ class ConfigureInput : public QWidget { Q_OBJECT public: + struct InputBinding { + std::string binding_type; + std::string name; + int index; + int sub_index = -1; // used for sub-buttons + }; explicit ConfigureInput(Core::System& system, QWidget* parent = nullptr); ~ConfigureInput() override; @@ -44,10 +50,12 @@ public: /// Save the current input profile index void ApplyProfile(); public slots: - void OnHotkeysChanged(QList new_key_list); + void OnHotkeysChanged(QMap new_key_list); + void OnClearBinding(ConfigureInput::InputBinding binding); signals: - void InputKeysChanged(QList new_key_list); + void InputKeysChanged(QMap new_key_list); + void ClearHotkey(ConfigureInput::InputBinding hotkey_to_clear); private: Core::System& system; @@ -66,6 +74,8 @@ private: /// Each button input is represented by a QPushButton. std::array button_map; + std::array button_names; + std::array analog_names; /// A group of five QPushButtons represent one analog input. The buttons each represent up, /// down, left, right, and modifier, respectively. @@ -89,14 +99,17 @@ private: * These can't be bound to any input key. * Synchronised with ConfigureHotkeys via signal-slot. */ - QList hotkey_list; + QMap 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(); + /// Generates list of all used keyboard keys for sharing with hotkey code + QMap GetUsedKeyboardKeys(); + InputBinding GetMapping(const Common::ParamPackage& param); + + void ClearBinding(InputBinding binding); void MapFromButton(const Common::ParamPackage& params); void AutoMap(); @@ -117,7 +130,9 @@ private: /// 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 + /// Stop polling + void StopPolling(); + /// configure input using the input_setter void SetPollingResult(const Common::ParamPackage& params, bool abort); /// Handle key press events.