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