citra-qt: Add base support for hotkey reconfiguration + UI (whole of PR citra-emu/citra#3786)

* Adds a new Hotkeys tab in the Controls group.
* Right click to reconfigure.
* See the original PR for more details & screenshots.
This commit is contained in:
Adityarup Laha 2018-05-22 21:30:36 +02:00
parent 95a57a2fe3
commit 5fa25fcf13
No known key found for this signature in database
GPG key ID: 1B24048A1F78845F
22 changed files with 559 additions and 306 deletions

View file

@ -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
)

View file

@ -3,7 +3,9 @@
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <unordered_map>
#include <QKeySequence>
#include <QSettings>
#include "citra_qt/configuration/config.h"
#include "citra_qt/ui_settings.h"
@ -318,20 +320,46 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");
QStringList groups = qt_config->childGroups();
for (auto group : groups) {
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();
}
const std::array<UISettings::Shortcut, 14> default_hotkeys{
{{"Load File", "Main Window",
UISettings::ContextualShortcut(QKeySequence(QKeySequence::Open).toString(),
Qt::WindowShortcut)},
{"Exit Citra", "Main Window",
UISettings::ContextualShortcut("Ctrl+Q", Qt::WindowShortcut)},
{"Continue/Pause Emulation", "Main Window",
UISettings::ContextualShortcut("F4", Qt::WindowShortcut)},
{"Stop Emulation", "Main Window",
UISettings::ContextualShortcut("F5", Qt::WindowShortcut)},
{"Restart Emulation", "Main Window",
UISettings::ContextualShortcut("F6", Qt::WindowShortcut)},
{"Swap Screens", "Main Window", UISettings::ContextualShortcut("F9", Qt::WindowShortcut)},
{"Toggle Screen Layout", "Main Window",
UISettings::ContextualShortcut("F10", Qt::WindowShortcut)},
{"Toggle Filter Bar", "Main Window",
UISettings::ContextualShortcut("Ctrl+F", Qt::WindowShortcut)},
{"Toggle Status Bar", "Main Window",
UISettings::ContextualShortcut("Ctrl+S", Qt::WindowShortcut)},
{"Fullscreen", "Main Window",
UISettings::ContextualShortcut(QKeySequence(QKeySequence::FullScreen).toString(),
Qt::WindowShortcut)},
{"Exit Fullscreen", "Main Window",
UISettings::ContextualShortcut("Escape", Qt::WindowShortcut)},
{"Toggle Speed Limit", "Main Window",
UISettings::ContextualShortcut("Ctrl+Z", Qt::ApplicationShortcut)},
{"Increase Speed Limit", "Main Window",
UISettings::ContextualShortcut("+", Qt::ApplicationShortcut)},
{"Decrease Speed Limit", "Main Window",
UISettings::ContextualShortcut("-", Qt::ApplicationShortcut)}}};
for (int i = 0; i < default_hotkeys.size(); i++) {
qt_config->beginGroup(default_hotkeys[i].group);
qt_config->beginGroup(default_hotkeys[i].name);
UISettings::values.shortcuts.push_back(
{default_hotkeys[i].name, default_hotkeys[i].group,
UISettings::ContextualShortcut(
qt_config->value("KeySeq", default_hotkeys[i].shortcut.first).toString(),
qt_config->value("Context", default_hotkeys[i].shortcut.second).toInt())});
qt_config->endGroup();
qt_config->endGroup();
}
qt_config->endGroup();
@ -564,8 +592,12 @@ void Config::SaveValues() {
qt_config->beginGroup("Shortcuts");
for (auto shortcut : UISettings::values.shortcuts) {
WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
WriteSetting(shortcut.first + "/Context", shortcut.second.second);
qt_config->beginGroup(shortcut.group);
qt_config->beginGroup(shortcut.name);
WriteSetting("KeySeq", shortcut.shortcut.first);
WriteSetting("Context", shortcut.shortcut.second);
qt_config->endGroup();
qt_config->endGroup();
}
qt_config->endGroup();

View file

@ -38,6 +38,11 @@
<string>Input</string>
</attribute>
</widget>
<widget class="ConfigureHotkeys" name="hotkeysTab">
<attribute name="title">
<string>Hotkeys</string>
</attribute>
</widget>
<widget class="ConfigureGraphics" name="graphicsTab">
<attribute name="title">
<string>Graphics</string>
@ -118,6 +123,12 @@
<header>configuration/configure_input.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureHotkeys</class>
<extends>QWidget</extends>
<header>configuration/configure_hotkeys.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ConfigureGraphics</class>
<extends>QWidget</extends>

View file

@ -13,15 +13,25 @@
ConfigureDialog::ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry)
: QDialog(parent), 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);
connect(ui->inputTab, &ConfigureInput::InputKeysChanged, ui->hotkeysTab,
&ConfigureHotkeys::OnInputKeysChanged);
connect(ui->hotkeysTab, &ConfigureHotkeys::HotkeysChanged, ui->inputTab,
&ConfigureInput::OnHotkeysChanged);
connect(ui->hotkeysTab, &ConfigureHotkeys::HotkeysChanged, this,
[this]() { emit UpdateHotkeys(); });
// Synchronise lists upon initialisation
ui->inputTab->EmitInputKeysChanged();
ui->hotkeysTab->EmitHotkeysChanged();
}
ConfigureDialog::~ConfigureDialog() = default;
@ -38,11 +48,12 @@ void ConfigureDialog::setConfiguration() {
ui->uiTab->setConfiguration();
}
void ConfigureDialog::applyConfiguration() {
void ConfigureDialog::applyConfiguration(HotkeyRegistry& registry) {
ui->generalTab->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 +72,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 +102,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 +117,11 @@ void ConfigureDialog::UpdateVisibleTabs() {
return;
const QHash<QString, QWidget*> 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}};
{tr("General"), ui->generalTab}, {tr("System"), ui->systemTab},
{tr("Input"), ui->inputTab}, {tr("Hotkeys"), ui->hotkeysTab},
{tr("Graphics"), ui->graphicsTab}, {tr("Audio"), ui->audioTab},
{tr("Camera"), ui->cameraTab}, {tr("Debug"), ui->debugTab},
{tr("Web"), ui->webTab}, {tr("UI"), ui->uiTab}};
ui->tabWidget->clear();

View file

@ -20,15 +20,16 @@ public:
explicit ConfigureDialog(QWidget* parent, const HotkeyRegistry& registry);
~ConfigureDialog() override;
void applyConfiguration();
void UpdateVisibleTabs();
void PopulateSelectionList();
void applyConfiguration(HotkeyRegistry& registry);
private slots:
void onLanguageChanged(const QString& locale);
signals:
void languageChanged(const QString& locale);
void UpdateHotkeys();
private:
void setConfiguration();

View file

@ -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();
}

View file

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

View file

@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>345</width>
<height>504</height>
<height>357</height>
</rect>
</property>
<property name="windowTitle">
@ -21,17 +21,13 @@
<property name="title">
<string>General</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QCheckBox" name="toggle_check_exit">
<property name="text">
<string>Confirm exit while emulation is running</string>
</property>
</widget>
</item>
</layout>
<widget class="QCheckBox" name="toggle_check_exit">
<property name="text">
<string>Confirm exit while emulation is running</string>
</property>
</widget>
</item>
</layout>
</widget>
@ -41,24 +37,20 @@
<property name="title">
<string>Updates</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_update">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_update">
<item>
<widget class="QCheckBox" name="toggle_update_check">
<property name="text">
<string>Check for updates on start</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_auto_update">
<property name="text">
<string>Silently auto update after closing</string>
</property>
</widget>
</item>
</layout>
<widget class="QCheckBox" name="toggle_update_check">
<property name="text">
<string>Check for updates on start</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_auto_update">
<property name="text">
<string>Silently auto update after closing</string>
</property>
</widget>
</item>
</layout>
</widget>
@ -68,81 +60,57 @@
<property name="title">
<string>Emulation</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<layout class="QVBoxLayout" name="verticalLayout_6">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="region_combobox">
<item>
<property name="text">
<string>Auto-select</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">JPN</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">USA</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">EUR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">AUS</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">CHN</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">KOR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">TWN</string>
</property>
</item>
</widget>
</item>
</layout>
</item>
</layout>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Hotkeys</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item row="0" column="1">
<widget class="QComboBox" name="region_combobox">
<item>
<widget class="GHotkeysDialog" name="hotkeysDialog" native="true"/>
<property name="text">
<string>Auto-select</string>
</property>
</item>
</layout>
<item>
<property name="text">
<string notr="true">JPN</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">USA</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">EUR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">AUS</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">CHN</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">KOR</string>
</property>
</item>
<item>
<property name="text">
<string notr="true">TWN</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
@ -158,14 +126,6 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>GHotkeysDialog</class>
<extends>QWidget</extends>
<header>hotkeys.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -0,0 +1,130 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QMessageBox>
#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::ConfigureHotkeys>()) {
ui->setupUi(this);
setFocusPolicy(Qt::ClickFocus);
model = new QStandardItemModel(this);
model->setColumnCount(3);
model->setHorizontalHeaderLabels({tr("Action"), tr("Hotkey"), tr("Context")});
ui->hotkey_list->setSelectionMode(QTreeView::SingleSelection);
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);
ui->hotkey_list->setEditTriggers(QTreeView::NoEditTriggers);
}
ConfigureHotkeys::~ConfigureHotkeys() {}
void ConfigureHotkeys::EmitHotkeysChanged() {
emit HotkeysChanged(GetUsedKeyList());
}
QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() {
QList<QKeySequence> 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<QKeySequence> 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(), QKeySequence::NativeText)) {
model->setData(index, previous_key);
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 key_iterator = registry.hotkey_groups.begin();
key_iterator != registry.hotkey_groups.end(); ++key_iterator) {
if (key_iterator->first == parent->text()) {
for (auto it2 = key_iterator->second.begin(); it2 != key_iterator->second.end();
++it2) {
if (it2->first == action->text()) {
it2->second.keyseq = QKeySequence(keyseq->text());
}
}
}
}
}
}
registry.SaveHotkeys();
Settings::Apply();
}
void ConfigureHotkeys::retranslateUi() {
ui->retranslateUi(this);
}

View file

@ -0,0 +1,48 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QStandardItemModel>
#include <QWidget>
#include "common/param_package.h"
#include "core/settings.h"
namespace Ui {
class ConfigureHotkeys;
}
class HotkeyRegistry;
class ConfigureHotkeys : public QWidget {
Q_OBJECT
public:
explicit ConfigureHotkeys(QWidget* parent = nullptr);
~ConfigureHotkeys();
void applyConfiguration(HotkeyRegistry& registry);
void retranslateUi();
void EmitHotkeysChanged();
void Populate(const HotkeyRegistry& registry);
public slots:
void OnInputKeysChanged(QList<QKeySequence> new_key_list);
signals:
void HotkeysChanged(QList<QKeySequence> new_key_list);
private:
void Configure(QModelIndex index);
bool IsUsedKey(QKeySequence key_sequence);
QList<QKeySequence> GetUsedKeyList();
std::unique_ptr<Ui::ConfigureHotkeys> ui;
QList<QKeySequence> input_keys_list;
QStandardItemModel* model;
};

View file

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

View file

@ -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<QKeySequence> new_key_list) {
hotkey_list = new_key_list;
}
QList<QKeySequence> ConfigureInput::GetUsedKeyboardKeys() {
QList<QKeySequence> 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: Reffered 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<void(const Common::ParamPackage&)> 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() {

View file

@ -11,6 +11,7 @@
#include <string>
#include <unordered_map>
#include <QKeyEvent>
#include <QKeySequence>
#include <QWidget>
#include "common/param_package.h"
#include "core/settings.h"
@ -38,6 +39,13 @@ public:
/// Load configuration settings.
void loadConfiguration();
void EmitInputKeysChanged();
public slots:
void OnHotkeysChanged(QList<QKeySequence> new_key_list);
signals:
void InputKeysChanged(QList<QKeySequence> new_key_list);
// Save the current input profile index
void ApplyProfile();
@ -72,10 +80,16 @@ private:
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> device_pollers;
/// Keys currently registered as hotkeys
QList<QKeySequence> 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<QKeySequence> GetUsedKeyboardKeys();
/// Restore all buttons to their default values.
void restoreDefaults();
/// Clear all input configuration
@ -89,6 +103,9 @@ private:
std::function<void(const Common::ParamPackage&)> 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);

View file

@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <map>
#include <QKeySequence>
#include <QShortcut>
#include <QtGlobal>
@ -12,47 +11,30 @@
HotkeyRegistry::HotkeyRegistry() = default;
HotkeyRegistry::~HotkeyRegistry() = default;
void HotkeyRegistry::LoadHotkeys() {
// Make sure NOT to use a reference here because it would become invalid once we call
// beginGroup()
for (auto shortcut : UISettings::values.shortcuts) {
const QStringList cat = shortcut.first.split('/');
Q_ASSERT(cat.size() >= 2);
// RegisterHotkey assigns default keybindings, so use old values as default parameters
Hotkey& hk = hotkey_groups[cat[0]][cat[1]];
if (!shortcut.second.first.isEmpty()) {
hk.keyseq = QKeySequence::fromString(shortcut.second.first);
hk.context = static_cast<Qt::ShortcutContext>(shortcut.second.second);
}
if (hk.shortcut)
hk.shortcut->setKey(hk.keyseq);
}
}
void HotkeyRegistry::SaveHotkeys() {
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 = (Qt::ShortcutContext)shortcut.shortcut.second;
}
if (hk.shortcut)
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 +46,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;
}

View file

@ -5,7 +5,6 @@
#pragma once
#include <map>
#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,26 @@ public:
QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widget);
/**
* Register a hotkey.
* Returns a QKeySequence object who 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 shortcutcontext 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 +78,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;
};

View file

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

View file

@ -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);
@ -1325,13 +1319,15 @@ void GMainWindow::OnConfigure() {
ConfigureDialog configureDialog(this, hotkey_registry);
connect(&configureDialog, &ConfigureDialog::languageChanged, this,
&GMainWindow::OnLanguageChanged);
connect(&configureDialog, &ConfigureDialog::UpdateHotkeys, this,
[this]() { InitializeHotkeys(); });
auto old_theme = UISettings::values.theme;
const int old_input_profile_index = Settings::values.current_input_profile_index;
const auto old_input_profiles = Settings::values.input_profiles;
const bool old_discord_presence = UISettings::values.enable_discord_presence;
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
configureDialog.applyConfiguration(hotkey_registry);
if (UISettings::values.theme != old_theme)
UpdateUITheme();
if (UISettings::values.enable_discord_presence != old_discord_presence)

View file

@ -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();

View file

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

View file

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

View file

@ -0,0 +1,35 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDialogButtonBox>
#include <QVBoxLayout>
#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() {
return QKeySequence(key_sequence->keySequence()[0]);
}
bool SequenceDialog::focusNextPrevChild(bool next) {
return false;
}
void SequenceDialog::closeEvent(QCloseEvent*) {
reject();
}

View file

@ -0,0 +1,24 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <QDialog>
#include <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;
};