diff --git a/CMakeLists.txt b/CMakeLists.txt index cd6ac90b..ddf0d3cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -397,8 +397,8 @@ set(SRC_FILES src/ui/RoomSettings.h src/ui/RoomSummary.cpp src/ui/RoomSummary.h - src/ui/ShortcutRegistry.cpp - src/ui/ShortcutRegistry.h + src/ui/KeySequenceRegistry.cpp + src/ui/KeySequenceRegistry.h src/ui/Theme.cpp src/ui/Theme.h src/ui/UIA.cpp diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 6cdfc99c..b506b93e 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -112,15 +112,14 @@ Pane { onActivated: Qt.quit() } - EditableShortcut { + EditableKeySequence { id: quickSwitcherShortcut name: qsTr("Room search") - description: qsTr("Opens a search bar for quick switching between rooms") - shortcut: "Ctrl+K" + defaultKeySequence: "Ctrl+K" } Shortcut { - sequence: quickSwitcherShortcut.shortcut + sequence: quickSwitcherShortcut.keySequence onActivated: { var component = Qt.createComponent("qrc:/resources/qml/QuickSwitcher.qml"); @@ -134,45 +133,98 @@ Pane { } } - EditableShortcut { + EditableKeySequence { id: nextRoomWithActivityShortcut name: qsTr("Next room with activity") - description: qsTr("Switches to the next unread room in the roomlist") // Add alternative shortcut, because sometimes Alt+A is stolen by the TextEdit - shortcuts: ["Alt+A", "Ctrl+Shift+A"] + defaultKeySequences: ["Alt+A", "Ctrl+Shift+A"] } Shortcut { - sequences: nextRoomWithActivityShortcut.shortcuts + sequences: nextRoomWithActivityShortcut.keySequence onActivated: Rooms.nextRoomWithActivity() } - EditableShortcut { + EditableKeySequence { id: nextRoomShortcut name: qsTr("Next room") - description: qsTr("Switches to the room below the room that is currently open") - shortcut: "Ctrl+Down" + defaultKeySequence: "Ctrl+Down" } Shortcut { - sequence: nextRoomShortcut.shortcut + sequence: nextRoomShortcut.keySequence onActivated: Rooms.nextRoom() } - EditableShortcut { + EditableKeySequence { id: previousRoomShortcut name: qsTr("Previous room") - description: qsTr("Switches to the room above the room that is currently open") - shortcut: "Ctrl+Up" + defaultKeySequence: "Ctrl+Up" } Shortcut { - sequence: previousRoomShortcut.shortcut + sequence: previousRoomShortcut.keySequence onActivated: Rooms.previousRoom() } + + EditableKeySequence { + id: nextSpaceShortcut + + name: qsTr("Next space") + } + Shortcut { + sequence: nextSpaceShortcut.keySequence + +// onActivated: Communities.setCurrentTagId(model.id) + } + + EditableKeySequence { + id: previousSpaceShortcut + + name: qsTr("Previous space") + } + Shortcut { + sequence: previousSpaceShortcut.keySequence + +// onActivated: Communities.setCurrentTagId(model.id) + } + + EditableKeySequence { + id: allRoomsSpaceShortcut + + name: qsTr("Show all rooms") + } + Shortcut { + sequence: allRoomsSpaceShortcut.keySequence + + onActivated: Communities.setCurrentTagId("global") + } + + EditableKeySequence { + id: favoriteRoomsShortcut + + name: qsTr("Show favorite rooms") + } + Shortcut { + sequence: favoriteRoomsShortcut.keySequence + + onActivated: Communities.setCurrentTagId("m.favourite") + } + + EditableKeySequence { + id: directChatsShortcut + + name: qsTr("Show direct chats") + } + Shortcut { + sequence: directChatsShortcut.keySequence + + onActivated: Communities.setCurrentTagId("dm") + } + Connections { function onOpenJoinRoomDialog() { var component = Qt.createComponent("qrc:/resources/qml/dialogs/JoinRoomDialog.qml"); diff --git a/resources/qml/dialogs/ShortcutEditor.qml b/resources/qml/dialogs/ShortcutEditor.qml index 0bd8a00b..b2558557 100644 --- a/resources/qml/dialogs/ShortcutEditor.qml +++ b/resources/qml/dialogs/ShortcutEditor.qml @@ -28,42 +28,31 @@ ApplicationWindow { anchors.fill: parent ListView { - model: ShortcutRegistry + model: KeySequenceRegistry delegate: RowLayout { id: del required property string name - required property string description - required property string shortcut + required property string keySequence 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 - } + Label { + text: del.name } Item { Layout.fillWidth: true } Button { - property bool selectingNewShortcut: false + property bool selectingNewKeySequence: false - text: selectingNewShortcut ? qsTr("Input..") : del.shortcut - onClicked: selectingNewShortcut = !selectingNewShortcut + text: selectingNewKeySequence ? qsTr("Input..") : (del.keySequence === "" ? "None" : del.keySequence) + onClicked: selectingNewKeySequence = !selectingNewKeySequence Keys.onPressed: event => { - if (!selectingNewShortcut) + if (!selectingNewKeySequence) return; event.accepted = true; @@ -77,12 +66,14 @@ ApplicationWindow { 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) + 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_Super_L || event.key === Qt.Key_Super_R || + event.key === Qt.Key_Shift) keySequence += "..."; else { - keySequence += ShortcutRegistry.keycodeToChar(event.key); - ShortcutRegistry.changeShortcut(del.name, keySequence); - selectingNewShortcut = false; + keySequence += KeySequenceRegistry.keycodeToChar(event.key); + KeySequenceRegistry.changeKeySequence(del.name, keySequence); + selectingNewKeySequence = false; } } } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index a003dee3..d06171de 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -57,7 +57,6 @@ 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 e5b395cb..a070266e 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -11,7 +11,7 @@ #include #include -#include "ShortcutRegistry.h" +#include "KeySequenceRegistry.h" #include "UserSettingsPage.h" #include "dock/Dock.h" @@ -141,7 +141,6 @@ 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 67ab9bab..5306f577 100644 --- a/src/UserSettingsPage.cpp +++ b/src/UserSettingsPage.cpp @@ -30,8 +30,10 @@ QSharedPointer UserSettings::instance_; UserSettings::UserSettings() { - connect( - QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { instance_.clear(); }); + connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { + instance_->save(); + instance_.clear(); + }); } QSharedPointer @@ -164,6 +166,16 @@ UserSettings::load(std::optional profile) disableCertificateValidation_ = settings.value(QStringLiteral("disable_certificate_validation"), false).toBool(); + settings.beginGroup(QStringLiteral("user")); + settings.beginGroup(QStringLiteral("shortcuts")); + QMap bindings; + for (const auto &key : settings.childKeys()) + bindings[key] = settings.value(key).toStringList(); + qDebug() << "restoring with size:" << bindings.size(); + KeySequenceRegistry::instance()->restoreBindings(bindings); + settings.endGroup(); // user/shortcuts + settings.endGroup(); // user/shortcuts + applyTheme(); } @@ -882,7 +894,7 @@ UserSettings::save() settings.beginGroup(QStringLiteral("sidebar")); settings.setValue(QStringLiteral("community_list_width"), communityListWidth_); settings.setValue(QStringLiteral("room_list_width"), roomListWidth_); - settings.endGroup(); // window + settings.endGroup(); // sidebar settings.beginGroup(QStringLiteral("timeline")); settings.setValue(QStringLiteral("buttons"), buttonsInTimeline_); @@ -939,6 +951,13 @@ UserSettings::save() settings.setValue(QStringLiteral("space_background_maintenance"), updateSpaceVias_); settings.setValue(QStringLiteral("expired_events_background_maintenance"), expireEvents_); + settings.beginGroup(QStringLiteral("shortcuts")); + auto bindings = KeySequenceRegistry::instance()->dumpBindings(); + for (const auto &[name, sequences] : bindings.asKeyValueRange()) + settings.setValue(name, sequences); + qDebug() << "saved with size:" << bindings.size(); + settings.endGroup(); // shortcuts + settings.endGroup(); // user QString prefix = (profile_ != QLatin1String("") && profile_ != QLatin1String("default")) diff --git a/src/ui/KeySequenceRegistry.cpp b/src/ui/KeySequenceRegistry.cpp new file mode 100644 index 00000000..8184ff48 --- /dev/null +++ b/src/ui/KeySequenceRegistry.cpp @@ -0,0 +1,241 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "KeySequenceRegistry.h" + +KeySequenceRegistry *KeySequenceRegistry::s_instance = nullptr; + +KeySequenceImpl::KeySequenceImpl(const QString &name, + const QStringList &keySequences, + QObject *parent) + : QObject{parent} + , m_name{name} + , m_keySequences{keySequences} +{ +} + +void +KeySequenceImpl::setKeySequences(const QStringList &keySequences) +{ + if (keySequences == m_keySequences) + return; + m_keySequences = keySequences; + emit keySequencesChanged(); +} + +EditableKeySequence::EditableKeySequence(QObject *parent) + : QObject{parent} +{ + KeySequenceRegistry::instance()->registerKeySequence(this); +} + +EditableKeySequence::EditableKeySequence(const QString &name, QObject *parent) + : QObject{parent} + , m_name{name} +{ + KeySequenceRegistry::instance()->registerKeySequence(this); +} + +const QString +EditableKeySequence::keySequence() const +{ + return (m_impl && m_impl->keySequences().size() > 0) ? m_impl->keySequences().first() + : defaultKeySequence(); +} + +const QStringList +EditableKeySequence::keySequences() const +{ + return m_impl ? m_impl->keySequences() : defaultKeySequences(); +} + +const QString +EditableKeySequence::defaultKeySequence() const +{ + return m_defaultKeySequences.size() > 0 ? m_defaultKeySequences.first().toString() : QString{}; +} + +const QStringList +EditableKeySequence::defaultKeySequences() const +{ + QStringList dest; + dest.resize(m_defaultKeySequences.size()); + std::transform(m_defaultKeySequences.begin(), + m_defaultKeySequences.end(), + dest.begin(), + [](const auto &keySequence) { return keySequence.toString(); }); + return dest; +} + +void +EditableKeySequence::setName(const QString &name) +{ + if (name == m_name) + return; + m_name = name; + emit nameChanged(); + KeySequenceRegistry::instance()->registerKeySequence(this); +} + +void +EditableKeySequence::setKeySequence(const QString &keySequence) +{ + setKeySequences({keySequence}); +} + +void +EditableKeySequence::setKeySequences(const QStringList &keySequences) +{ + m_impl->setKeySequences(keySequences); +} + +void +EditableKeySequence::setDefaultKeySequence(const QString &keySequence) +{ + setDefaultKeySequences({keySequence}); +} + +void +EditableKeySequence::setDefaultKeySequences(const QStringList &keySequences) +{ + QList temp; + temp.resize(keySequences.size()); + std::transform(keySequences.begin(), + keySequences.end(), + temp.begin(), + [](const auto &keySequence) { return QKeySequence(keySequence); }); + + if (temp == m_defaultKeySequences) + return; + m_defaultKeySequences = temp; + emit defaultKeySequencesChanged(); + + if (m_impl && m_impl->keySequences().isEmpty()) + m_impl->setKeySequences(keySequences); +} + +KeySequenceRegistry::KeySequenceRegistry(QObject *parent) + : QAbstractListModel{parent} +{ + s_instance = this; +} + +KeySequenceRegistry * +KeySequenceRegistry::instance() +{ + if (!s_instance) + s_instance = new KeySequenceRegistry; + return s_instance; +} + +KeySequenceRegistry * +KeySequenceRegistry::create(QQmlEngine *qmlEngine, QJSEngine *) +{ + // The instance has to exist before it is used. We cannot replace it. + Q_ASSERT(s_instance); + + // The engine has to have the same thread affinity as the singleton. + Q_ASSERT(qmlEngine->thread() == s_instance->thread()); + + // There can only be one engine accessing the singleton. + static QJSEngine *s_engine = nullptr; + if (s_engine) + Q_ASSERT(qmlEngine == s_engine); + else + s_engine = qmlEngine; + + QJSEngine::setObjectOwnership(s_instance, QJSEngine::CppOwnership); + return s_instance; +} + +QHash +KeySequenceRegistry::roleNames() const +{ + return {{Roles::Name, "name"}, {Roles::KeySequence, "keySequence"}}; +} + +QVariant +KeySequenceRegistry::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= m_keySequences.size() || index.row() < 0) + return {}; + + switch (role) { + case Roles::Name: + return m_keySequences.at(index.row())->name(); + case Roles::KeySequence: { + const auto &data = m_keySequences.at(index.row())->keySequences(); + return data.size() > 0 ? data.first() : QString{}; + } + default: + return {}; + } +} + +void +KeySequenceRegistry::changeKeySequence(const QString &name, const QString &newKeySequence) +{ + for (int i = 0; i < m_keySequences.size(); ++i) { + if (m_keySequences.at(i)->name() == name) { + m_keySequences.at(i)->setKeySequences({newKeySequence}); + emit dataChanged(index(i), index(i), {Roles::KeySequence}); + return; + } + } +} + +QString +KeySequenceRegistry::keycodeToChar(int keycode) const +{ + return QString((char)keycode); +} + +QMap +KeySequenceRegistry::dumpBindings() const +{ + QMap bindings; + for (const auto sequence : m_keySequences) + bindings[sequence->name()] = sequence->keySequences(); + return bindings; +} + +void +KeySequenceRegistry::restoreBindings(const QMap &bindings) +{ + for (const auto &[name, keySequences] : bindings.asKeyValueRange()) { + if (auto it = std::find_if(m_keySequences.begin(), + m_keySequences.end(), + [&name](const auto &impl) { return impl->name() == name; }); + it != m_keySequences.end()) + (*it)->setKeySequences(keySequences); + else + m_keySequences.push_back(new KeySequenceImpl{name, keySequences}); + } +} + +void +KeySequenceRegistry::registerKeySequence(EditableKeySequence *action) +{ + if (action->name().isEmpty()) + return; + + KeySequenceImpl *impl = nullptr; + if (auto it = + std::find_if(m_keySequences.begin(), + m_keySequences.end(), + [action](const auto &impl) { return impl->name() == action->name(); }); + it != m_keySequences.end()) + impl = *it; + else { + impl = new KeySequenceImpl{action->name(), action->keySequences()}; + m_keySequences.push_back(impl); + } + + action->m_impl = impl; + connect(impl, + &KeySequenceImpl::keySequencesChanged, + action, + &EditableKeySequence::keySequencesChanged); + emit action->keySequencesChanged(); +} diff --git a/src/ui/KeySequenceRegistry.h b/src/ui/KeySequenceRegistry.h new file mode 100644 index 00000000..a00f4005 --- /dev/null +++ b/src/ui/KeySequenceRegistry.h @@ -0,0 +1,116 @@ +// SPDX-FileCopyrightText: Nheko Contributors +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include +#include +#include + +class KeySequenceImpl : public QObject +{ + Q_OBJECT + +public: + KeySequenceImpl(const QString &name, + const QStringList &keySequences, + QObject *parent = nullptr); + + const QString &name() const { return m_name; } + const QStringList &keySequences() const { return m_keySequences; } + + void setKeySequences(const QStringList &keySequences); + +signals: + void keySequencesChanged(); + +private: + const QString m_name; + QStringList m_keySequences; +}; + +class EditableKeySequence : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged FINAL) + Q_PROPERTY( + QString keySequence READ keySequence WRITE setKeySequence NOTIFY keySequencesChanged FINAL) + Q_PROPERTY(QStringList keySequences READ keySequences WRITE setKeySequences NOTIFY + keySequencesChanged FINAL) + Q_PROPERTY(QString defaultKeySequence READ defaultKeySequence WRITE setDefaultKeySequence NOTIFY + defaultKeySequencesChanged FINAL) + Q_PROPERTY(QStringList defaultKeySequences READ defaultKeySequences WRITE setDefaultKeySequences + NOTIFY defaultKeySequencesChanged FINAL) + +public: + EditableKeySequence(QObject *parent = nullptr); + EditableKeySequence(const QString &name, QObject *parent = nullptr); + + const QString &name() const { return m_name; } + const QString keySequence() const; + const QStringList keySequences() const; + const QString defaultKeySequence() const; + const QStringList defaultKeySequences() const; + + void setName(const QString &name); + void setKeySequence(const QString &keySequence); + void setKeySequences(const QStringList &keySequences); + void setDefaultKeySequence(const QString &keySequence); + void setDefaultKeySequences(const QStringList &keySequences); + +signals: + void nameChanged(); + void keySequencesChanged(); + void defaultKeySequencesChanged(); + +private: + QString m_name; + QList m_defaultKeySequences; + + KeySequenceImpl *m_impl = nullptr; + + friend class KeySequenceRegistry; +}; + +class KeySequenceRegistry : public QAbstractListModel +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + enum Roles + { + Name, + KeySequence, + }; + + static KeySequenceRegistry *instance(); + static KeySequenceRegistry *create(QQmlEngine *qmlEngine, QJSEngine *); + + QHash roleNames() const override; + int rowCount(const QModelIndex & = QModelIndex()) const override + { + return m_keySequences.size(); + } + QVariant data(const QModelIndex &index, int role) const override; + + Q_INVOKABLE void changeKeySequence(const QString &name, const QString &newKeysequence); + Q_INVOKABLE QString keycodeToChar(int keycode) const; + + QMap dumpBindings() const; + void restoreBindings(const QMap &bindings); + +private: + explicit KeySequenceRegistry(QObject *parent = nullptr); + + void registerKeySequence(EditableKeySequence *action); + + static KeySequenceRegistry *s_instance; + QList m_keySequences; + + friend EditableKeySequence; +}; diff --git a/src/ui/ShortcutRegistry.cpp b/src/ui/ShortcutRegistry.cpp deleted file mode 100644 index 5c32b8cb..00000000 --- a/src/ui/ShortcutRegistry.cpp +++ /dev/null @@ -1,158 +0,0 @@ -// 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} -{ - 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 -{ - QStringList dest; - dest.resize(m_shortcuts.size()); - std::transform(m_shortcuts.begin(), m_shortcuts.end(), dest.begin(), [](const auto &shortcut) { - return shortcut.toString(); - }); - return dest; -} - -void -EditableShortcut::setName(const QString &name) -{ - if (name == m_name) - return; - m_name = name; - emit nameChanged(); -} - -void -EditableShortcut::setDescription(const QString &description) -{ - if (description == m_description) - return; - m_description = description; - emit descriptionChanged(); -} - -void -EditableShortcut::setShortcut(const QString &shortcut) -{ - setShortcuts({shortcut}); -} - -void -EditableShortcut::setShortcuts(const QStringList &shortcuts) -{ - QList temp; - temp.resize(shortcuts.size()); - std::transform(shortcuts.begin(), shortcuts.end(), temp.begin(), [](const auto &shortcut) { - return QKeySequence(shortcut); - }); - - if (temp == m_shortcuts) - return; - m_shortcuts = temp; - emit shortcutsChanged(); -} - -ShortcutRegistry::ShortcutRegistry(QObject *parent) - : QAbstractListModel{parent} -{ - if (s_instance) - m_shortcuts = s_instance->m_shortcuts; - - s_instance = this; -} - -ShortcutRegistry * -ShortcutRegistry::instance() -{ - return s_instance; -} - -ShortcutRegistry * -ShortcutRegistry::create(QQmlEngine *qmlEngine, QJSEngine *) -{ - // The instance has to exist before it is used. We cannot replace it. - Q_ASSERT(s_instance); - - // The engine has to have the same thread affinity as the singleton. - Q_ASSERT(qmlEngine->thread() == s_instance->thread()); - - // There can only be one engine accessing the singleton. - static QJSEngine *s_engine = nullptr; - if (s_engine) - Q_ASSERT(qmlEngine == s_engine); - else - s_engine = qmlEngine; - - QJSEngine::setObjectOwnership(s_instance, QJSEngine::CppOwnership); - return s_instance; -} - -QHash -ShortcutRegistry::roleNames() const -{ - return { - {Roles::Name, "name"}, {Roles::Description, "description"}, {Roles::Shortcut, "shortcut"}}; -} - -QVariant -ShortcutRegistry::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() || index.row() >= m_shortcuts.size() || index.row() < 0) - return {}; - - switch (role) { - case Roles::Name: - return m_shortcuts[index.row()]->name(); - case Roles::Description: - return m_shortcuts[index.row()]->description(); - case Roles::Shortcut: - return m_shortcuts[index.row()]->shortcut(); - default: - return {}; - } -} - -void -ShortcutRegistry::changeShortcut(const QString &name, const QString &newShortcut) -{ - 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; - } - } -} - -QString -ShortcutRegistry::keycodeToChar(int keycode) const -{ - return QString((char)keycode); -} - -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 deleted file mode 100644 index c2fe6265..00000000 --- a/src/ui/ShortcutRegistry.h +++ /dev/null @@ -1,84 +0,0 @@ -// SPDX-FileCopyrightText: Nheko Contributors -// -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include -#include -#include - -class EditableShortcut : public QObject -{ - Q_OBJECT - 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 shortcut READ shortcut WRITE setShortcut 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); - - const QString &name() const { return m_name; } - const QString &description() const { return m_description; } - const QString shortcut() const - { - return m_shortcuts.size() > 0 ? m_shortcuts.first().toString() : QString{}; - } - const QStringList shortcuts() const; - - void setName(const QString &name); - void setDescription(const QString &description); - void setShortcut(const QString &shortcut); - void setShortcuts(const QStringList &shortcuts); - -signals: - void nameChanged(); - void descriptionChanged(); - void shortcutsChanged(); - -private: - QString m_name; - QString m_description; - QList m_shortcuts; -}; - -class ShortcutRegistry : public QAbstractListModel -{ - Q_OBJECT - QML_ELEMENT - QML_SINGLETON - -public: - enum Roles - { - Name, - Description, - Shortcut, - }; - - explicit ShortcutRegistry(QObject *parent = nullptr); - - static ShortcutRegistry *instance(); - static ShortcutRegistry *create(QQmlEngine *qmlEngine, QJSEngine *); - - QHash roleNames() const override; - int rowCount(const QModelIndex & = QModelIndex()) const override { return m_shortcuts.size(); } - QVariant data(const QModelIndex &index, int role) const override; - - 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; - QList m_shortcuts; - - friend EditableShortcut; -};