mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-21 18:50:47 +03:00
Add working shortcut serialization
This commit is contained in:
parent
72410c499d
commit
56dcdd7fd4
10 changed files with 464 additions and 289 deletions
|
@ -397,8 +397,8 @@ set(SRC_FILES
|
||||||
src/ui/RoomSettings.h
|
src/ui/RoomSettings.h
|
||||||
src/ui/RoomSummary.cpp
|
src/ui/RoomSummary.cpp
|
||||||
src/ui/RoomSummary.h
|
src/ui/RoomSummary.h
|
||||||
src/ui/ShortcutRegistry.cpp
|
src/ui/KeySequenceRegistry.cpp
|
||||||
src/ui/ShortcutRegistry.h
|
src/ui/KeySequenceRegistry.h
|
||||||
src/ui/Theme.cpp
|
src/ui/Theme.cpp
|
||||||
src/ui/Theme.h
|
src/ui/Theme.h
|
||||||
src/ui/UIA.cpp
|
src/ui/UIA.cpp
|
||||||
|
|
|
@ -112,15 +112,14 @@ Pane {
|
||||||
onActivated: Qt.quit()
|
onActivated: Qt.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableShortcut {
|
EditableKeySequence {
|
||||||
id: quickSwitcherShortcut
|
id: quickSwitcherShortcut
|
||||||
|
|
||||||
name: qsTr("Room search")
|
name: qsTr("Room search")
|
||||||
description: qsTr("Opens a search bar for quick switching between rooms")
|
defaultKeySequence: "Ctrl+K"
|
||||||
shortcut: "Ctrl+K"
|
|
||||||
}
|
}
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: quickSwitcherShortcut.shortcut
|
sequence: quickSwitcherShortcut.keySequence
|
||||||
|
|
||||||
onActivated: {
|
onActivated: {
|
||||||
var component = Qt.createComponent("qrc:/resources/qml/QuickSwitcher.qml");
|
var component = Qt.createComponent("qrc:/resources/qml/QuickSwitcher.qml");
|
||||||
|
@ -134,45 +133,98 @@ Pane {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableShortcut {
|
EditableKeySequence {
|
||||||
id: nextRoomWithActivityShortcut
|
id: nextRoomWithActivityShortcut
|
||||||
|
|
||||||
name: qsTr("Next room with activity")
|
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
|
// 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 {
|
Shortcut {
|
||||||
sequences: nextRoomWithActivityShortcut.shortcuts
|
sequences: nextRoomWithActivityShortcut.keySequence
|
||||||
|
|
||||||
onActivated: Rooms.nextRoomWithActivity()
|
onActivated: Rooms.nextRoomWithActivity()
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableShortcut {
|
EditableKeySequence {
|
||||||
id: nextRoomShortcut
|
id: nextRoomShortcut
|
||||||
|
|
||||||
name: qsTr("Next room")
|
name: qsTr("Next room")
|
||||||
description: qsTr("Switches to the room below the room that is currently open")
|
defaultKeySequence: "Ctrl+Down"
|
||||||
shortcut: "Ctrl+Down"
|
|
||||||
}
|
}
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: nextRoomShortcut.shortcut
|
sequence: nextRoomShortcut.keySequence
|
||||||
|
|
||||||
onActivated: Rooms.nextRoom()
|
onActivated: Rooms.nextRoom()
|
||||||
}
|
}
|
||||||
|
|
||||||
EditableShortcut {
|
EditableKeySequence {
|
||||||
id: previousRoomShortcut
|
id: previousRoomShortcut
|
||||||
|
|
||||||
name: qsTr("Previous room")
|
name: qsTr("Previous room")
|
||||||
description: qsTr("Switches to the room above the room that is currently open")
|
defaultKeySequence: "Ctrl+Up"
|
||||||
shortcut: "Ctrl+Up"
|
|
||||||
}
|
}
|
||||||
Shortcut {
|
Shortcut {
|
||||||
sequence: previousRoomShortcut.shortcut
|
sequence: previousRoomShortcut.keySequence
|
||||||
|
|
||||||
onActivated: Rooms.previousRoom()
|
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 {
|
Connections {
|
||||||
function onOpenJoinRoomDialog() {
|
function onOpenJoinRoomDialog() {
|
||||||
var component = Qt.createComponent("qrc:/resources/qml/dialogs/JoinRoomDialog.qml");
|
var component = Qt.createComponent("qrc:/resources/qml/dialogs/JoinRoomDialog.qml");
|
||||||
|
|
|
@ -28,42 +28,31 @@ ApplicationWindow {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
model: ShortcutRegistry
|
model: KeySequenceRegistry
|
||||||
|
|
||||||
delegate: RowLayout {
|
delegate: RowLayout {
|
||||||
id: del
|
id: del
|
||||||
|
|
||||||
required property string name
|
required property string name
|
||||||
required property string description
|
required property string keySequence
|
||||||
required property string shortcut
|
|
||||||
|
|
||||||
spacing: Nheko.paddingMedium
|
spacing: Nheko.paddingMedium
|
||||||
width: ListView.view.width
|
width: ListView.view.width
|
||||||
height: implicitHeight + Nheko.paddingSmall * 2
|
height: implicitHeight + Nheko.paddingSmall * 2
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
spacing: Nheko.paddingSmall
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: del.name
|
text: del.name
|
||||||
font.bold: true
|
|
||||||
font.pointSize: fontMetrics.font.pointSize * 1.1
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: del.description
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item { Layout.fillWidth: true }
|
Item { Layout.fillWidth: true }
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
property bool selectingNewShortcut: false
|
property bool selectingNewKeySequence: false
|
||||||
|
|
||||||
text: selectingNewShortcut ? qsTr("Input..") : del.shortcut
|
text: selectingNewKeySequence ? qsTr("Input..") : (del.keySequence === "" ? "None" : del.keySequence)
|
||||||
onClicked: selectingNewShortcut = !selectingNewShortcut
|
onClicked: selectingNewKeySequence = !selectingNewKeySequence
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (!selectingNewShortcut)
|
if (!selectingNewKeySequence)
|
||||||
return;
|
return;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
|
||||||
|
@ -77,12 +66,14 @@ ApplicationWindow {
|
||||||
if (event.modifiers & Qt.ShiftModifier)
|
if (event.modifiers & Qt.ShiftModifier)
|
||||||
keySequence += "Shift+";
|
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 += "...";
|
keySequence += "...";
|
||||||
else {
|
else {
|
||||||
keySequence += ShortcutRegistry.keycodeToChar(event.key);
|
keySequence += KeySequenceRegistry.keycodeToChar(event.key);
|
||||||
ShortcutRegistry.changeShortcut(del.name, keySequence);
|
KeySequenceRegistry.changeKeySequence(del.name, keySequence);
|
||||||
selectingNewShortcut = false;
|
selectingNewKeySequence = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,6 @@ 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,7 +11,7 @@
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
|
|
||||||
#include "ShortcutRegistry.h"
|
#include "KeySequenceRegistry.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "dock/Dock.h"
|
#include "dock/Dock.h"
|
||||||
|
|
||||||
|
@ -141,7 +141,6 @@ 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;
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,10 @@ QSharedPointer<UserSettings> UserSettings::instance_;
|
||||||
|
|
||||||
UserSettings::UserSettings()
|
UserSettings::UserSettings()
|
||||||
{
|
{
|
||||||
connect(
|
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() {
|
||||||
QCoreApplication::instance(), &QCoreApplication::aboutToQuit, []() { instance_.clear(); });
|
instance_->save();
|
||||||
|
instance_.clear();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<UserSettings>
|
QSharedPointer<UserSettings>
|
||||||
|
@ -164,6 +166,16 @@ UserSettings::load(std::optional<QString> profile)
|
||||||
disableCertificateValidation_ =
|
disableCertificateValidation_ =
|
||||||
settings.value(QStringLiteral("disable_certificate_validation"), false).toBool();
|
settings.value(QStringLiteral("disable_certificate_validation"), false).toBool();
|
||||||
|
|
||||||
|
settings.beginGroup(QStringLiteral("user"));
|
||||||
|
settings.beginGroup(QStringLiteral("shortcuts"));
|
||||||
|
QMap<QString, QStringList> 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();
|
applyTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -882,7 +894,7 @@ UserSettings::save()
|
||||||
settings.beginGroup(QStringLiteral("sidebar"));
|
settings.beginGroup(QStringLiteral("sidebar"));
|
||||||
settings.setValue(QStringLiteral("community_list_width"), communityListWidth_);
|
settings.setValue(QStringLiteral("community_list_width"), communityListWidth_);
|
||||||
settings.setValue(QStringLiteral("room_list_width"), roomListWidth_);
|
settings.setValue(QStringLiteral("room_list_width"), roomListWidth_);
|
||||||
settings.endGroup(); // window
|
settings.endGroup(); // sidebar
|
||||||
|
|
||||||
settings.beginGroup(QStringLiteral("timeline"));
|
settings.beginGroup(QStringLiteral("timeline"));
|
||||||
settings.setValue(QStringLiteral("buttons"), buttonsInTimeline_);
|
settings.setValue(QStringLiteral("buttons"), buttonsInTimeline_);
|
||||||
|
@ -939,6 +951,13 @@ UserSettings::save()
|
||||||
settings.setValue(QStringLiteral("space_background_maintenance"), updateSpaceVias_);
|
settings.setValue(QStringLiteral("space_background_maintenance"), updateSpaceVias_);
|
||||||
settings.setValue(QStringLiteral("expired_events_background_maintenance"), expireEvents_);
|
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
|
settings.endGroup(); // user
|
||||||
|
|
||||||
QString prefix = (profile_ != QLatin1String("") && profile_ != QLatin1String("default"))
|
QString prefix = (profile_ != QLatin1String("") && profile_ != QLatin1String("default"))
|
||||||
|
|
241
src/ui/KeySequenceRegistry.cpp
Normal file
241
src/ui/KeySequenceRegistry.cpp
Normal file
|
@ -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<QKeySequence> 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<int, QByteArray>
|
||||||
|
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<QString, QStringList>
|
||||||
|
KeySequenceRegistry::dumpBindings() const
|
||||||
|
{
|
||||||
|
QMap<QString, QStringList> bindings;
|
||||||
|
for (const auto sequence : m_keySequences)
|
||||||
|
bindings[sequence->name()] = sequence->keySequences();
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
KeySequenceRegistry::restoreBindings(const QMap<QString, QStringList> &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();
|
||||||
|
}
|
116
src/ui/KeySequenceRegistry.h
Normal file
116
src/ui/KeySequenceRegistry.h
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
// SPDX-FileCopyrightText: Nheko Contributors
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
#include <QKeySequence>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
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<QKeySequence> 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<int, QByteArray> 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<QString, QStringList> dumpBindings() const;
|
||||||
|
void restoreBindings(const QMap<QString, QStringList> &bindings);
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit KeySequenceRegistry(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void registerKeySequence(EditableKeySequence *action);
|
||||||
|
|
||||||
|
static KeySequenceRegistry *s_instance;
|
||||||
|
QList<KeySequenceImpl *> m_keySequences;
|
||||||
|
|
||||||
|
friend EditableKeySequence;
|
||||||
|
};
|
|
@ -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<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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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<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 {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Nheko Contributors
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
|
||||||
#include <QKeySequence>
|
|
||||||
#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);
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
|
||||||
|
|
||||||
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(); }
|
|
||||||
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<EditableShortcut *> m_shortcuts;
|
|
||||||
|
|
||||||
friend EditableShortcut;
|
|
||||||
};
|
|
Loading…
Reference in a new issue