Begin work on adding editable shortcuts

This will eventually allow users to assign arbitrary shortcuts to
actions to give them the ability to have shortcuts for everything(tm).
pull/1568/head
Loren Burkholder 1 year ago
parent 03be9e479a
commit 66ade755eb
  1. 2
      CMakeLists.txt
  2. 42
      resources/qml/Root.qml
  3. 1
      resources/qml/TopBar.qml
  4. 2
      resources/qml/dialogs/ImageOverlay.qml
  5. 140
      src/ui/ShortcutRegistry.cpp
  6. 81
      src/ui/ShortcutRegistry.h

@ -397,6 +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/Theme.cpp
src/ui/Theme.h
src/ui/UIA.cpp

@ -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()
}

@ -395,6 +395,7 @@ Pane {
onActivated: searchButton.searchActive = !searchButton.searchActive
}
TapHandler {
gesturePolicy: TapHandler.ReleaseWithinBounds

@ -25,7 +25,7 @@ Window {
Component.onCompleted: Nheko.setWindowRole(imageOverlay, "imageoverlay")
Shortcut {
sequences: [StandardKey.Cancel]
sequence: StandardKey.Cancel
onActivated: imageOverlay.close()
}

@ -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<QKeySequence> 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<int, QByteArray> 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);
}

@ -0,0 +1,81 @@
#pragma once
#include <QAbstractListModel>
#include <QAction>
#include <QQmlEngine>
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<QKeySequence> 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<int, QByteArray> 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<EditableShortcut *> m_shortcuts;
friend EditableShortcut;
};
Loading…
Cancel
Save