Browse Source
yuzu: Make hotkeys configurable via the GUI
yuzu: Make hotkeys configurable via the GUI
* Adds a new Hotkeys tab in the Controls group. * Double-click a Hotkey to rebind it.nce_cpp
committed by
fearlessTobi
23 changed files with 426 additions and 208 deletions
-
6src/yuzu/CMakeLists.txt
-
1src/yuzu/applets/profile_select.cpp
-
2src/yuzu/applets/profile_select.h
-
59src/yuzu/configuration/config.cpp
-
3src/yuzu/configuration/config.h
-
19src/yuzu/configuration/configure.ui
-
16src/yuzu/configuration/configure_dialog.cpp
-
3src/yuzu/configuration/configure_dialog.h
-
4src/yuzu/configuration/configure_general.cpp
-
1src/yuzu/configuration/configure_general.h
-
24src/yuzu/configuration/configure_general.ui
-
121src/yuzu/configuration/configure_hotkeys.cpp
-
48src/yuzu/configuration/configure_hotkeys.h
-
42src/yuzu/configuration/configure_hotkeys.ui
-
73src/yuzu/hotkeys.cpp
-
42src/yuzu/hotkeys.h
-
46src/yuzu/hotkeys.ui
-
53src/yuzu/main.cpp
-
2src/yuzu/main.h
-
1src/yuzu/ui_settings.cpp
-
7src/yuzu/ui_settings.h
-
37src/yuzu/util/sequence_dialog/sequence_dialog.cpp
-
24src/yuzu/util/sequence_dialog/sequence_dialog.h
@ -0,0 +1,121 @@ |
|||
// Copyright 2017 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <QMessageBox>
|
|||
#include <QStandardItemModel>
|
|||
#include "core/settings.h"
|
|||
#include "ui_configure_hotkeys.h"
|
|||
#include "yuzu/configuration/configure_hotkeys.h"
|
|||
#include "yuzu/hotkeys.h"
|
|||
#include "yuzu/util/sequence_dialog/sequence_dialog.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")}); |
|||
|
|||
connect(ui->hotkey_list, &QTreeView::doubleClicked, this, &ConfigureHotkeys::Configure); |
|||
ui->hotkey_list->setModel(model); |
|||
|
|||
// TODO(Kloen): Make context configurable as well (hiding the column for now)
|
|||
ui->hotkey_list->hideColumn(2); |
|||
|
|||
ui->hotkey_list->setColumnWidth(0, 200); |
|||
ui->hotkey_list->resizeColumnToContents(1); |
|||
} |
|||
|
|||
ConfigureHotkeys::~ConfigureHotkeys() = default; |
|||
|
|||
void ConfigureHotkeys::EmitHotkeysChanged() { |
|||
emit HotkeysChanged(GetUsedKeyList()); |
|||
} |
|||
|
|||
QList<QKeySequence> ConfigureHotkeys::GetUsedKeyList() const { |
|||
QList<QKeySequence> list; |
|||
for (int r = 0; r < model->rowCount(); r++) { |
|||
const QStandardItem* parent = model->item(r, 0); |
|||
for (int r2 = 0; r2 < parent->rowCount(); r2++) { |
|||
const 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) { |
|||
auto* parent_item = new QStandardItem(group.first); |
|||
parent_item->setEditable(false); |
|||
for (const auto& hotkey : group.second) { |
|||
auto* action = new QStandardItem(hotkey.first); |
|||
auto* 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::Configure(QModelIndex index) { |
|||
if (index.parent() == QModelIndex()) |
|||
return; |
|||
|
|||
index = index.sibling(index.row(), 1); |
|||
auto* model = ui->hotkey_list->model(); |
|||
auto previous_key = model->data(index); |
|||
|
|||
auto* hotkey_dialog = new SequenceDialog; |
|||
int return_code = hotkey_dialog->exec(); |
|||
|
|||
auto key_sequence = hotkey_dialog->GetSequence(); |
|||
|
|||
if (return_code == QDialog::Rejected || key_sequence.isEmpty()) |
|||
return; |
|||
|
|||
if (IsUsedKey(key_sequence) && key_sequence != QKeySequence(previous_key.toString())) { |
|||
QMessageBox::critical(this, tr("Error in inputted key"), |
|||
tr("You're using a key that's already bound.")); |
|||
} else { |
|||
model->setData(index, key_sequence.toString(QKeySequence::NativeText)); |
|||
EmitHotkeysChanged(); |
|||
} |
|||
} |
|||
|
|||
bool ConfigureHotkeys::IsUsedKey(QKeySequence key_sequence) { |
|||
return GetUsedKeyList().contains(key_sequence); |
|||
} |
|||
|
|||
void ConfigureHotkeys::applyConfiguration(HotkeyRegistry& registry) { |
|||
for (int key_id = 0; key_id < model->rowCount(); key_id++) { |
|||
const QStandardItem* parent = model->item(key_id, 0); |
|||
for (int key_column_id = 0; key_column_id < parent->rowCount(); key_column_id++) { |
|||
const QStandardItem* action = parent->child(key_column_id, 0); |
|||
const QStandardItem* keyseq = parent->child(key_column_id, 1); |
|||
for (auto& [group, sub_actions] : registry.hotkey_groups) { |
|||
if (group != parent->text()) |
|||
continue; |
|||
for (auto& [action_name, hotkey] : sub_actions) { |
|||
if (action_name != action->text()) |
|||
continue; |
|||
hotkey.keyseq = QKeySequence(keyseq->text()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
registry.SaveHotkeys(); |
|||
Settings::Apply(); |
|||
} |
|||
|
|||
void ConfigureHotkeys::retranslateUi() { |
|||
ui->retranslateUi(this); |
|||
} |
|||
@ -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 <QWidget> |
|||
#include "core/settings.h" |
|||
|
|||
namespace Ui { |
|||
class ConfigureHotkeys; |
|||
} |
|||
|
|||
class HotkeyRegistry; |
|||
class QStandardItemModel; |
|||
|
|||
class ConfigureHotkeys : public QWidget { |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
explicit ConfigureHotkeys(QWidget* parent = nullptr); |
|||
~ConfigureHotkeys() override; |
|||
|
|||
void applyConfiguration(HotkeyRegistry& registry); |
|||
void retranslateUi(); |
|||
|
|||
void EmitHotkeysChanged(); |
|||
|
|||
/** |
|||
* Populates the hotkey list widget using data from the provided registry. |
|||
* Called everytime the Configure dialog is opened. |
|||
* @param registry The HotkeyRegistry whose data is used to populate the list. |
|||
*/ |
|||
void Populate(const HotkeyRegistry& registry); |
|||
|
|||
signals: |
|||
void HotkeysChanged(QList<QKeySequence> new_key_list); |
|||
|
|||
private: |
|||
void Configure(QModelIndex index); |
|||
bool IsUsedKey(QKeySequence key_sequence); |
|||
QList<QKeySequence> GetUsedKeyList() const; |
|||
|
|||
std::unique_ptr<Ui::ConfigureHotkeys> ui; |
|||
|
|||
QStandardItemModel* model; |
|||
}; |
|||
@ -0,0 +1,42 @@ |
|||
<?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"> |
|||
<property name="editTriggers"> |
|||
<set>QAbstractItemView::NoEditTriggers</set> |
|||
</property> |
|||
<property name="sortingEnabled"> |
|||
<bool>false</bool> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
</layout> |
|||
</item> |
|||
</layout> |
|||
</widget> |
|||
<resources/> |
|||
<connections/> |
|||
</ui> |
|||
@ -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> |
|||
@ -0,0 +1,37 @@ |
|||
// Copyright 2018 Citra Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <QDialogButtonBox>
|
|||
#include <QKeySequenceEdit>
|
|||
#include <QVBoxLayout>
|
|||
#include "yuzu/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() const { |
|||
// Only the first key is returned. The other 3, if present, are ignored.
|
|||
return QKeySequence(key_sequence->keySequence()[0]); |
|||
} |
|||
|
|||
bool SequenceDialog::focusNextPrevChild(bool next) { |
|||
return false; |
|||
} |
|||
|
|||
void SequenceDialog::closeEvent(QCloseEvent*) { |
|||
reject(); |
|||
} |
|||
@ -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 <QDialog> |
|||
|
|||
class QKeySequenceEdit; |
|||
|
|||
class SequenceDialog : public QDialog { |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
explicit SequenceDialog(QWidget* parent = nullptr); |
|||
~SequenceDialog() override; |
|||
|
|||
QKeySequence GetSequence() const; |
|||
void closeEvent(QCloseEvent*) override; |
|||
|
|||
private: |
|||
QKeySequenceEdit* key_sequence; |
|||
bool focusNextPrevChild(bool next) override; |
|||
}; |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue