diff --git a/CMakeLists.txt b/CMakeLists.txt index e9fe8cd4..9eb8df67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -396,7 +396,9 @@ set(SRC_FILES src/ui/RoomSettings.cpp src/ui/RoomSettings.h src/ui/RoomSummary.cpp - src/ui/RoomSummary.h + src/ui/RoomSummary.h + src/ui/ShortcutRegistry.cpp + src/ui/ShortcutRegistry.h src/ui/Theme.cpp src/ui/Theme.h src/ui/UIA.cpp @@ -810,7 +812,7 @@ qt_add_qml_module(nheko ${QML_SOURCES} SOURCES src/UserDirectoryModel.cpp - src/UserDirectoryModel.h + src/UserDirectoryModel.h ) #qt_target_qml_sources(nheko # #PREFIX "/" diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 1e8a6a27..6cdfc99c 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -111,8 +111,16 @@ Pane { onActivated: Qt.quit() } + + EditableShortcut { + id: quickSwitcherShortcut + + name: qsTr("Room search") + description: qsTr("Opens a search bar for quick switching between rooms") + shortcut: "Ctrl+K" + } Shortcut { - sequence: "Ctrl+K" + sequence: quickSwitcherShortcut.shortcut onActivated: { var component = Qt.createComponent("qrc:/resources/qml/QuickSwitcher.qml"); @@ -125,19 +133,43 @@ Pane { } } } - Shortcut { + + EditableShortcut { + 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 - sequences: ["Alt+A", "Ctrl+Shift+A"] + shortcuts: ["Alt+A", "Ctrl+Shift+A"] + } + Shortcut { + sequences: nextRoomWithActivityShortcut.shortcuts onActivated: Rooms.nextRoomWithActivity() } + + EditableShortcut { + id: nextRoomShortcut + + name: qsTr("Next room") + description: qsTr("Switches to the room below the room that is currently open") + shortcut: "Ctrl+Down" + } Shortcut { - sequence: "Ctrl+Down" + sequence: nextRoomShortcut.shortcut onActivated: Rooms.nextRoom() } + + EditableShortcut { + id: previousRoomShortcut + + name: qsTr("Previous room") + description: qsTr("Switches to the room above the room that is currently open") + shortcut: "Ctrl+Up" + } Shortcut { - sequence: "Ctrl+Up" + sequence: previousRoomShortcut.shortcut onActivated: Rooms.previousRoom() } diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 699595e6..d1ba6f73 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -395,6 +395,7 @@ Pane { onActivated: searchButton.searchActive = !searchButton.searchActive } + TapHandler { gesturePolicy: TapHandler.ReleaseWithinBounds diff --git a/resources/qml/dialogs/ImageOverlay.qml b/resources/qml/dialogs/ImageOverlay.qml index b914829e..e67e5b3d 100644 --- a/resources/qml/dialogs/ImageOverlay.qml +++ b/resources/qml/dialogs/ImageOverlay.qml @@ -25,7 +25,7 @@ Window { Component.onCompleted: Nheko.setWindowRole(imageOverlay, "imageoverlay") Shortcut { - sequences: [StandardKey.Cancel] + sequence: StandardKey.Cancel onActivated: imageOverlay.close() } diff --git a/src/ui/ShortcutRegistry.cpp b/src/ui/ShortcutRegistry.cpp new file mode 100644 index 00000000..a9a47e3f --- /dev/null +++ b/src/ui/ShortcutRegistry.cpp @@ -0,0 +1,140 @@ +#include "ShortcutRegistry.h" + +ShortcutRegistry *ShortcutRegistry::s_instance = nullptr; + +EditableShortcut::EditableShortcut(QObject *parent) + : QObject{parent} +{ +} + +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(); +} + +EditableShortcut::EditableShortcut(const QString &name, const QString &description, QObject *parent) + : QObject{parent} + , m_name{name} + , m_description{description} +{ + ShortcutRegistry::instance()->registerShortcut(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 {}; + } +} + +bool ShortcutRegistry::setData(const QModelIndex &index, const QVariant &value, int role) +{ + 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; + } +} + +ShortcutRegistry::ShortcutRegistry(QObject *parent) + : QAbstractListModel{parent} +{ + s_instance = this; +} + +void ShortcutRegistry::registerShortcut(EditableShortcut *action) +{ + m_shortcuts.push_back(action); +} diff --git a/src/ui/ShortcutRegistry.h b/src/ui/ShortcutRegistry.h new file mode 100644 index 00000000..c075b50c --- /dev/null +++ b/src/ui/ShortcutRegistry.h @@ -0,0 +1,81 @@ +#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); + 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; } + 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, + }; + + 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; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + +private: + explicit ShortcutRegistry(QObject *parent = nullptr); + + void registerShortcut(EditableShortcut *action); + + static ShortcutRegistry *s_instance; + QList m_shortcuts; + + friend EditableShortcut; +};