summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoren Burkholder <computersemiexpert@outlook.com>2023-09-26 10:19:32 -0400
committerLoren Burkholder <computersemiexpert@outlook.com>2023-09-26 10:19:32 -0400
commit72410c499dc821db99feaacabbc3ae8095ea9480 (patch)
tree80256a76a63e4ef672bd90ed8678bdf740fb0f73
parent66ade755ebbfd9aac3cba19345d51bd593b52d71 (diff)
Add UI to allow editing shortcuts dynamically
-rw-r--r--CMakeLists.txt1
-rw-r--r--resources/qml/dialogs/ShortcutEditor.qml92
-rw-r--r--resources/qml/pages/UserSettingsPage.qml18
-rw-r--r--src/MainWindow.cpp1
-rw-r--r--src/MainWindow.h2
-rw-r--r--src/UserSettingsPage.cpp5
-rw-r--r--src/UserSettingsPage.h2
-rw-r--r--src/ui/ShortcutRegistry.cpp92
-rw-r--r--src/ui/ShortcutRegistry.h27
9 files changed, 191 insertions, 49 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9eb8df67..cd6ac90b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -779,6 +779,7 @@ set(QML_SOURCES
resources/qml/dialogs/RoomMembers.qml
resources/qml/dialogs/AllowedRoomsSettingsDialog.qml
resources/qml/dialogs/RoomSettings.qml
+ resources/qml/dialogs/ShortcutEditor.qml
resources/qml/dialogs/UserProfile.qml
resources/qml/emoji/StickerPicker.qml
resources/qml/pages/LoginPage.qml
diff --git a/resources/qml/dialogs/ShortcutEditor.qml b/resources/qml/dialogs/ShortcutEditor.qml
new file mode 100644
index 00000000..0bd8a00b
--- /dev/null
+++ b/resources/qml/dialogs/ShortcutEditor.qml
@@ -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;
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/resources/qml/pages/UserSettingsPage.qml b/resources/qml/pages/UserSettingsPage.qml
index f23095b6..1407ead7 100644
--- a/resources/qml/pages/UserSettingsPage.qml
+++ b/resources/qml/pages/UserSettingsPage.qml
@@ -5,6 +5,7 @@
pragma ComponentBehavior: Bound
import ".."
import "../ui"
+import "../dialogs"
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15
import QtQuick.Controls 2.15
@@ -216,6 +217,23 @@ Rectangle {
}
}
DelegateChoice {
+ roleValue: UserSettingsModel.ConfigureKeyboardShortcuts
+ Button {
+ text: qsTr("CONFIGURE")
+ onClicked: {
+ var dialog = keyboardShortcutsDialog.createObject();
+ dialog.show();
+ destroyOnClose(dialog);
+ }
+
+ Component {
+ id: keyboardShortcutsDialog
+
+ ShortcutEditor {}
+ }
+ }
+ }
+ DelegateChoice {
Text {
text: model.value
}
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index d06171de..a003dee3 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -57,6 +57,7 @@ MainWindow *MainWindow::instance_ = nullptr;
MainWindow::MainWindow(QWindow *parent)
: QQuickView(parent)
, userSettings_{UserSettings::instance()}
+ , shortcuts_{new ShortcutRegistry}
{
instance_ = this;
diff --git a/src/MainWindow.h b/src/MainWindow.h
index fc06e183..e5b395cb 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -11,6 +11,7 @@
#include <QSharedPointer>
#include <QSystemTrayIcon>
+#include "ShortcutRegistry.h"
#include "UserSettingsPage.h"
#include "dock/Dock.h"
@@ -140,6 +141,7 @@ private:
//! Tray icon that shows the unread message count.
TrayIcon *trayIcon_;
Dock *dock_;
+ ShortcutRegistry *shortcuts_;
MxcImageProvider *imgProvider = nullptr;
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 5caa4838..67ab9bab 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -1145,6 +1145,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
return tr("Periodically update community routing information");
case ExpireEvents:
return tr("Periodically delete expired events");
+ case KeyboardShortcuts:
+ return tr("Configure keyboard shortcuts");
}
} else if (role == Value) {
switch (index.row()) {
@@ -1444,6 +1446,7 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
case LoginInfoSection:
case SessionKeys:
case CrossSigningSecrets:
+ case KeyboardShortcuts:
return {};
case OnlineBackupKey:
return tr(
@@ -1562,6 +1565,8 @@ UserSettingsModel::data(const QModelIndex &index, int role) const
case UserSigningKey:
case MasterKey:
return KeyStatus;
+ case KeyboardShortcuts:
+ return ConfigureKeyboardShortcuts;
}
} else if (role == ValueLowerBound) {
switch (index.row()) {
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 71eb039b..61d66d4e 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -475,6 +475,7 @@ class UserSettingsModel : public QAbstractListModel
#endif
UpdateSpaceVias,
ExpireEvents,
+ KeyboardShortcuts,
AccessibilitySection,
ReducedMotion,
@@ -562,6 +563,7 @@ public:
KeyStatus,
SessionKeyImportExport,
XSignKeysRequestDownload,
+ ConfigureKeyboardShortcuts,
};
Q_ENUM(Types);
diff --git a/src/ui/ShortcutRegistry.cpp b/src/ui/ShortcutRegistry.cpp
index a9a47e3f..5c32b8cb 100644
--- a/src/ui/ShortcutRegistry.cpp
+++ b/src/ui/ShortcutRegistry.cpp
@@ -1,13 +1,27 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
#include "ShortcutRegistry.h"
ShortcutRegistry *ShortcutRegistry::s_instance = nullptr;
EditableShortcut::EditableShortcut(QObject *parent)
- : QObject{parent}
+ : QObject{parent}
+{
+ ShortcutRegistry::instance()->registerShortcut(this);
+}
+
+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
+const QStringList
+EditableShortcut::shortcuts() const
{
QStringList dest;
dest.resize(m_shortcuts.size());
@@ -17,7 +31,8 @@ const QStringList EditableShortcut::shortcuts() const
return dest;
}
-void EditableShortcut::setName(const QString &name)
+void
+EditableShortcut::setName(const QString &name)
{
if (name == m_name)
return;
@@ -25,7 +40,8 @@ void EditableShortcut::setName(const QString &name)
emit nameChanged();
}
-void EditableShortcut::setDescription(const QString &description)
+void
+EditableShortcut::setDescription(const QString &description)
{
if (description == m_description)
return;
@@ -33,12 +49,14 @@ void EditableShortcut::setDescription(const QString &description)
emit descriptionChanged();
}
-void EditableShortcut::setShortcut(const QString &shortcut)
+void
+EditableShortcut::setShortcut(const QString &shortcut)
{
setShortcuts({shortcut});
}
-void EditableShortcut::setShortcuts(const QStringList &shortcuts)
+void
+EditableShortcut::setShortcuts(const QStringList &shortcuts)
{
QList<QKeySequence> temp;
temp.resize(shortcuts.size());
@@ -52,12 +70,13 @@ void EditableShortcut::setShortcuts(const QStringList &shortcuts)
emit shortcutsChanged();
}
-EditableShortcut::EditableShortcut(const QString &name, const QString &description, QObject *parent)
- : QObject{parent}
- , m_name{name}
- , m_description{description}
+ShortcutRegistry::ShortcutRegistry(QObject *parent)
+ : QAbstractListModel{parent}
{
- ShortcutRegistry::instance()->registerShortcut(this);
+ if (s_instance)
+ m_shortcuts = s_instance->m_shortcuts;
+
+ s_instance = this;
}
ShortcutRegistry *
@@ -66,7 +85,8 @@ ShortcutRegistry::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.
Q_ASSERT(s_instance);
@@ -85,20 +105,20 @@ ShortcutRegistry *ShortcutRegistry::create(QQmlEngine *qmlEngine, QJSEngine *)
return s_instance;
}
-QHash<int, QByteArray> ShortcutRegistry::roleNames() const
+QHash<int, QByteArray>
+ShortcutRegistry::roleNames() const
{
- return {{Roles::Name, "name"},
- {Roles::Description, "description"},
- {Roles::Shortcut, "shortcut"}};
+ return {
+ {Roles::Name, "name"}, {Roles::Description, "description"}, {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)
return {};
- switch (role)
- {
+ switch (role) {
case Roles::Name:
return m_shortcuts[index.row()]->name();
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)
- return false;
-
- switch (role)
- {
- case Roles::Shortcut:
- if (auto shortcut = QKeySequence(value.toString()); !shortcut.isEmpty()) {
- m_shortcuts[index.row()]->setShortcut(shortcut.toString());
- return true;
- } else
- return false;
- default:
- return false;
+ for (int i = 0; i < m_shortcuts.size(); ++i) {
+ if (m_shortcuts[i]->name() == name) {
+ qDebug() << "new:" << newShortcut;
+ m_shortcuts[i]->setShortcut(newShortcut);
+ emit dataChanged(index(i), index(i), {Roles::Shortcut});
+ return;
+ }
}
}
-ShortcutRegistry::ShortcutRegistry(QObject *parent)
- : QAbstractListModel{parent}
+QString
+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);
+ endInsertRows();
}
diff --git a/src/ui/ShortcutRegistry.h b/src/ui/ShortcutRegistry.h
index c075b50c..c2fe6265 100644
--- a/src/ui/ShortcutRegistry.h
+++ b/src/ui/ShortcutRegistry.h
@@ -1,7 +1,11 @@
+// SPDX-FileCopyrightText: Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
#pragma once
#include <QAbstractListModel>
-#include <QAction>
+#include <QKeySequence>
#include <QQmlEngine>
class EditableShortcut : public QObject
@@ -10,15 +14,15 @@ class EditableShortcut : public QObject
QML_ELEMENT
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(QStringList shortcuts READ shortcuts WRITE setShortcuts NOTIFY shortcutsChanged FINAL)
+ Q_PROPERTY(
+ QStringList shortcuts READ shortcuts WRITE setShortcuts NOTIFY shortcutsChanged FINAL)
public:
EditableShortcut(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 &description() const { return m_description; }
@@ -58,20 +62,19 @@ public:
Shortcut,
};
+ explicit ShortcutRegistry(QObject *parent = nullptr);
+
static ShortcutRegistry *instance();
static ShortcutRegistry *create(QQmlEngine *qmlEngine, QJSEngine *);
QHash<int, QByteArray> roleNames() const override;
- int rowCount(const QModelIndex & = QModelIndex()) const override
- {
- return m_shortcuts.size();
- }
+ int rowCount(const QModelIndex & = QModelIndex()) const override { return m_shortcuts.size(); }
QVariant data(const QModelIndex &index, int role) const override;
- bool setData(const QModelIndex &index, const QVariant &value, int role) override;
-private:
- explicit ShortcutRegistry(QObject *parent = nullptr);
+ Q_INVOKABLE void changeShortcut(const QString &name, const QString &newShortcut);
+ Q_INVOKABLE QString keycodeToChar(int keycode) const;
+private:
void registerShortcut(EditableShortcut *action);
static ShortcutRegistry *s_instance;