From 3eade9c3cf1baa8c970437b6befe0d6424b0a24a Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Tue, 29 Oct 2024 23:28:27 +0800 Subject: [PATCH] feat: support custom shortcut for existing actions Resolve https://github.com/BLumia/pineapple-pictures/issues/128 Actually also https://github.com/BLumia/pineapple-pictures/issues/72 but not sure why that one is now a 404 page. --- CMakeLists.txt | 2 + app/mainwindow.cpp | 1 + app/settings.cpp | 37 ++++++++++++ app/settings.h | 5 +- app/settingsdialog.cpp | 43 ++++++++++++- app/shortcutedit.cpp | 133 +++++++++++++++++++++++++++++++++++++++++ app/shortcutedit.h | 54 +++++++++++++++++ 7 files changed, 273 insertions(+), 2 deletions(-) create mode 100644 app/shortcutedit.cpp create mode 100644 app/shortcutedit.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d4f3c1c..c0872d8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,6 +66,7 @@ set (PPIC_CPP_FILES app/metadatadialog.cpp app/exiv2wrapper.cpp app/playlistmanager.cpp + app/shortcutedit.cpp ) set (PPIC_HEADER_FILES @@ -85,6 +86,7 @@ set (PPIC_HEADER_FILES app/metadatadialog.h app/exiv2wrapper.h app/playlistmanager.h + app/shortcutedit.h ) set (PPIC_QRC_FILES diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 3e28251..2a54516 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -151,6 +151,7 @@ MainWindow::MainWindow(QWidget *parent) QTimer::singleShot(0, this, [this](){ m_am->setupShortcuts(); + Settings::instance()->applyUserShortcuts(this); }); // allow some mouse events can go through these widgets for resizing window. diff --git a/app/settings.cpp b/app/settings.cpp index c9a7506..768f1ab 100644 --- a/app/settings.cpp +++ b/app/settings.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include namespace QEnumHelper @@ -116,6 +118,39 @@ void Settings::setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy m_qsettings->sync(); } +void Settings::applyUserShortcuts(QWidget *widget) +{ + m_qsettings->beginGroup("shortcuts"); + const QStringList shortcutNames = m_qsettings->allKeys(); + for (const QString & name : shortcutNames) { + QList shortcuts = m_qsettings->value(name).value>(); + setShortcutsForAction(widget, name, shortcuts, false); + } + m_qsettings->endGroup(); +} + +bool Settings::setShortcutsForAction(QWidget *widget, const QString &objectName, + QList shortcuts, bool writeConfig) +{ + bool result = false; + for (QAction * action : widget->actions()) { + if (action->objectName() == objectName) { + action->setShortcuts(shortcuts); + result = true; + break; + } + } + + if (result && writeConfig) { + m_qsettings->beginGroup("shortcuts"); + m_qsettings->setValue(objectName, QVariant::fromValue(shortcuts)); + m_qsettings->endGroup(); + m_qsettings->sync(); + } + + return result; +} + #if defined(FLAG_PORTABLE_MODE_SUPPORT) && defined(Q_OS_WIN) #include // QCoreApplication::applicationDirPath() parses the "applicationDirPath" from arg0, which... @@ -154,5 +189,7 @@ Settings::Settings() } m_qsettings = new QSettings(QDir(configPath).absoluteFilePath("config.ini"), QSettings::IniFormat, this); + + qRegisterMetaType>(); } diff --git a/app/settings.h b/app/settings.h index ed5cc37..ea807b5 100644 --- a/app/settings.h +++ b/app/settings.h @@ -46,6 +46,10 @@ public: void setInitWindowSizeBehavior(WindowSizeBehavior wsb); void setHiDpiScaleFactorBehavior(Qt::HighDpiScaleFactorRoundingPolicy hidpi); + void applyUserShortcuts(QWidget * widget); + bool setShortcutsForAction(QWidget * widget, const QString & objectName, + QList shortcuts, bool writeConfig = true); + private: Settings(); @@ -57,4 +61,3 @@ signals: public slots: }; - diff --git a/app/settingsdialog.cpp b/app/settingsdialog.cpp index ae3b5b2..74a8571 100644 --- a/app/settingsdialog.cpp +++ b/app/settingsdialog.cpp @@ -5,10 +5,14 @@ #include "settingsdialog.h" #include "settings.h" +#include "shortcutedit.h" #include #include #include +#include +#include +#include #include SettingsDialog::SettingsDialog(QWidget *parent) @@ -22,7 +26,44 @@ SettingsDialog::SettingsDialog(QWidget *parent) { this->setWindowTitle(tr("Settings")); - QFormLayout * settingsForm = new QFormLayout(this); + QHBoxLayout * mainLayout = new QHBoxLayout(this); + QTabWidget * settingsTabs = new QTabWidget(this); + mainLayout->addWidget(settingsTabs); + + QWidget * settingsFormHolder = new QWidget; + QFormLayout * settingsForm = new QFormLayout(settingsFormHolder); + settingsTabs->addTab(settingsFormHolder, tr("Options")); + + QSplitter * shortcutEditorSplitter = new QSplitter; + shortcutEditorSplitter->setOrientation(Qt::Vertical); + shortcutEditorSplitter->setChildrenCollapsible(false); + QScrollArea * shortcutScrollArea = new QScrollArea; + shortcutEditorSplitter->addWidget(shortcutScrollArea); + shortcutScrollArea->setWidgetResizable(true); + shortcutScrollArea->setMinimumHeight(200); + QWidget * shortcutsFormHolder = new QWidget; + QFormLayout * shortcutsForm = new QFormLayout(shortcutsFormHolder); + shortcutScrollArea->setWidget(shortcutsFormHolder); + settingsTabs->addTab(shortcutEditorSplitter, tr("Shortcuts")); + + for (const QAction * action : parent->actions()) { + ShortcutEdit * shortcutEdit = new ShortcutEdit; + shortcutEdit->setObjectName(QLatin1String("shortcut_") + action->objectName()); + shortcutEdit->setShortcuts(action->shortcuts()); + shortcutsForm->addRow(action->text(), shortcutEdit); + connect(shortcutEdit, &ShortcutEdit::editButtonClicked, this, [=](){ + if (shortcutEditorSplitter->count() == 1) shortcutEditorSplitter->addWidget(new QWidget); + ShortcutEditor * shortcutEditor = new ShortcutEditor(shortcutEdit); + shortcutEditor->setDescription(tr("Editing shortcuts for action \"%1\":").arg(action->text())); + QWidget * oldEditor = shortcutEditorSplitter->replaceWidget(1, shortcutEditor); + shortcutEditorSplitter->setSizes({shortcutEditorSplitter->height(), 1}); + oldEditor->deleteLater(); + }); + connect(shortcutEdit, &ShortcutEdit::shortcutsChanged, this, [=](){ + Settings::instance()->setShortcutsForAction(parent, shortcutEdit->objectName().mid(9), + shortcutEdit->shortcuts()); + }); + } static QList< QPair > _dc_options { { Settings::DoubleClickBehavior::Ignore, tr("Do nothing") }, diff --git a/app/shortcutedit.cpp b/app/shortcutedit.cpp new file mode 100644 index 0000000..3f4f82e --- /dev/null +++ b/app/shortcutedit.cpp @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + +#include "shortcutedit.h" + +#include +#include +#include +#include +#include +#include + +ShortcutEditor::ShortcutEditor(ShortcutEdit * shortcutEdit, QWidget * parent) + : QWidget(parent) + , m_descriptionLabel(new QLabel) + , m_shortcutEdit(shortcutEdit) + , m_shortcutLayout(new QFormLayout) +{ + Q_CHECK_PTR(m_shortcutEdit); + + QDialogButtonBox * buttons = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Discard); + + QVBoxLayout * layout = new QVBoxLayout(this); + layout->addWidget(m_descriptionLabel); + layout->addLayout(m_shortcutLayout); + layout->addWidget(buttons); + + connect(buttons, &QDialogButtonBox::clicked, this, [=](QAbstractButton *button){ + if ((QPushButton *)button == buttons->button(QDialogButtonBox::Apply)) { + applyShortcuts(); + } else { + reloadShortcuts(); + } + }); + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + + reloadShortcuts(); +} + +ShortcutEditor::~ShortcutEditor() +{ + +} + +void ShortcutEditor::setDescription(const QString &desc) +{ + m_descriptionLabel->setText(desc); +} + +void ShortcutEditor::reloadShortcuts() +{ + if (!m_keySequenceEdits.isEmpty()) { + for (QKeySequenceEdit * keyseqEdit : m_keySequenceEdits) { + m_shortcutLayout->removeRow(keyseqEdit); + } + m_keySequenceEdits.clear(); + } + + QList shortcuts = m_shortcutEdit->shortcuts(); + shortcuts.append(QKeySequence()); + for (const QKeySequence & shortcut : shortcuts) { + QKeySequenceEdit * keyseqEdit = new QKeySequenceEdit(this); +#if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) + keyseqEdit->setClearButtonEnabled(true); +#endif // QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + keyseqEdit->setMaximumSequenceLength(1); +#endif // QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + keyseqEdit->setKeySequence(shortcut); + m_keySequenceEdits.append(keyseqEdit); + } + + for (int i = 0; i < m_keySequenceEdits.count(); i++) { + m_shortcutLayout->addRow(tr("Shortcut #%1").arg(i + 1), m_keySequenceEdits.at(i)); + } +} + +void ShortcutEditor::applyShortcuts() +{ + QList shortcuts; + for (const QKeySequenceEdit * keyseqEdit : m_keySequenceEdits) { + if (!keyseqEdit->keySequence().isEmpty()) { + shortcuts.append(keyseqEdit->keySequence()); + } + } + m_shortcutEdit->setShortcuts(shortcuts); + reloadShortcuts(); +} + +// ---------------------------------------- + +ShortcutEdit::ShortcutEdit(QWidget *parent) + : QWidget(parent) + , m_shortcutsLabel(new QLabel(this)) + , m_setShortcutButton(new QToolButton(this)) +{ + m_setShortcutButton->setText("..."); + + QHBoxLayout * layout = new QHBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(m_shortcutsLabel, 1); + layout->addWidget(m_setShortcutButton); + + connect(this, &ShortcutEdit::shortcutsChanged, this, [=](){ + QStringList shortcutTexts; + for (const QKeySequence & shortcut : m_shortcuts) { + shortcutTexts.append(shortcut.toString()); + } + m_shortcutsLabel->setText(shortcutTexts.isEmpty() ? tr("No shortcuts") : shortcutTexts.join(", ")); + m_shortcutsLabel->setDisabled(shortcutTexts.isEmpty()); + }); + + connect(m_setShortcutButton, &QToolButton::clicked, this, &ShortcutEdit::editButtonClicked); + + adjustSize(); +} + +ShortcutEdit::~ShortcutEdit() +{ +} + +QList ShortcutEdit::shortcuts() const +{ + return m_shortcuts; +} + +void ShortcutEdit::setShortcuts(const QList &shortcuts) +{ + m_shortcuts = shortcuts; + emit shortcutsChanged(); +} diff --git a/app/shortcutedit.h b/app/shortcutedit.h new file mode 100644 index 0000000..ef8da03 --- /dev/null +++ b/app/shortcutedit.h @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include + +class QLabel; +class QFormLayout; +class QToolButton; +class QKeySequenceEdit; +class ShortcutEdit; +class ShortcutEditor : public QWidget +{ + Q_OBJECT +public: + explicit ShortcutEditor(ShortcutEdit * shortcutEdit, QWidget * parent = nullptr); + ~ShortcutEditor(); + + void setDescription(const QString & desc); + + void reloadShortcuts(); + void applyShortcuts(); + +private: + QLabel * m_descriptionLabel; + ShortcutEdit * m_shortcutEdit; + QFormLayout * m_shortcutLayout; + QList m_keySequenceEdits; +}; + +class ShortcutEdit : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QList shortcuts MEMBER m_shortcuts WRITE setShortcuts NOTIFY shortcutsChanged) +public: + explicit ShortcutEdit(QWidget * parent = nullptr); + ~ShortcutEdit(); + + QList shortcuts() const; + void setShortcuts(const QList &shortcuts); + +signals: + void shortcutsChanged(); + void editButtonClicked(); + +private: + QList m_shortcuts; + QLabel * m_shortcutsLabel; + QToolButton * m_setShortcutButton; +};