mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 20:48:52 +03:00
Add UI to allow editing shortcuts dynamically
This commit is contained in:
parent
66ade755eb
commit
72410c499d
9 changed files with 191 additions and 49 deletions
|
@ -779,6 +779,7 @@ set(QML_SOURCES
|
||||||
resources/qml/dialogs/RoomMembers.qml
|
resources/qml/dialogs/RoomMembers.qml
|
||||||
resources/qml/dialogs/AllowedRoomsSettingsDialog.qml
|
resources/qml/dialogs/AllowedRoomsSettingsDialog.qml
|
||||||
resources/qml/dialogs/RoomSettings.qml
|
resources/qml/dialogs/RoomSettings.qml
|
||||||
|
resources/qml/dialogs/ShortcutEditor.qml
|
||||||
resources/qml/dialogs/UserProfile.qml
|
resources/qml/dialogs/UserProfile.qml
|
||||||
resources/qml/emoji/StickerPicker.qml
|
resources/qml/emoji/StickerPicker.qml
|
||||||
resources/qml/pages/LoginPage.qml
|
resources/qml/pages/LoginPage.qml
|
||||||
|
|
92
resources/qml/dialogs/ShortcutEditor.qml
Normal file
92
resources/qml/dialogs/ShortcutEditor.qml
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// SPDX-FileCopyrightText: Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import ".."
|
||||||
|
import "../ui"
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import QtQuick.Window
|
||||||
|
import im.nheko
|
||||||
|
|
||||||
|
ApplicationWindow {
|
||||||
|
id: shortcutEditorDialog
|
||||||
|
|
||||||
|
minimumWidth: 500
|
||||||
|
minimumHeight: 450
|
||||||
|
width: 500
|
||||||
|
height: 680
|
||||||
|
color: palette.window
|
||||||
|
modality: Qt.NonModal
|
||||||
|
flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint
|
||||||
|
title: qsTr("Keyboard shortcuts")
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
padding: Nheko.paddingMedium
|
||||||
|
ScrollBar.horizontal.visible: false
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
model: ShortcutRegistry
|
||||||
|
|
||||||
|
delegate: RowLayout {
|
||||||
|
id: del
|
||||||
|
|
||||||
|
required property string name
|
||||||
|
required property string description
|
||||||
|
required property string shortcut
|
||||||
|
|
||||||
|
spacing: Nheko.paddingMedium
|
||||||
|
width: ListView.view.width
|
||||||
|
height: implicitHeight + Nheko.paddingSmall * 2
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: Nheko.paddingSmall
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: del.name
|
||||||
|
font.bold: true
|
||||||
|
font.pointSize: fontMetrics.font.pointSize * 1.1
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: del.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
|
Button {
|
||||||
|
property bool selectingNewShortcut: false
|
||||||
|
|
||||||
|
text: selectingNewShortcut ? qsTr("Input..") : del.shortcut
|
||||||
|
onClicked: selectingNewShortcut = !selectingNewShortcut
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (!selectingNewShortcut)
|
||||||
|
return;
|
||||||
|
event.accepted = true;
|
||||||
|
|
||||||
|
let keySequence = "";
|
||||||
|
if (event.modifiers & Qt.ControlModifier)
|
||||||
|
keySequence += "Ctrl+";
|
||||||
|
if (event.modifiers & Qt.AltModifier)
|
||||||
|
keySequence += "Alt+";
|
||||||
|
if (event.modifiers & Qt.MetaModifier)
|
||||||
|
keySequence += "Meta+";
|
||||||
|
if (event.modifiers & Qt.ShiftModifier)
|
||||||
|
keySequence += "Shift+";
|
||||||
|
|
||||||
|
if (event.key === 0 || event.key === Qt.Key_unknown || event.key === Qt.Key_Control || event.key === Qt.Key_Alt || event.key === Qt.Key_AltGr || event.key === Qt.Key_Meta || event.key === Qt.Key_Shift)
|
||||||
|
keySequence += "...";
|
||||||
|
else {
|
||||||
|
keySequence += ShortcutRegistry.keycodeToChar(event.key);
|
||||||
|
ShortcutRegistry.changeShortcut(del.name, keySequence);
|
||||||
|
selectingNewShortcut = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
import ".."
|
import ".."
|
||||||
import "../ui"
|
import "../ui"
|
||||||
|
import "../dialogs"
|
||||||
import Qt.labs.platform 1.1 as Platform
|
import Qt.labs.platform 1.1 as Platform
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
|
@ -215,6 +216,23 @@ Rectangle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: UserSettingsModel.ConfigureKeyboardShortcuts
|
||||||
|
Button {
|
||||||
|
text: qsTr("CONFIGURE")
|
||||||
|
onClicked: {
|
||||||
|
var dialog = keyboardShortcutsDialog.createObject();
|
||||||
|
dialog.show();
|
||||||
|
destroyOnClose(dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: keyboardShortcutsDialog
|
||||||
|
|
||||||
|
ShortcutEditor {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
Text {
|
Text {
|
||||||
text: model.value
|
text: model.value
|
||||||
|
|
|
@ -57,6 +57,7 @@ MainWindow *MainWindow::instance_ = nullptr;
|
||||||
MainWindow::MainWindow(QWindow *parent)
|
MainWindow::MainWindow(QWindow *parent)
|
||||||
: QQuickView(parent)
|
: QQuickView(parent)
|
||||||
, userSettings_{UserSettings::instance()}
|
, userSettings_{UserSettings::instance()}
|
||||||
|
, shortcuts_{new ShortcutRegistry}
|
||||||
{
|
{
|
||||||
instance_ = this;
|
instance_ = this;
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
|
#include "ShortcutRegistry.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "dock/Dock.h"
|
#include "dock/Dock.h"
|
||||||
|
|
||||||
|
@ -140,6 +141,7 @@ private:
|
||||||
//! Tray icon that shows the unread message count.
|
//! Tray icon that shows the unread message count.
|
||||||
TrayIcon *trayIcon_;
|
TrayIcon *trayIcon_;
|
||||||
Dock *dock_;
|
Dock *dock_;
|
||||||
|
ShortcutRegistry *shortcuts_;
|
||||||
|
|
||||||
MxcImageProvider *imgProvider = nullptr;
|
MxcImageProvider *imgProvider = nullptr;
|
||||||
|
|
||||||
|
|
|
@ -1145,6 +1145,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
||||||
return tr("Periodically update community routing information");
|
return tr("Periodically update community routing information");
|
||||||
case ExpireEvents:
|
case ExpireEvents:
|
||||||
return tr("Periodically delete expired events");
|
return tr("Periodically delete expired events");
|
||||||
|
case KeyboardShortcuts:
|
||||||
|
return tr("Configure keyboard shortcuts");
|
||||||
}
|
}
|
||||||
} else if (role == Value) {
|
} else if (role == Value) {
|
||||||
switch (index.row()) {
|
switch (index.row()) {
|
||||||
|
@ -1444,6 +1446,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
||||||
case LoginInfoSection:
|
case LoginInfoSection:
|
||||||
case SessionKeys:
|
case SessionKeys:
|
||||||
case CrossSigningSecrets:
|
case CrossSigningSecrets:
|
||||||
|
case KeyboardShortcuts:
|
||||||
return {};
|
return {};
|
||||||
case OnlineBackupKey:
|
case OnlineBackupKey:
|
||||||
return tr(
|
return tr(
|
||||||
|
@ -1562,6 +1565,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
|
||||||
case UserSigningKey:
|
case UserSigningKey:
|
||||||
case MasterKey:
|
case MasterKey:
|
||||||
return KeyStatus;
|
return KeyStatus;
|
||||||
|
case KeyboardShortcuts:
|
||||||
|
return ConfigureKeyboardShortcuts;
|
||||||
}
|
}
|
||||||
} else if (role == ValueLowerBound) {
|
} else if (role == ValueLowerBound) {
|
||||||
switch (index.row()) {
|
switch (index.row()) {
|
||||||
|
|
|
@ -475,6 +475,7 @@ class UserSettingsModel : public QAbstractListModel
|
||||||
#endif
|
#endif
|
||||||
UpdateSpaceVias,
|
UpdateSpaceVias,
|
||||||
ExpireEvents,
|
ExpireEvents,
|
||||||
|
KeyboardShortcuts,
|
||||||
|
|
||||||
AccessibilitySection,
|
AccessibilitySection,
|
||||||
ReducedMotion,
|
ReducedMotion,
|
||||||
|
@ -562,6 +563,7 @@ public:
|
||||||
KeyStatus,
|
KeyStatus,
|
||||||
SessionKeyImportExport,
|
SessionKeyImportExport,
|
||||||
XSignKeysRequestDownload,
|
XSignKeysRequestDownload,
|
||||||
|
ConfigureKeyboardShortcuts,
|
||||||
};
|
};
|
||||||
Q_ENUM(Types);
|
Q_ENUM(Types);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,27 @@
|
||||||
|
// SPDX-FileCopyrightText: Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include "ShortcutRegistry.h"
|
#include "ShortcutRegistry.h"
|
||||||
|
|
||||||
ShortcutRegistry *ShortcutRegistry::s_instance = nullptr;
|
ShortcutRegistry *ShortcutRegistry::s_instance = nullptr;
|
||||||
|
|
||||||
EditableShortcut::EditableShortcut(QObject *parent)
|
EditableShortcut::EditableShortcut(QObject *parent)
|
||||||
: QObject{parent}
|
: QObject{parent}
|
||||||
{
|
{
|
||||||
|
ShortcutRegistry::instance()->registerShortcut(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QStringList EditableShortcut::shortcuts() const
|
EditableShortcut::EditableShortcut(const QString &name, const QString &description, QObject *parent)
|
||||||
|
: QObject{parent}
|
||||||
|
, m_name{name}
|
||||||
|
, m_description{description}
|
||||||
|
{
|
||||||
|
ShortcutRegistry::instance()->registerShortcut(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
const QStringList
|
||||||
|
EditableShortcut::shortcuts() const
|
||||||
{
|
{
|
||||||
QStringList dest;
|
QStringList dest;
|
||||||
dest.resize(m_shortcuts.size());
|
dest.resize(m_shortcuts.size());
|
||||||
|
@ -17,7 +31,8 @@ const QStringList EditableShortcut::shortcuts() const
|
||||||
return dest;
|
return dest;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditableShortcut::setName(const QString &name)
|
void
|
||||||
|
EditableShortcut::setName(const QString &name)
|
||||||
{
|
{
|
||||||
if (name == m_name)
|
if (name == m_name)
|
||||||
return;
|
return;
|
||||||
|
@ -25,7 +40,8 @@ void EditableShortcut::setName(const QString &name)
|
||||||
emit nameChanged();
|
emit nameChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditableShortcut::setDescription(const QString &description)
|
void
|
||||||
|
EditableShortcut::setDescription(const QString &description)
|
||||||
{
|
{
|
||||||
if (description == m_description)
|
if (description == m_description)
|
||||||
return;
|
return;
|
||||||
|
@ -33,12 +49,14 @@ void EditableShortcut::setDescription(const QString &description)
|
||||||
emit descriptionChanged();
|
emit descriptionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditableShortcut::setShortcut(const QString &shortcut)
|
void
|
||||||
|
EditableShortcut::setShortcut(const QString &shortcut)
|
||||||
{
|
{
|
||||||
setShortcuts({shortcut});
|
setShortcuts({shortcut});
|
||||||
}
|
}
|
||||||
|
|
||||||
void EditableShortcut::setShortcuts(const QStringList &shortcuts)
|
void
|
||||||
|
EditableShortcut::setShortcuts(const QStringList &shortcuts)
|
||||||
{
|
{
|
||||||
QList<QKeySequence> temp;
|
QList<QKeySequence> temp;
|
||||||
temp.resize(shortcuts.size());
|
temp.resize(shortcuts.size());
|
||||||
|
@ -52,12 +70,13 @@ void EditableShortcut::setShortcuts(const QStringList &shortcuts)
|
||||||
emit shortcutsChanged();
|
emit shortcutsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableShortcut::EditableShortcut(const QString &name, const QString &description, QObject *parent)
|
ShortcutRegistry::ShortcutRegistry(QObject *parent)
|
||||||
: QObject{parent}
|
: QAbstractListModel{parent}
|
||||||
, m_name{name}
|
|
||||||
, m_description{description}
|
|
||||||
{
|
{
|
||||||
ShortcutRegistry::instance()->registerShortcut(this);
|
if (s_instance)
|
||||||
|
m_shortcuts = s_instance->m_shortcuts;
|
||||||
|
|
||||||
|
s_instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShortcutRegistry *
|
ShortcutRegistry *
|
||||||
|
@ -66,7 +85,8 @@ ShortcutRegistry::instance()
|
||||||
return s_instance;
|
return s_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShortcutRegistry *ShortcutRegistry::create(QQmlEngine *qmlEngine, QJSEngine *)
|
ShortcutRegistry *
|
||||||
|
ShortcutRegistry::create(QQmlEngine *qmlEngine, QJSEngine *)
|
||||||
{
|
{
|
||||||
// The instance has to exist before it is used. We cannot replace it.
|
// The instance has to exist before it is used. We cannot replace it.
|
||||||
Q_ASSERT(s_instance);
|
Q_ASSERT(s_instance);
|
||||||
|
@ -85,20 +105,20 @@ ShortcutRegistry *ShortcutRegistry::create(QQmlEngine *qmlEngine, QJSEngine *)
|
||||||
return s_instance;
|
return s_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<int, QByteArray> ShortcutRegistry::roleNames() const
|
QHash<int, QByteArray>
|
||||||
|
ShortcutRegistry::roleNames() const
|
||||||
{
|
{
|
||||||
return {{Roles::Name, "name"},
|
return {
|
||||||
{Roles::Description, "description"},
|
{Roles::Name, "name"}, {Roles::Description, "description"}, {Roles::Shortcut, "shortcut"}};
|
||||||
{Roles::Shortcut, "shortcut"}};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ShortcutRegistry::data(const QModelIndex &index, int role) const
|
QVariant
|
||||||
|
ShortcutRegistry::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
if (!index.isValid() || index.row() >= m_shortcuts.size() || index.row() < 0)
|
if (!index.isValid() || index.row() >= m_shortcuts.size() || index.row() < 0)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
switch (role)
|
switch (role) {
|
||||||
{
|
|
||||||
case Roles::Name:
|
case Roles::Name:
|
||||||
return m_shortcuts[index.row()]->name();
|
return m_shortcuts[index.row()]->name();
|
||||||
case Roles::Description:
|
case Roles::Description:
|
||||||
|
@ -110,31 +130,29 @@ QVariant ShortcutRegistry::data(const QModelIndex &index, int role) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShortcutRegistry::setData(const QModelIndex &index, const QVariant &value, int role)
|
void
|
||||||
|
ShortcutRegistry::changeShortcut(const QString &name, const QString &newShortcut)
|
||||||
{
|
{
|
||||||
if (!index.isValid() || index.row() >= m_shortcuts.size() || index.row() < 0)
|
for (int i = 0; i < m_shortcuts.size(); ++i) {
|
||||||
return false;
|
if (m_shortcuts[i]->name() == name) {
|
||||||
|
qDebug() << "new:" << newShortcut;
|
||||||
switch (role)
|
m_shortcuts[i]->setShortcut(newShortcut);
|
||||||
{
|
emit dataChanged(index(i), index(i), {Roles::Shortcut});
|
||||||
case Roles::Shortcut:
|
return;
|
||||||
if (auto shortcut = QKeySequence(value.toString()); !shortcut.isEmpty()) {
|
}
|
||||||
m_shortcuts[index.row()]->setShortcut(shortcut.toString());
|
|
||||||
return true;
|
|
||||||
} else
|
|
||||||
return false;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ShortcutRegistry::ShortcutRegistry(QObject *parent)
|
QString
|
||||||
: QAbstractListModel{parent}
|
ShortcutRegistry::keycodeToChar(int keycode) const
|
||||||
{
|
{
|
||||||
s_instance = this;
|
return QString((char)keycode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShortcutRegistry::registerShortcut(EditableShortcut *action)
|
void
|
||||||
|
ShortcutRegistry::registerShortcut(EditableShortcut *action)
|
||||||
{
|
{
|
||||||
|
beginInsertRows({}, m_shortcuts.size(), m_shortcuts.size());
|
||||||
m_shortcuts.push_back(action);
|
m_shortcuts.push_back(action);
|
||||||
|
endInsertRows();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
|
// SPDX-FileCopyrightText: Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QAction>
|
#include <QKeySequence>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
class EditableShortcut : public QObject
|
class EditableShortcut : public QObject
|
||||||
|
@ -10,15 +14,15 @@ class EditableShortcut : public QObject
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
|
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL)
|
||||||
Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged FINAL)
|
Q_PROPERTY(
|
||||||
|
QString description READ description WRITE setDescription NOTIFY descriptionChanged FINAL)
|
||||||
Q_PROPERTY(QString shortcut READ shortcut WRITE setShortcut NOTIFY shortcutsChanged FINAL)
|
Q_PROPERTY(QString shortcut READ shortcut WRITE setShortcut NOTIFY shortcutsChanged FINAL)
|
||||||
Q_PROPERTY(QStringList shortcuts READ shortcuts WRITE setShortcuts NOTIFY shortcutsChanged FINAL)
|
Q_PROPERTY(
|
||||||
|
QStringList shortcuts READ shortcuts WRITE setShortcuts NOTIFY shortcutsChanged FINAL)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
EditableShortcut(QObject *parent = nullptr);
|
EditableShortcut(QObject *parent = nullptr);
|
||||||
EditableShortcut(const QString &name, const QString &description, QObject *parent = nullptr);
|
EditableShortcut(const QString &name, const QString &description, QObject *parent = nullptr);
|
||||||
EditableShortcut(const QString &name, const QString &description, const QString &text, QObject *parent = nullptr);
|
|
||||||
EditableShortcut(const QString &name, const QString &description, const QIcon &icon, const QString &text, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
const QString &name() const { return m_name; }
|
const QString &name() const { return m_name; }
|
||||||
const QString &description() const { return m_description; }
|
const QString &description() const { return m_description; }
|
||||||
|
@ -58,20 +62,19 @@ public:
|
||||||
Shortcut,
|
Shortcut,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
explicit ShortcutRegistry(QObject *parent = nullptr);
|
||||||
|
|
||||||
static ShortcutRegistry *instance();
|
static ShortcutRegistry *instance();
|
||||||
static ShortcutRegistry *create(QQmlEngine *qmlEngine, QJSEngine *);
|
static ShortcutRegistry *create(QQmlEngine *qmlEngine, QJSEngine *);
|
||||||
|
|
||||||
QHash<int, QByteArray> roleNames() const override;
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
int rowCount(const QModelIndex & = QModelIndex()) const override
|
int rowCount(const QModelIndex & = QModelIndex()) const override { return m_shortcuts.size(); }
|
||||||
{
|
|
||||||
return m_shortcuts.size();
|
|
||||||
}
|
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
|
||||||
|
Q_INVOKABLE void changeShortcut(const QString &name, const QString &newShortcut);
|
||||||
|
Q_INVOKABLE QString keycodeToChar(int keycode) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
explicit ShortcutRegistry(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
void registerShortcut(EditableShortcut *action);
|
void registerShortcut(EditableShortcut *action);
|
||||||
|
|
||||||
static ShortcutRegistry *s_instance;
|
static ShortcutRegistry *s_instance;
|
||||||
|
|
Loading…
Reference in a new issue