1 // Copyright 2016 Citra Emulator Project
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4 
5 #include <algorithm>
6 #include <memory>
7 #include <utility>
8 #include <QInputDialog>
9 #include <QKeyEvent>
10 #include <QLabel>
11 #include <QMenu>
12 #include <QMessageBox>
13 #include <QSlider>
14 #include <QTimer>
15 #include "citra_qt/configuration/config.h"
16 #include "citra_qt/configuration/configure_input.h"
17 #include "citra_qt/configuration/configure_motion_touch.h"
18 #include "common/param_package.h"
19 #include "ui_configure_input.h"
20 
21 const std::array<std::string, ConfigureInput::ANALOG_SUB_BUTTONS_NUM>
22     ConfigureInput::analog_sub_buttons{{
23         "up",
24         "down",
25         "left",
26         "right",
27         "modifier",
28     }};
29 
GetKeyName(int key_code)30 static QString GetKeyName(int key_code) {
31     switch (key_code) {
32     case Qt::Key_Shift:
33         return QObject::tr("Shift");
34     case Qt::Key_Control:
35         return QObject::tr("Ctrl");
36     case Qt::Key_Alt:
37         return QObject::tr("Alt");
38     case Qt::Key_Meta:
39         return QString{};
40     default:
41         return QKeySequence(key_code).toString();
42     }
43 }
44 
SetAnalogButton(const Common::ParamPackage & input_param,Common::ParamPackage & analog_param,const std::string & button_name)45 static void SetAnalogButton(const Common::ParamPackage& input_param,
46                             Common::ParamPackage& analog_param, const std::string& button_name) {
47     if (analog_param.Get("engine", "") != "analog_from_button") {
48         analog_param = {
49             {"engine", "analog_from_button"},
50         };
51     }
52     analog_param.Set(button_name, input_param.Serialize());
53 }
54 
ButtonToText(const Common::ParamPackage & param)55 static QString ButtonToText(const Common::ParamPackage& param) {
56     if (!param.Has("engine")) {
57         return QObject::tr("[not set]");
58     }
59     const auto engine_str = param.Get("engine", "");
60     if (engine_str == "keyboard") {
61         return GetKeyName(param.Get("code", 0));
62     }
63 
64     if (engine_str == "sdl") {
65         if (param.Has("hat")) {
66             const QString hat_str = QString::fromStdString(param.Get("hat", ""));
67             const QString direction_str = QString::fromStdString(param.Get("direction", ""));
68 
69             return QObject::tr("Hat %1 %2").arg(hat_str, direction_str);
70         }
71 
72         if (param.Has("axis")) {
73             const QString axis_str = QString::fromStdString(param.Get("axis", ""));
74             const QString direction_str = QString::fromStdString(param.Get("direction", ""));
75 
76             return QObject::tr("Axis %1%2").arg(axis_str, direction_str);
77         }
78 
79         if (param.Has("button")) {
80             const QString button_str = QString::fromStdString(param.Get("button", ""));
81 
82             return QObject::tr("Button %1").arg(button_str);
83         }
84 
85         return {};
86     }
87 
88     if (engine_str == "gcpad") {
89         if (param.Has("axis")) {
90             const QString axis_str = QString::fromStdString(param.Get("axis", ""));
91             const QString direction_str = QString::fromStdString(param.Get("direction", ""));
92 
93             return QObject::tr("GC Axis %1%2").arg(axis_str, direction_str);
94         }
95         if (param.Has("button")) {
96             const QString button_str = QString::number(int(std::log2(param.Get("button", 0))));
97             return QObject::tr("GC Button %1").arg(button_str);
98         }
99         return GetKeyName(param.Get("code", 0));
100     }
101 
102     return QObject::tr("[unknown]");
103 }
104 
AnalogToText(const Common::ParamPackage & param,const std::string & dir)105 static QString AnalogToText(const Common::ParamPackage& param, const std::string& dir) {
106     if (!param.Has("engine")) {
107         return QObject::tr("[not set]");
108     }
109 
110     const auto engine_str = param.Get("engine", "");
111     if (engine_str == "analog_from_button") {
112         return ButtonToText(Common::ParamPackage{param.Get(dir, "")});
113     }
114 
115     const QString axis_x_str{QString::fromStdString(param.Get("axis_x", ""))};
116     const QString axis_y_str{QString::fromStdString(param.Get("axis_y", ""))};
117     static const QString plus_str{QString::fromStdString("+")};
118     static const QString minus_str{QString::fromStdString("-")};
119     if (engine_str == "sdl" || engine_str == "gcpad") {
120         if (dir == "modifier") {
121             return QObject::tr("[unused]");
122         }
123         if (dir == "left") {
124             return QObject::tr("Axis %1%2").arg(axis_x_str, minus_str);
125         }
126         if (dir == "right") {
127             return QObject::tr("Axis %1%2").arg(axis_x_str, plus_str);
128         }
129         if (dir == "up") {
130             return QObject::tr("Axis %1%2").arg(axis_y_str, plus_str);
131         }
132         if (dir == "down") {
133             return QObject::tr("Axis %1%2").arg(axis_y_str, minus_str);
134         }
135         return {};
136     }
137     return QObject::tr("[unknown]");
138 }
139 
ConfigureInput(QWidget * parent)140 ConfigureInput::ConfigureInput(QWidget* parent)
141     : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()),
142       timeout_timer(std::make_unique<QTimer>()), poll_timer(std::make_unique<QTimer>()) {
143     ui->setupUi(this);
144     setFocusPolicy(Qt::ClickFocus);
145 
146     for (const auto& profile : Settings::values.input_profiles) {
147         ui->profile->addItem(QString::fromStdString(profile.name));
148     }
149 
150     ui->profile->setCurrentIndex(Settings::values.current_input_profile_index);
151 
152     button_map = {
153         ui->buttonA,      ui->buttonB,        ui->buttonX,        ui->buttonY,
154         ui->buttonDpadUp, ui->buttonDpadDown, ui->buttonDpadLeft, ui->buttonDpadRight,
155         ui->buttonL,      ui->buttonR,        ui->buttonStart,    ui->buttonSelect,
156         ui->buttonDebug,  ui->buttonGpio14,   ui->buttonZL,       ui->buttonZR,
157         ui->buttonHome,
158     };
159 
160     analog_map_buttons = {{
161         {
162             ui->buttonCircleUp,
163             ui->buttonCircleDown,
164             ui->buttonCircleLeft,
165             ui->buttonCircleRight,
166             ui->buttonCircleMod,
167         },
168         {
169             ui->buttonCStickUp,
170             ui->buttonCStickDown,
171             ui->buttonCStickLeft,
172             ui->buttonCStickRight,
173             nullptr,
174         },
175     }};
176 
177     analog_map_stick = {ui->buttonCircleAnalog, ui->buttonCStickAnalog};
178     analog_map_deadzone_and_modifier_slider = {ui->sliderCirclePadDeadzoneAndModifier,
179                                                ui->sliderCStickDeadzoneAndModifier};
180     analog_map_deadzone_and_modifier_slider_label = {ui->labelCirclePadDeadzoneAndModifier,
181                                                      ui->labelCStickDeadzoneAndModifier};
182 
183     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
184         if (!button_map[button_id])
185             continue;
186         button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
187         connect(button_map[button_id], &QPushButton::clicked, [=]() {
188             HandleClick(
189                 button_map[button_id],
190                 [=](const Common::ParamPackage& params) {
191                     buttons_param[button_id] = params;
192                     // If the user closes the dialog, the changes are reverted in
193                     // `GMainWindow::OnConfigure()`
194                     ApplyConfiguration();
195                     Settings::SaveProfile(ui->profile->currentIndex());
196                 },
197                 InputCommon::Polling::DeviceType::Button);
198         });
199         connect(button_map[button_id], &QPushButton::customContextMenuRequested,
200                 [=](const QPoint& menu_location) {
201                     QMenu context_menu;
202                     context_menu.addAction(tr("Clear"), [&] {
203                         buttons_param[button_id].Clear();
204                         button_map[button_id]->setText(tr("[not set]"));
205                         ApplyConfiguration();
206                         Settings::SaveProfile(ui->profile->currentIndex());
207                     });
208                     context_menu.addAction(tr("Restore Default"), [&] {
209                         buttons_param[button_id] = Common::ParamPackage{
210                             InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
211                         button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
212                         ApplyConfiguration();
213                         Settings::SaveProfile(ui->profile->currentIndex());
214                     });
215                     context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
216                 });
217     }
218 
219     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
220         for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
221             if (!analog_map_buttons[analog_id][sub_button_id])
222                 continue;
223             analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
224                 Qt::CustomContextMenu);
225             connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::clicked, [=]() {
226                 HandleClick(
227                     analog_map_buttons[analog_id][sub_button_id],
228                     [=](const Common::ParamPackage& params) {
229                         SetAnalogButton(params, analogs_param[analog_id],
230                                         analog_sub_buttons[sub_button_id]);
231                         ApplyConfiguration();
232                         Settings::SaveProfile(ui->profile->currentIndex());
233                     },
234                     InputCommon::Polling::DeviceType::Button);
235             });
236             connect(analog_map_buttons[analog_id][sub_button_id],
237                     &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
238                         QMenu context_menu;
239                         context_menu.addAction(tr("Clear"), [&] {
240                             analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
241                             analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
242                             ApplyConfiguration();
243                             Settings::SaveProfile(ui->profile->currentIndex());
244                         });
245                         context_menu.addAction(tr("Restore Default"), [&] {
246                             Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
247                                 Config::default_analogs[analog_id][sub_button_id])};
248                             SetAnalogButton(params, analogs_param[analog_id],
249                                             analog_sub_buttons[sub_button_id]);
250                             analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
251                                 analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
252                             ApplyConfiguration();
253                             Settings::SaveProfile(ui->profile->currentIndex());
254                         });
255                         context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
256                             menu_location));
257                     });
258         }
259         connect(analog_map_stick[analog_id], &QPushButton::clicked, [=]() {
260             if (QMessageBox::information(
261                     this, tr("Information"),
262                     tr("After pressing OK, first move your joystick horizontally, "
263                        "and then vertically."),
264                     QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
265                 HandleClick(
266                     analog_map_stick[analog_id],
267                     [=](const Common::ParamPackage& params) {
268                         analogs_param[analog_id] = params;
269                         ApplyConfiguration();
270                         Settings::SaveProfile(ui->profile->currentIndex());
271                     },
272                     InputCommon::Polling::DeviceType::Analog);
273             }
274         });
275         connect(analog_map_deadzone_and_modifier_slider[analog_id], &QSlider::valueChanged, [=] {
276             const int slider_value = analog_map_deadzone_and_modifier_slider[analog_id]->value();
277             const auto engine = analogs_param[analog_id].Get("engine", "");
278             if (engine == "sdl" || engine == "gcpad") {
279                 analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
280                     tr("Deadzone: %1%").arg(slider_value));
281                 analogs_param[analog_id].Set("deadzone", slider_value / 100.0f);
282             } else {
283                 analog_map_deadzone_and_modifier_slider_label[analog_id]->setText(
284                     tr("Modifier Scale: %1%").arg(slider_value));
285                 analogs_param[analog_id].Set("modifier_scale", slider_value / 100.0f);
286             }
287             ApplyConfiguration();
288             Settings::SaveProfile(ui->profile->currentIndex());
289         });
290     }
291 
292     connect(ui->buttonMotionTouch, &QPushButton::clicked, [this] {
293         QDialog* motion_touch_dialog = new ConfigureMotionTouch(this);
294         return motion_touch_dialog->exec();
295     });
296 
297     ui->buttonDelete->setEnabled(ui->profile->count() > 1);
298 
299     connect(ui->buttonAutoMap, &QPushButton::clicked, this, &ConfigureInput::AutoMap);
300     connect(ui->buttonClearAll, &QPushButton::clicked, this, &ConfigureInput::ClearAll);
301     connect(ui->buttonRestoreDefaults, &QPushButton::clicked, this,
302             &ConfigureInput::RestoreDefaults);
303     connect(ui->buttonNew, &QPushButton::clicked, this, &ConfigureInput::NewProfile);
304     connect(ui->buttonDelete, &QPushButton::clicked, this, &ConfigureInput::DeleteProfile);
305     connect(ui->buttonRename, &QPushButton::clicked, this, &ConfigureInput::RenameProfile);
306 
307     connect(ui->profile, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
308             [this](int i) {
309                 ApplyConfiguration();
310                 Settings::SaveProfile(Settings::values.current_input_profile_index);
311                 Settings::LoadProfile(i);
312                 LoadConfiguration();
313             });
314 
315     timeout_timer->setSingleShot(true);
316     connect(timeout_timer.get(), &QTimer::timeout, [this]() { SetPollingResult({}, true); });
317 
318     connect(poll_timer.get(), &QTimer::timeout, [this]() {
319         Common::ParamPackage params;
320         for (auto& poller : device_pollers) {
321             params = poller->GetNextInput();
322             if (params.Has("engine")) {
323                 SetPollingResult(params, false);
324                 return;
325             }
326         }
327     });
328 
329     LoadConfiguration();
330 
331     // TODO(wwylele): enable this when we actually emulate it
332     ui->buttonHome->setEnabled(false);
333 }
334 
335 ConfigureInput::~ConfigureInput() = default;
336 
ApplyConfiguration()337 void ConfigureInput::ApplyConfiguration() {
338     std::transform(buttons_param.begin(), buttons_param.end(),
339                    Settings::values.current_input_profile.buttons.begin(),
340                    [](const Common::ParamPackage& param) { return param.Serialize(); });
341     std::transform(analogs_param.begin(), analogs_param.end(),
342                    Settings::values.current_input_profile.analogs.begin(),
343                    [](const Common::ParamPackage& param) { return param.Serialize(); });
344 }
345 
ApplyProfile()346 void ConfigureInput::ApplyProfile() {
347     Settings::values.current_input_profile_index = ui->profile->currentIndex();
348 }
349 
EmitInputKeysChanged()350 void ConfigureInput::EmitInputKeysChanged() {
351     emit InputKeysChanged(GetUsedKeyboardKeys());
352 }
353 
OnHotkeysChanged(QList<QKeySequence> new_key_list)354 void ConfigureInput::OnHotkeysChanged(QList<QKeySequence> new_key_list) {
355     hotkey_list = new_key_list;
356 }
357 
GetUsedKeyboardKeys()358 QList<QKeySequence> ConfigureInput::GetUsedKeyboardKeys() {
359     QList<QKeySequence> list;
360     for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
361         // TODO(adityaruplaha): Add home button to list when we finally emulate it
362         if (button == Settings::NativeButton::Home) {
363             continue;
364         }
365 
366         auto button_param = buttons_param[button];
367         if (button_param.Get("engine", "") == "keyboard") {
368             list << QKeySequence(button_param.Get("code", 0));
369         }
370     }
371 
372     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; ++analog_id) {
373         auto analog_param = analogs_param[analog_id];
374         if (analog_param.Get("engine", "") == "analog_from_button") {
375             for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; ++sub_button_id) {
376                 const Common::ParamPackage sub_button{
377                     analog_param.Get(analog_sub_buttons[sub_button_id], "")};
378                 list << QKeySequence(sub_button.Get("code", 0));
379             }
380         }
381     }
382 
383     return list;
384 }
385 
LoadConfiguration()386 void ConfigureInput::LoadConfiguration() {
387     std::transform(Settings::values.current_input_profile.buttons.begin(),
388                    Settings::values.current_input_profile.buttons.end(), buttons_param.begin(),
389                    [](const std::string& str) { return Common::ParamPackage(str); });
390     std::transform(Settings::values.current_input_profile.analogs.begin(),
391                    Settings::values.current_input_profile.analogs.end(), analogs_param.begin(),
392                    [](const std::string& str) { return Common::ParamPackage(str); });
393     UpdateButtonLabels();
394 }
395 
RestoreDefaults()396 void ConfigureInput::RestoreDefaults() {
397     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
398         buttons_param[button_id] = Common::ParamPackage{
399             InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
400     }
401 
402     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
403         for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
404             Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
405                 Config::default_analogs[analog_id][sub_button_id])};
406             SetAnalogButton(params, analogs_param[analog_id], analog_sub_buttons[sub_button_id]);
407         }
408         analogs_param[analog_id].Set("modifier_scale", 0.5f);
409     }
410     UpdateButtonLabels();
411 
412     ApplyConfiguration();
413     Settings::SaveProfile(Settings::values.current_input_profile_index);
414 }
415 
ClearAll()416 void ConfigureInput::ClearAll() {
417     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
418         if (button_map[button_id] && button_map[button_id]->isEnabled())
419             buttons_param[button_id].Clear();
420     }
421     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
422         analogs_param[analog_id].Clear();
423     }
424     UpdateButtonLabels();
425 
426     ApplyConfiguration();
427     Settings::SaveProfile(Settings::values.current_input_profile_index);
428 }
429 
UpdateButtonLabels()430 void ConfigureInput::UpdateButtonLabels() {
431     for (int button = 0; button < Settings::NativeButton::NumButtons; button++) {
432         if (button_map[button])
433             button_map[button]->setText(ButtonToText(buttons_param[button]));
434     }
435 
436     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
437         for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
438             if (analog_map_buttons[analog_id][sub_button_id]) {
439                 analog_map_buttons[analog_id][sub_button_id]->setText(
440                     AnalogToText(analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
441             }
442         }
443         analog_map_stick[analog_id]->setText(tr("Set Analog Stick"));
444 
445         auto& param = analogs_param[analog_id];
446         auto* const analog_stick_slider = analog_map_deadzone_and_modifier_slider[analog_id];
447         auto* const analog_stick_slider_label =
448             analog_map_deadzone_and_modifier_slider_label[analog_id];
449 
450         if (param.Has("engine")) {
451             const auto engine{param.Get("engine", "")};
452             if (engine == "sdl" || engine == "gcpad") {
453                 if (!param.Has("deadzone")) {
454                     param.Set("deadzone", 0.1f);
455                 }
456                 const auto slider_value = static_cast<int>(param.Get("deadzone", 0.1f) * 100);
457                 analog_stick_slider_label->setText(tr("Deadzone: %1%").arg(slider_value));
458                 analog_stick_slider->setValue(slider_value);
459             } else {
460                 if (!param.Has("modifier_scale")) {
461                     param.Set("modifier_scale", 0.5f);
462                 }
463                 const auto slider_value = static_cast<int>(param.Get("modifier_scale", 0.5f) * 100);
464                 analog_stick_slider_label->setText(tr("Modifier Scale: %1%").arg(slider_value));
465                 analog_stick_slider->setValue(slider_value);
466             }
467         }
468     }
469 
470     EmitInputKeysChanged();
471 }
472 
MapFromButton(const Common::ParamPackage & params)473 void ConfigureInput::MapFromButton(const Common::ParamPackage& params) {
474     Common::ParamPackage aux_param;
475     bool mapped = false;
476     for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
477         aux_param = InputCommon::GetControllerButtonBinds(params, button_id);
478         if (aux_param.Has("engine")) {
479             buttons_param[button_id] = aux_param;
480             mapped = true;
481         }
482     }
483     for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
484         aux_param = InputCommon::GetControllerAnalogBinds(params, analog_id);
485         if (aux_param.Has("engine")) {
486             analogs_param[analog_id] = aux_param;
487             mapped = true;
488         }
489     }
490     if (!mapped) {
491         QMessageBox::warning(
492             this, tr("Warning"),
493             tr("Auto mapping failed. Your controller may not have a corresponding mapping"));
494     }
495 }
496 
AutoMap()497 void ConfigureInput::AutoMap() {
498     if (QMessageBox::information(this, tr("Information"),
499                                  tr("After pressing OK, press any button on your joystick"),
500                                  QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Cancel) {
501         return;
502     }
503     input_setter = [=](const Common::ParamPackage& params) {
504         MapFromButton(params);
505         ApplyConfiguration();
506         Settings::SaveProfile(ui->profile->currentIndex());
507     };
508     device_pollers = InputCommon::Polling::GetPollers(InputCommon::Polling::DeviceType::Button);
509     want_keyboard_keys = false;
510     for (auto& poller : device_pollers) {
511         poller->Start();
512     }
513     timeout_timer->start(5000); // Cancel after 5 seconds
514     poll_timer->start(200);     // Check for new inputs every 200ms
515 }
516 
HandleClick(QPushButton * button,std::function<void (const Common::ParamPackage &)> new_input_setter,InputCommon::Polling::DeviceType type)517 void ConfigureInput::HandleClick(QPushButton* button,
518                                  std::function<void(const Common::ParamPackage&)> new_input_setter,
519                                  InputCommon::Polling::DeviceType type) {
520     previous_key_code = QKeySequence(button->text())[0];
521     button->setText(tr("[press key]"));
522     button->setFocus();
523 
524     input_setter = new_input_setter;
525 
526     device_pollers = InputCommon::Polling::GetPollers(type);
527 
528     // Keyboard keys can only be used as button devices
529     want_keyboard_keys = type == InputCommon::Polling::DeviceType::Button;
530 
531     for (auto& poller : device_pollers) {
532         poller->Start();
533     }
534 
535     grabKeyboard();
536     grabMouse();
537     timeout_timer->start(5000); // Cancel after 5 seconds
538     poll_timer->start(200);     // Check for new inputs every 200ms
539 }
540 
SetPollingResult(const Common::ParamPackage & params,bool abort)541 void ConfigureInput::SetPollingResult(const Common::ParamPackage& params, bool abort) {
542     releaseKeyboard();
543     releaseMouse();
544     timeout_timer->stop();
545     poll_timer->stop();
546     for (auto& poller : device_pollers) {
547         poller->Stop();
548     }
549 
550     if (!abort && input_setter) {
551         (*input_setter)(params);
552     }
553 
554     UpdateButtonLabels();
555     input_setter.reset();
556 }
557 
keyPressEvent(QKeyEvent * event)558 void ConfigureInput::keyPressEvent(QKeyEvent* event) {
559     if (!input_setter || !event)
560         return;
561 
562     if (event->key() != Qt::Key_Escape && event->key() != previous_key_code) {
563         if (want_keyboard_keys) {
564             // Check if key is already bound
565             if (hotkey_list.contains(QKeySequence(event->key())) ||
566                 GetUsedKeyboardKeys().contains(QKeySequence(event->key()))) {
567                 SetPollingResult({}, true);
568                 QMessageBox::critical(this, tr("Error!"),
569                                       tr("You're using a key that's already bound."));
570                 return;
571             }
572             SetPollingResult(Common::ParamPackage{InputCommon::GenerateKeyboardParam(event->key())},
573                              false);
574         } else {
575             // Escape key wasn't pressed and we don't want any keyboard keys, so don't stop
576             // polling
577             return;
578         }
579     }
580     SetPollingResult({}, true);
581     previous_key_code = 0;
582 }
583 
RetranslateUI()584 void ConfigureInput::RetranslateUI() {
585     ui->retranslateUi(this);
586 }
587 
NewProfile()588 void ConfigureInput::NewProfile() {
589     const QString name =
590         QInputDialog::getText(this, tr("New Profile"), tr("Enter the name for the new profile."));
591     if (name.isEmpty()) {
592         return;
593     }
594     if (IsProfileNameDuplicate(name)) {
595         WarnProposedProfileNameIsDuplicate();
596         return;
597     }
598 
599     ApplyConfiguration();
600     Settings::SaveProfile(ui->profile->currentIndex());
601     Settings::CreateProfile(name.toStdString());
602     ui->profile->addItem(name);
603     ui->profile->setCurrentIndex(Settings::values.current_input_profile_index);
604     LoadConfiguration();
605     ui->buttonDelete->setEnabled(ui->profile->count() > 1);
606 }
607 
DeleteProfile()608 void ConfigureInput::DeleteProfile() {
609     const auto answer = QMessageBox::question(
610         this, tr("Delete Profile"), tr("Delete profile %1?").arg(ui->profile->currentText()));
611     if (answer != QMessageBox::Yes) {
612         return;
613     }
614     const int index = ui->profile->currentIndex();
615     ui->profile->removeItem(index);
616     ui->profile->setCurrentIndex(0);
617     Settings::DeleteProfile(index);
618     LoadConfiguration();
619     ui->buttonDelete->setEnabled(ui->profile->count() > 1);
620 }
621 
RenameProfile()622 void ConfigureInput::RenameProfile() {
623     const QString new_name = QInputDialog::getText(this, tr("Rename Profile"), tr("New name:"));
624     if (new_name.isEmpty()) {
625         return;
626     }
627     if (IsProfileNameDuplicate(new_name)) {
628         WarnProposedProfileNameIsDuplicate();
629         return;
630     }
631 
632     ui->profile->setItemText(ui->profile->currentIndex(), new_name);
633     Settings::RenameCurrentProfile(new_name.toStdString());
634     Settings::SaveProfile(ui->profile->currentIndex());
635 }
636 
IsProfileNameDuplicate(const QString & name) const637 bool ConfigureInput::IsProfileNameDuplicate(const QString& name) const {
638     return ui->profile->findText(name, Qt::MatchFixedString | Qt::MatchCaseSensitive) != -1;
639 }
640 
WarnProposedProfileNameIsDuplicate()641 void ConfigureInput::WarnProposedProfileNameIsDuplicate() {
642     QMessageBox::warning(this, tr("Duplicate profile name"),
643                          tr("Profile name already exists. Please choose a different name."));
644 }
645