Allow configuring keyboard shortcuts (#9643)

Closes #2689

The design of the respective code is loosely based on KDE's KActionCollection. The ActionCollection manages all actions that can be shortcut configured. These actions are then exposed in the config and a user can assign a different shortcut.

Actions inside the MainWindow have been added to the ActionCollection.

---------

Co-authored-by: Jonathan White <support@dmapps.us>
This commit is contained in:
Waqar Ahmed 2024-02-04 16:29:04 +05:00 committed by GitHub
parent d03f5e4977
commit a472ef8a93
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1238 additions and 233 deletions

View file

@ -98,6 +98,7 @@ set(keepassx_SOURCES
gui/styles/dark/DarkStyle.cpp
gui/styles/light/LightStyle.cpp
gui/AboutDialog.cpp
gui/ActionCollection.cpp
gui/Application.cpp
gui/CategoryListWidget.cpp
gui/Clipboard.cpp
@ -130,6 +131,7 @@ set(keepassx_SOURCES
gui/SearchWidget.cpp
gui/SortFilterHideProxyModel.cpp
gui/SquareSvgWidget.cpp
gui/ShortcutSettingsPage.cpp
gui/TotpSetupDialog.cpp
gui/TotpDialog.cpp
gui/TotpExportSettingsDialog.cpp
@ -180,6 +182,7 @@ set(keepassx_SOURCES
gui/widgets/ElidedLabel.cpp
gui/widgets/KPToolBar.cpp
gui/widgets/PopupHelpWidget.cpp
gui/widgets/ShortcutWidget.cpp
gui/wizard/NewDatabaseWizard.cpp
gui/wizard/NewDatabaseWizardPage.cpp
gui/wizard/NewDatabaseWizardPageMetaData.cpp
@ -319,7 +322,6 @@ set(autotype_SOURCES
autotype/AutoTypeMatchView.cpp
autotype/AutoTypeSelectDialog.cpp
autotype/PickcharsDialog.cpp
autotype/ShortcutWidget.cpp
autotype/WindowSelectComboBox.cpp)
if(WIN32)

View file

@ -597,4 +597,28 @@ void Config::createTempFileInstance()
tmpFile->setParent(m_instance);
}
QList<Config::ShortcutEntry> Config::getShortcuts() const
{
m_settings->beginGroup("Shortcuts");
const auto keys = m_settings->childKeys();
QList<ShortcutEntry> ret;
ret.reserve(keys.size());
for (const auto& key : keys) {
const auto shortcut = m_settings->value(key).toString();
ret.push_back(ShortcutEntry{key, shortcut});
}
m_settings->endGroup();
return ret;
}
void Config::setShortcuts(const QList<ShortcutEntry>& shortcuts)
{
m_settings->beginGroup("Shortcuts");
m_settings->remove(""); // clear previous
for (const auto& shortcutEntry : shortcuts) {
m_settings->setValue(shortcutEntry.name, shortcutEntry.shortcut);
}
m_settings->endGroup();
}
#undef QS

View file

@ -21,6 +21,7 @@
#include <QPointer>
#include <QVariant>
#include <QVector>
class QSettings;
@ -198,6 +199,12 @@ public:
Deleted
};
struct ShortcutEntry
{
QString name;
QString shortcut;
};
~Config() override;
QVariant get(ConfigKey key);
QVariant getDefault(ConfigKey key);
@ -208,6 +215,9 @@ public:
void sync();
void resetToDefaults();
QList<ShortcutEntry> getShortcuts() const;
void setShortcuts(const QList<ShortcutEntry>& shortcuts);
static Config* instance();
static void createConfigFromFile(const QString& configFileName, const QString& localConfigFileName = {});
static void createTempFileInstance();

View file

@ -0,0 +1,123 @@
/*
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ActionCollection.h"
#include "core/Config.h"
#include <QDebug>
ActionCollection* ActionCollection::instance()
{
static ActionCollection ac;
return &ac;
}
QList<QAction*> ActionCollection::actions() const
{
return m_actions;
}
void ActionCollection::addAction(QAction* action)
{
if (!m_actions.contains(action)) {
m_actions << action;
}
}
void ActionCollection::addActions(const QList<QAction*>& actions)
{
for (auto a : actions) {
addAction(a);
}
}
QKeySequence ActionCollection::defaultShortcut(const QAction* action) const
{
auto shortcuts = defaultShortcuts(action);
return shortcuts.isEmpty() ? QKeySequence() : shortcuts.first();
}
QList<QKeySequence> ActionCollection::defaultShortcuts(const QAction* action) const
{
return action->property("defaultShortcuts").value<QList<QKeySequence>>();
}
void ActionCollection::setDefaultShortcut(QAction* action, const QKeySequence& shortcut)
{
setDefaultShortcuts(action, {shortcut});
}
void ActionCollection::setDefaultShortcut(QAction* action,
QKeySequence::StandardKey standard,
const QKeySequence& fallback)
{
if (!QKeySequence::keyBindings(standard).isEmpty()) {
setDefaultShortcuts(action, QKeySequence::keyBindings(standard));
} else if (fallback != 0) {
setDefaultShortcut(action, QKeySequence(fallback));
}
}
void ActionCollection::setDefaultShortcuts(QAction* action, const QList<QKeySequence>& shortcuts)
{
action->setShortcuts(shortcuts);
action->setProperty("defaultShortcuts", QVariant::fromValue(shortcuts));
}
void ActionCollection::restoreShortcuts()
{
const auto shortcuts = Config::instance()->getShortcuts();
QHash<QString, QAction*> actionsByName;
for (auto action : m_actions) {
actionsByName.insert(action->objectName(), action);
}
for (const auto& shortcut : shortcuts) {
if (actionsByName.contains(shortcut.name)) {
const auto key = QKeySequence::fromString(shortcut.shortcut);
actionsByName.value(shortcut.name)->setShortcut(key);
}
}
}
void ActionCollection::saveShortcuts()
{
QList<Config::ShortcutEntry> shortcuts;
shortcuts.reserve(m_actions.size());
for (auto a : m_actions) {
// Only store non-default shortcut assignments
if (a->shortcut() != defaultShortcut(a)) {
shortcuts << Config::ShortcutEntry{a->objectName(), a->shortcut().toString()};
}
}
Config::instance()->setShortcuts(shortcuts);
}
QAction* ActionCollection::isConflictingShortcut(const QAction* action, const QKeySequence& seq) const
{
// Empty sequences don't conflict with anything
if (seq.isEmpty()) {
return nullptr;
}
for (auto a : m_actions) {
if (a != action && a->shortcut() == seq) {
return a;
}
}
return nullptr;
}

View file

@ -0,0 +1,61 @@
/*
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_ACTION_COLLECTION_H
#define KEEPASSXC_ACTION_COLLECTION_H
#include <QAction>
#include <QKeySequence>
#include <QObject>
/**
* This class manages all actions that are shortcut configurable.
* It also allows you to access the actions inside it from anywhere
* in the gui code.
*/
class ActionCollection : public QObject
{
Q_OBJECT
ActionCollection() = default;
public:
static ActionCollection* instance();
QList<QAction*> actions() const;
void addAction(QAction* action);
void addActions(const QList<QAction*>& actions);
QKeySequence defaultShortcut(const QAction* a) const;
QList<QKeySequence> defaultShortcuts(const QAction* a) const;
void setDefaultShortcut(QAction* a, const QKeySequence& shortcut);
void setDefaultShortcut(QAction* a, QKeySequence::StandardKey standard, const QKeySequence& fallback);
void setDefaultShortcuts(QAction* a, const QList<QKeySequence>& shortcut);
// Check if any action conflicts with @p seq and return the conflicting action
QAction* isConflictingShortcut(const QAction* action, const QKeySequence& seq) const;
public slots:
void restoreShortcuts();
void saveShortcuts();
private:
QList<QAction*> m_actions;
};
#endif

View file

@ -21,6 +21,7 @@
#include "ui_ApplicationSettingsWidgetSecurity.h"
#include <QDesktopServices>
#include <QDir>
#include <QToolTip>
#include "config-keepassx.h"
@ -138,6 +139,22 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
m_secUi->lockDatabaseMinimizeCheckBox->setEnabled(!state);
});
// Set Auto-Type shortcut when changed
connect(
m_generalUi->autoTypeShortcutWidget, &ShortcutWidget::shortcutChanged, this, [this](auto key, auto modifiers) {
QString error;
if (autoType()->registerGlobalShortcut(key, modifiers, &error)) {
m_generalUi->autoTypeShortcutWidget->setStyleSheet("");
} else {
QToolTip::showText(mapToGlobal(rect().bottomLeft()), error);
m_generalUi->autoTypeShortcutWidget->setStyleSheet("background-color: #FF9696;");
}
});
connect(m_generalUi->autoTypeShortcutWidget, &ShortcutWidget::shortcutReset, this, [this] {
autoType()->unregisterGlobalShortcut();
m_generalUi->autoTypeShortcutWidget->setStyleSheet("");
});
// Disable mouse wheel grab when scrolling
// This prevents combo box and spinner values from changing without explicit focus
auto mouseWheelFilter = new MouseWheelEventFilter(this);

View file

@ -58,8 +58,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>564</width>
<height>930</height>
<width>566</width>
<height>975</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
@ -1260,7 +1260,7 @@
<customwidget>
<class>ShortcutWidget</class>
<extends>QLineEdit</extends>
<header>autotype/ShortcutWidget.h</header>
<header>gui/widgets/ShortcutWidget.h</header>
</customwidget>
</customwidgets>
<tabstops>

View file

@ -40,9 +40,11 @@
#include "core/Resources.h"
#include "core/Tools.h"
#include "gui/AboutDialog.h"
#include "gui/ActionCollection.h"
#include "gui/Icons.h"
#include "gui/MessageBox.h"
#include "gui/SearchWidget.h"
#include "gui/ShortcutSettingsPage.h"
#include "gui/entry/EntryView.h"
#include "gui/osutils/OSUtils.h"
@ -189,6 +191,11 @@ MainWindow::MainWindow()
connect(m_ui->tabWidget, &DatabaseTabWidget::databaseUnlocked, this, &MainWindow::databaseUnlocked);
connect(m_ui->tabWidget, &DatabaseTabWidget::activeDatabaseChanged, this, &MainWindow::activeDatabaseChanged);
initViewMenu();
initActionCollection();
m_ui->settingsWidget->addSettingsPage(new ShortcutSettingsPage());
#ifdef WITH_XC_BROWSER
m_ui->settingsWidget->addSettingsPage(new BrowserSettingsPage());
connect(
@ -201,8 +208,6 @@ MainWindow::MainWindow()
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage());
#endif
initViewMenu();
#if defined(WITH_XC_KEESHARE)
KeeShare::init(this);
m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget));
@ -262,45 +267,6 @@ MainWindow::MainWindow()
connect(m_inactivityTimer, SIGNAL(inactivityDetected()), this, SLOT(lockDatabasesAfterInactivity()));
applySettingsChanges();
m_ui->actionDatabaseNew->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_N);
setShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O);
setShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S);
setShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs, Qt::CTRL + Qt::SHIFT + Qt::Key_S);
setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W);
m_ui->actionDatabaseSettings->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Comma);
m_ui->actionReports->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_R);
setShortcut(m_ui->actionSettings, QKeySequence::Preferences, Qt::CTRL + Qt::Key_Comma);
m_ui->actionLockDatabase->setShortcut(Qt::CTRL + Qt::Key_L);
m_ui->actionLockAllDatabases->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_L);
setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q);
setShortcut(m_ui->actionEntryNew, QKeySequence::New, Qt::CTRL + Qt::Key_N);
m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E);
m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D);
m_ui->actionEntryDelete->setShortcut(Qt::Key_Delete);
m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K);
m_ui->actionEntryTotp->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_T);
m_ui->actionEntryDownloadIcon->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_D);
m_ui->actionEntryCopyTotp->setShortcut(Qt::CTRL + Qt::Key_T);
m_ui->actionEntryCopyPasswordTotp->setShortcut(Qt::CTRL + Qt::Key_Y);
m_ui->actionEntryMoveUp->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_Up);
m_ui->actionEntryMoveDown->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_Down);
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
m_ui->actionEntryCopyTitle->setShortcut(Qt::CTRL + Qt::Key_I);
m_ui->actionEntryAutoTypeSequence->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V);
m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U);
m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::Key_U);
m_ui->actionEntryRestore->setShortcut(Qt::CTRL + Qt::Key_R);
// Prevent conflicts with global Mac shortcuts (force Control on all platforms)
#ifdef Q_OS_MAC
auto modifier = Qt::META;
#else
auto modifier = Qt::CTRL;
#endif
m_ui->actionEntryAddToAgent->setShortcut(modifier + Qt::Key_H);
m_ui->actionEntryRemoveFromAgent->setShortcut(modifier + Qt::SHIFT + Qt::Key_H);
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
// Qt 5.10 introduced a new "feature" to hide shortcuts in context menus
// Unfortunately, Qt::AA_DontShowShortcutsInContextMenus is broken, have to manually enable them
@ -1675,15 +1641,6 @@ void MainWindow::showGroupContextMenu(const QPoint& globalPos)
m_ui->menuGroups->popup(globalPos);
}
void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback)
{
if (!QKeySequence::keyBindings(standard).isEmpty()) {
action->setShortcuts(standard);
} else if (fallback != 0) {
action->setShortcut(QKeySequence(fallback));
}
}
void MainWindow::applySettingsChanges()
{
int timeout = config()->get(Config::Security_LockDatabaseIdleSeconds).toInt() * 1000;
@ -2094,6 +2051,145 @@ void MainWindow::initViewMenu()
});
}
void MainWindow::initActionCollection()
{
auto ac = ActionCollection::instance();
ac->addActions({// Database Menu
m_ui->actionDatabaseNew,
m_ui->actionDatabaseOpen,
m_ui->actionDatabaseSave,
m_ui->actionDatabaseSaveAs,
m_ui->actionDatabaseSaveBackup,
m_ui->actionDatabaseClose,
m_ui->actionLockDatabase,
m_ui->actionLockAllDatabases,
m_ui->actionDatabaseSettings,
m_ui->actionDatabaseSecurity,
m_ui->actionReports,
m_ui->actionPasskeys,
m_ui->actionDatabaseMerge,
m_ui->actionImportPasskey,
m_ui->actionImportCsv,
m_ui->actionImportOpVault,
m_ui->actionImportKeePass1,
m_ui->actionExportCsv,
m_ui->actionExportHtml,
m_ui->actionExportXML,
m_ui->actionQuit,
// Entry Menu
m_ui->actionEntryNew,
m_ui->actionEntryEdit,
m_ui->actionEntryClone,
m_ui->actionEntryDelete,
m_ui->actionEntryCopyUsername,
m_ui->actionEntryCopyPassword,
m_ui->actionEntryCopyURL,
m_ui->actionEntryCopyTitle,
m_ui->actionEntryCopyNotes,
m_ui->actionEntryTotp,
m_ui->actionEntryTotpQRCode,
m_ui->actionEntrySetupTotp,
m_ui->actionEntryCopyTotp,
m_ui->actionEntryCopyPasswordTotp,
m_ui->actionEntryAutoTypeSequence,
m_ui->actionEntryAutoTypeUsername,
m_ui->actionEntryAutoTypeUsernameEnter,
m_ui->actionEntryAutoTypePassword,
m_ui->actionEntryAutoTypePasswordEnter,
m_ui->actionEntryAutoTypeTOTP,
m_ui->actionEntryDownloadIcon,
m_ui->actionEntryOpenUrl,
m_ui->actionEntryMoveUp,
m_ui->actionEntryMoveDown,
m_ui->actionEntryAddToAgent,
m_ui->actionEntryRemoveFromAgent,
m_ui->actionEntryRestore,
// Group Menu
m_ui->actionGroupNew,
m_ui->actionGroupEdit,
m_ui->actionGroupClone,
m_ui->actionGroupDelete,
m_ui->actionGroupDownloadFavicons,
m_ui->actionGroupSortAsc,
m_ui->actionGroupSortDesc,
m_ui->actionGroupEmptyRecycleBin,
// Tools Menu
m_ui->actionPasswordGenerator,
m_ui->actionSettings,
// View Menu
m_ui->actionThemeAuto,
m_ui->actionThemeLight,
m_ui->actionThemeDark,
m_ui->actionThemeClassic,
m_ui->actionCompactMode,
m_ui->actionShowToolbar,
m_ui->actionShowPreviewPanel,
m_ui->actionAllowScreenCapture,
m_ui->actionAlwaysOnTop,
m_ui->actionHideUsernames,
m_ui->actionHidePasswords,
// Help Menu
m_ui->actionGettingStarted,
m_ui->actionUserGuide,
m_ui->actionKeyboardShortcuts,
m_ui->actionOnlineHelp,
m_ui->actionCheckForUpdates,
m_ui->actionDonate,
m_ui->actionBugReport,
m_ui->actionAbout});
// Add actions whose shortcuts were set in the .ui file
for (const auto action : ac->actions()) {
if (!action->shortcut().isEmpty()) {
ac->setDefaultShortcut(action, action->shortcut());
}
}
// Actions with standard shortcuts
ac->setDefaultShortcut(m_ui->actionDatabaseOpen, QKeySequence::Open, Qt::CTRL + Qt::Key_O);
ac->setDefaultShortcut(m_ui->actionDatabaseSave, QKeySequence::Save, Qt::CTRL + Qt::Key_S);
ac->setDefaultShortcut(m_ui->actionDatabaseSaveAs, QKeySequence::SaveAs, Qt::CTRL + Qt::SHIFT + Qt::Key_S);
ac->setDefaultShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W);
ac->setDefaultShortcut(m_ui->actionSettings, QKeySequence::Preferences, Qt::CTRL + Qt::Key_Comma);
ac->setDefaultShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q);
ac->setDefaultShortcut(m_ui->actionEntryNew, QKeySequence::New, Qt::CTRL + Qt::Key_N);
// Prevent conflicts with global Mac shortcuts (force Control on all platforms)
#ifdef Q_OS_MAC
auto modifier = Qt::META;
#else
auto modifier = Qt::CTRL;
#endif
// All other actions with default shortcuts
ac->setDefaultShortcut(m_ui->actionDatabaseNew, Qt::CTRL + Qt::SHIFT + Qt::Key_N);
ac->setDefaultShortcut(m_ui->actionDatabaseSettings, Qt::CTRL + Qt::SHIFT + Qt::Key_Comma);
ac->setDefaultShortcut(m_ui->actionReports, Qt::CTRL + Qt::SHIFT + Qt::Key_R);
ac->setDefaultShortcut(m_ui->actionLockDatabase, Qt::CTRL + Qt::Key_L);
ac->setDefaultShortcut(m_ui->actionLockAllDatabases, Qt::CTRL + Qt::SHIFT + Qt::Key_L);
ac->setDefaultShortcut(m_ui->actionEntryEdit, Qt::CTRL + Qt::Key_E);
ac->setDefaultShortcut(m_ui->actionEntryDelete, Qt::CTRL + Qt::Key_D);
ac->setDefaultShortcut(m_ui->actionEntryDelete, Qt::Key_Delete);
ac->setDefaultShortcut(m_ui->actionEntryClone, Qt::CTRL + Qt::Key_K);
ac->setDefaultShortcut(m_ui->actionEntryTotp, Qt::CTRL + Qt::SHIFT + Qt::Key_T);
ac->setDefaultShortcut(m_ui->actionEntryDownloadIcon, Qt::CTRL + Qt::SHIFT + Qt::Key_D);
ac->setDefaultShortcut(m_ui->actionEntryCopyTotp, Qt::CTRL + Qt::Key_T);
ac->setDefaultShortcut(m_ui->actionEntryCopyPasswordTotp, Qt::CTRL + Qt::Key_Y);
ac->setDefaultShortcut(m_ui->actionEntryMoveUp, Qt::CTRL + Qt::ALT + Qt::Key_Up);
ac->setDefaultShortcut(m_ui->actionEntryMoveDown, Qt::CTRL + Qt::ALT + Qt::Key_Down);
ac->setDefaultShortcut(m_ui->actionEntryCopyUsername, Qt::CTRL + Qt::Key_B);
ac->setDefaultShortcut(m_ui->actionEntryCopyPassword, Qt::CTRL + Qt::Key_C);
ac->setDefaultShortcut(m_ui->actionEntryCopyTitle, Qt::CTRL + Qt::Key_I);
ac->setDefaultShortcut(m_ui->actionEntryAutoTypeSequence, Qt::CTRL + Qt::SHIFT + Qt::Key_V);
ac->setDefaultShortcut(m_ui->actionEntryOpenUrl, Qt::CTRL + Qt::SHIFT + Qt::Key_U);
ac->setDefaultShortcut(m_ui->actionEntryCopyURL, Qt::CTRL + Qt::Key_U);
ac->setDefaultShortcut(m_ui->actionEntryRestore, Qt::CTRL + Qt::Key_R);
ac->setDefaultShortcut(m_ui->actionEntryAddToAgent, modifier + Qt::Key_H);
ac->setDefaultShortcut(m_ui->actionEntryRemoveFromAgent, modifier + Qt::SHIFT + Qt::Key_H);
QTimer::singleShot(1, ac, &ActionCollection::restoreShortcuts);
}
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
MainWindowEventFilter::MainWindowEventFilter(QObject* parent)

View file

@ -154,8 +154,6 @@ private slots:
void focusSearchWidget();
private:
static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0);
static const QString BaseWindowTitle;
void saveWindowInformation();
@ -168,6 +166,7 @@ private:
void dropEvent(QDropEvent* event) override;
void initViewMenu();
void initActionCollection();
const QScopedPointer<Ui::MainWindow> m_ui;
SignalMultiplexer m_actionMultiplexer;

View file

@ -443,6 +443,9 @@
<property name="text">
<string>&amp;Quit</string>
</property>
<property name="toolTip">
<string>Quit Application</string>
</property>
<property name="menuRole">
<enum>QAction::QuitRole</enum>
</property>
@ -451,6 +454,9 @@
<property name="text">
<string>&amp;About</string>
</property>
<property name="toolTip">
<string>Open About Dialog</string>
</property>
<property name="menuRole">
<enum>QAction::AboutRole</enum>
</property>
@ -467,6 +473,9 @@
<property name="text">
<string>&amp;Open Database…</string>
</property>
<property name="toolTip">
<string>Open Database</string>
</property>
</action>
<action name="actionDatabaseSave">
<property name="enabled">
@ -489,7 +498,7 @@
<string>&amp;New Database…</string>
</property>
<property name="toolTip">
<string>Create a new database</string>
<string>Create Database</string>
</property>
</action>
<action name="actionDatabaseMerge">
@ -497,7 +506,7 @@
<string>&amp;Merge From Database…</string>
</property>
<property name="toolTip">
<string>Merge from another KDBX database</string>
<string>Merge From Database</string>
</property>
</action>
<action name="actionEntryNew">
@ -508,7 +517,7 @@
<string>&amp;New Entry…</string>
</property>
<property name="toolTip">
<string>Add a new entry</string>
<string>Create Entry</string>
</property>
</action>
<action name="actionEntryEdit">
@ -519,7 +528,7 @@
<string>&amp;Edit Entry…</string>
</property>
<property name="toolTip">
<string>View or edit entry</string>
<string>Edit Entry</string>
</property>
</action>
<action name="actionEntryDelete">
@ -529,6 +538,9 @@
<property name="text">
<string>&amp;Delete Entry…</string>
</property>
<property name="toolTip">
<string>Delete Entry</string>
</property>
</action>
<action name="actionGroupNew">
<property name="enabled">
@ -538,7 +550,7 @@
<string>&amp;New Group…</string>
</property>
<property name="toolTip">
<string>Add a new group</string>
<string>Create Group</string>
</property>
</action>
<action name="actionGroupEdit">
@ -548,6 +560,9 @@
<property name="text">
<string>&amp;Edit Group…</string>
</property>
<property name="toolTip">
<string>Edit Group</string>
</property>
</action>
<action name="actionGroupDelete">
<property name="enabled">
@ -556,6 +571,9 @@
<property name="text">
<string>&amp;Delete Group…</string>
</property>
<property name="toolTip">
<string>Delete Group</string>
</property>
</action>
<action name="actionGroupDownloadFavicons">
<property name="enabled">
@ -564,6 +582,9 @@
<property name="text">
<string>Download All &amp;Favicons…</string>
</property>
<property name="toolTip">
<string>Download All Favicons</string>
</property>
</action>
<action name="actionGroupSortAsc">
<property name="enabled">
@ -572,6 +593,9 @@
<property name="text">
<string>Sort &amp;A-Z</string>
</property>
<property name="toolTip">
<string>Sort Groups A-Z</string>
</property>
</action>
<action name="actionGroupSortDesc">
<property name="enabled">
@ -580,6 +604,9 @@
<property name="text">
<string>Sort &amp;Z-A</string>
</property>
<property name="toolTip">
<string>Sort Groups Z-A</string>
</property>
</action>
<action name="actionDatabaseSaveAs">
<property name="enabled">
@ -588,6 +615,9 @@
<property name="text">
<string>Sa&amp;ve Database As…</string>
</property>
<property name="toolTip">
<string>Save Database As</string>
</property>
</action>
<action name="actionDatabaseSecurity">
<property name="enabled">
@ -596,6 +626,9 @@
<property name="text">
<string>Database &amp;Security…</string>
</property>
<property name="toolTip">
<string>Show Database Security</string>
</property>
</action>
<action name="actionReports">
<property name="enabled">
@ -605,7 +638,7 @@
<string>Database &amp;Reports…</string>
</property>
<property name="toolTip">
<string>Statistics, health check, etc.</string>
<string>Show Database Reports</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
@ -619,7 +652,7 @@
<string>&amp;Database Settings…</string>
</property>
<property name="toolTip">
<string>Database settings</string>
<string>Show Database Settings</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
@ -633,7 +666,7 @@
<string>Passkeys…</string>
</property>
<property name="toolTip">
<string>Passkeys</string>
<string>Show Passkeys</string>
</property>
<property name="menuRole">
<enum>QAction::NoRole</enum>
@ -660,6 +693,9 @@
<property name="text">
<string>&amp;Clone Entry…</string>
</property>
<property name="toolTip">
<string>Clone Entry</string>
</property>
</action>
<action name="actionEntryMoveUp">
<property name="enabled">
@ -669,7 +705,7 @@
<string>Move u&amp;p</string>
</property>
<property name="toolTip">
<string>Move entry one step up</string>
<string>Move Entry Up</string>
</property>
</action>
<action name="actionEntryMoveDown">
@ -680,7 +716,7 @@
<string>Move do&amp;wn</string>
</property>
<property name="toolTip">
<string>Move entry one step down</string>
<string>Move Entry Down</string>
</property>
</action>
<action name="actionEntryCopyUsername">
@ -691,7 +727,7 @@
<string>Copy &amp;Username</string>
</property>
<property name="toolTip">
<string>Copy username to clipboard</string>
<string>Copy Username</string>
</property>
</action>
<action name="actionEntryCopyPassword">
@ -702,7 +738,7 @@
<string>Copy &amp;Password</string>
</property>
<property name="toolTip">
<string>Copy password to clipboard</string>
<string>Copy Password</string>
</property>
</action>
<action name="actionSettings">
@ -712,6 +748,9 @@
<property name="text">
<string>&amp;Settings</string>
</property>
<property name="toolTip">
<string>Show Application Settings</string>
</property>
<property name="menuRole">
<enum>QAction::PreferencesRole</enum>
</property>
@ -723,6 +762,9 @@
<property name="text">
<string>&amp;Password Generator</string>
</property>
<property name="toolTip">
<string>Show Password Generator</string>
</property>
</action>
<action name="actionEntryAutoType">
<property name="enabled">
@ -751,7 +793,7 @@
<string notr="true">{USERNAME}</string>
</property>
<property name="toolTip">
<string notr="true">{USERNAME}</string>
<string>Perform Auto-Type: {USERNAME}</string>
</property>
</action>
<action name="actionEntryAutoTypeUsernameEnter">
@ -765,7 +807,7 @@
<string notr="true">{USERNAME}{ENTER}</string>
</property>
<property name="toolTip">
<string notr="true">{USERNAME}{ENTER}</string>
<string>Perform Auto-Type: {USERNAME}{ENTER}</string>
</property>
</action>
<action name="actionEntryAutoTypePassword">
@ -779,7 +821,7 @@
<string notr="true">{PASSWORD}</string>
</property>
<property name="toolTip">
<string notr="true">{PASSWORD}</string>
<string>Perform Auto-Type: {PASSWORD}</string>
</property>
</action>
<action name="actionEntryAutoTypePasswordEnter">
@ -793,7 +835,7 @@
<string notr="true">{PASSWORD}{ENTER}</string>
</property>
<property name="toolTip">
<string notr="true">{PASSWORD}{ENTER}</string>
<string>Perform Auto-Type: {PASSWORD}{ENTER}</string>
</property>
</action>
<action name="actionEntryAutoTypeTOTP">
@ -807,7 +849,7 @@
<string notr="true">{TOTP}</string>
</property>
<property name="toolTip">
<string notr="true">{TOTP}</string>
<string>Perform Auto-Type: {TOTP}</string>
</property>
</action>
<action name="actionEntryDownloadIcon">
@ -847,7 +889,7 @@
<string>&amp;Title</string>
</property>
<property name="toolTip">
<string>Copy title to clipboard</string>
<string>Copy Title</string>
</property>
</action>
<action name="actionEntryCopyURL">
@ -858,7 +900,7 @@
<string>Copy &amp;URL</string>
</property>
<property name="toolTip">
<string>Copy URL to clipboard</string>
<string>Copy URL</string>
</property>
</action>
<action name="actionEntryCopyNotes">
@ -869,7 +911,7 @@
<string>&amp;Notes</string>
</property>
<property name="toolTip">
<string>Copy notes to clipboard</string>
<string>Copy Notes</string>
</property>
</action>
<action name="actionExportCsv">
@ -879,6 +921,9 @@
<property name="text">
<string>&amp;CSV File…</string>
</property>
<property name="toolTip">
<string>Export to CSV</string>
</property>
</action>
<action name="actionExportHtml">
<property name="enabled">
@ -887,13 +932,16 @@
<property name="text">
<string>&amp;HTML File…</string>
</property>
<property name="toolTip">
<string>Export to HTML</string>
</property>
</action>
<action name="actionImportKeePass1">
<property name="text">
<string>KeePass 1 Database…</string>
</property>
<property name="toolTip">
<string>Import a KeePass 1 database</string>
<string>Import KeePass1 Database</string>
</property>
</action>
<action name="actionImportOpVault">
@ -901,7 +949,7 @@
<string>1Password Vault…</string>
</property>
<property name="toolTip">
<string>Import a 1Password Vault</string>
<string>Import 1Password Vault</string>
</property>
</action>
<action name="actionImportCsv">
@ -909,7 +957,7 @@
<string>CSV File…</string>
</property>
<property name="toolTip">
<string>Import a CSV file</string>
<string>Import CSV File</string>
</property>
</action>
<action name="actionEntryTotp">
@ -921,11 +969,17 @@
<property name="text">
<string>Show QR Code</string>
</property>
<property name="toolTip">
<string>Show TOTP QR Code</string>
</property>
</action>
<action name="actionEntrySetupTotp">
<property name="text">
<string>Set up TOTP…</string>
</property>
<property name="toolTip">
<string>Set up TOTP</string>
</property>
</action>
<action name="actionEntryCopyTotp">
<property name="text">
@ -941,6 +995,9 @@
<property name="text">
<string>E&amp;mpty recycle bin</string>
</property>
<property name="toolTip">
<string>Empty Recycle Bin</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
@ -949,11 +1006,17 @@
<property name="text">
<string>&amp;Donate</string>
</property>
<property name="toolTip">
<string>Open Donation Website</string>
</property>
</action>
<action name="actionBugReport">
<property name="text">
<string>Report a &amp;Bug</string>
</property>
<property name="toolTip">
<string>Open Bug Report</string>
</property>
</action>
<action name="actionGettingStarted">
<property name="text">
@ -968,7 +1031,7 @@
<string>&amp;Online Help</string>
</property>
<property name="toolTip">
<string>Go to online documentation</string>
<string>Open Online Documentation</string>
</property>
</action>
<action name="actionUserGuide">
@ -983,6 +1046,9 @@
<property name="text">
<string>&amp;Keyboard Shortcuts</string>
</property>
<property name="toolTip">
<string>Open Keyboard Shortcuts Guide</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+/</string>
</property>
@ -994,16 +1060,25 @@
<property name="text">
<string>Save Database Backup…</string>
</property>
<property name="toolTip">
<string>Save Database Backup</string>
</property>
</action>
<action name="actionEntryAddToAgent">
<property name="text">
<string>Add key to SSH Agent</string>
</property>
<property name="toolTip">
<string>SSH Agent: Add Key</string>
</property>
</action>
<action name="actionEntryRemoveFromAgent">
<property name="text">
<string>Remove key from SSH Agent</string>
</property>
<property name="toolTip">
<string>SSH Agent: Remove Key</string>
</property>
</action>
<action name="actionCompactMode">
<property name="checkable">
@ -1012,6 +1087,9 @@
<property name="text">
<string>Compact Mode</string>
</property>
<property name="toolTip">
<string>Toggle Compact Mode</string>
</property>
</action>
<action name="actionThemeAuto">
<property name="checkable">
@ -1023,6 +1101,9 @@
<property name="text">
<string>Automatic</string>
</property>
<property name="toolTip">
<string>Set Theme: Automatic</string>
</property>
</action>
<action name="actionThemeLight">
<property name="checkable">
@ -1031,6 +1112,9 @@
<property name="text">
<string>Light</string>
</property>
<property name="toolTip">
<string>Set Theme: Light</string>
</property>
</action>
<action name="actionThemeDark">
<property name="checkable">
@ -1039,6 +1123,9 @@
<property name="text">
<string>Dark</string>
</property>
<property name="toolTip">
<string>Set Theme: Dark</string>
</property>
</action>
<action name="actionThemeClassic">
<property name="checkable">
@ -1047,6 +1134,9 @@
<property name="text">
<string>Classic (Platform-native)</string>
</property>
<property name="toolTip">
<string>Set Theme: Classic</string>
</property>
</action>
<action name="actionShowToolbar">
<property name="checkable">
@ -1058,6 +1148,9 @@
<property name="text">
<string>Show Toolbar</string>
</property>
<property name="toolTip">
<string>Toggle Show Toolbar</string>
</property>
</action>
<action name="actionShowPreviewPanel">
<property name="checkable">
@ -1069,6 +1162,9 @@
<property name="text">
<string>Show Preview Panel</string>
</property>
<property name="toolTip">
<string>Toggle Show Preview Panel</string>
</property>
</action>
<action name="actionAlwaysOnTop">
<property name="checkable">
@ -1077,6 +1173,9 @@
<property name="text">
<string>Always on Top</string>
</property>
<property name="toolTip">
<string>Toggle Always on Top</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Shift+A</string>
</property>
@ -1088,6 +1187,9 @@
<property name="text">
<string>Hide Usernames</string>
</property>
<property name="toolTip">
<string>Toggle Hide Usernames</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Shift+B</string>
</property>
@ -1102,6 +1204,9 @@
<property name="text">
<string>Hide Passwords</string>
</property>
<property name="toolTip">
<string>Toggle Hide Passwords</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+Shift+C</string>
</property>
@ -1114,7 +1219,7 @@
<string notr="true">{USERNAME}{TAB}{PASSWORD}{ENTER}</string>
</property>
<property name="toolTip">
<string notr="true">{USERNAME}{TAB}{PASSWORD}{ENTER}</string>
<string notr="true">Perform Auto-Type: Entry Default</string>
</property>
</action>
<action name="actionGroupClone">
@ -1130,7 +1235,7 @@
<string notr="true" extracomment="Translatable string with plural form set in CPP file">Restore Entry(s)</string>
</property>
<property name="toolTip">
<string notr="true" extracomment="Translatable string with plural form set in CPP file">Restore Entry(s)</string>
<string notr="true" extracomment="Translatable string with plural form set in CPP file">Restore Entry</string>
</property>
<property name="shortcut">
<string notr="true">Ctrl+R</string>
@ -1152,7 +1257,7 @@
<string>&amp;XML File…</string>
</property>
<property name="toolTip">
<string>XML File…</string>
<string>Export to XML</string>
</property>
</action>
<action name="actionAllowScreenCapture">
@ -1162,6 +1267,9 @@
<property name="text">
<string>Allow Screen Capture</string>
</property>
<property name="toolTip">
<string>Toggle Allow Screen Capture</string>
</property>
</action>
</widget>
<customwidgets>

View file

@ -0,0 +1,282 @@
/*
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ShortcutSettingsPage.h"
#include "core/Config.h"
#include "gui/ActionCollection.h"
#include "gui/Icons.h"
#include "gui/MessageBox.h"
#include "gui/widgets/ShortcutWidget.h"
#include <QAbstractButton>
#include <QDebug>
#include <QDialog>
#include <QDialogButtonBox>
#include <QHeaderView>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QTableView>
#include <QVBoxLayout>
class KeySequenceDialog final : public QDialog
{
public:
explicit KeySequenceDialog(QWidget* parent = nullptr)
: QDialog(parent)
, m_keySeqEdit(new ShortcutWidget(this))
, m_btnBox(new QDialogButtonBox(QDialogButtonBox::Save | QDialogButtonBox::Cancel
| QDialogButtonBox::RestoreDefaults,
this))
{
auto* l = new QVBoxLayout(this);
connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(m_btnBox, &QDialogButtonBox::clicked, this, &KeySequenceDialog::restoreDefault);
auto hLayout = new QHBoxLayout();
l->addLayout(hLayout);
hLayout->addWidget(new QLabel(QObject::tr("Enter Shortcut")));
hLayout->addWidget(m_keySeqEdit);
l->addStretch();
l->addWidget(m_btnBox);
setFocusProxy(m_keySeqEdit);
}
QKeySequence keySequence() const
{
return m_keySeqEdit->sequence();
}
bool shouldRestoreDefault() const
{
return m_restoreDefault;
}
private:
void restoreDefault(QAbstractButton* btn)
{
if (m_btnBox->standardButton(btn) == QDialogButtonBox::RestoreDefaults) {
m_restoreDefault = true;
reject();
}
}
private:
bool m_restoreDefault = false;
ShortcutWidget* const m_keySeqEdit;
QDialogButtonBox* const m_btnBox;
};
class ShortcutSettingsWidget final : public QWidget
{
public:
explicit ShortcutSettingsWidget(QWidget* parent = nullptr)
: QWidget(parent)
, m_tableView(new QTableView(this))
, m_filterLineEdit(new QLineEdit(this))
, m_resetShortcutsButton(new QPushButton(tr("Reset Shortcuts"), this))
{
auto h = new QHBoxLayout();
h->addWidget(m_filterLineEdit);
h->addWidget(m_resetShortcutsButton);
h->setStretch(0, 1);
auto l = new QVBoxLayout(this);
l->addWidget(new QLabel(tr("Double click an action to change its shortcut")));
l->addLayout(h);
l->addWidget(m_tableView);
m_model.setColumnCount(2);
m_model.setHorizontalHeaderLabels({QObject::tr("Action"), QObject::tr("Shortcuts")});
m_proxy.setFilterKeyColumn(-1);
m_proxy.setFilterCaseSensitivity(Qt::CaseInsensitive);
m_proxy.setSourceModel(&m_model);
m_filterLineEdit->setPlaceholderText(tr("Filter..."));
connect(m_filterLineEdit, &QLineEdit::textChanged, &m_proxy, &QSortFilterProxyModel::setFilterFixedString);
connect(m_resetShortcutsButton, &QPushButton::clicked, this, [this]() {
auto ac = ActionCollection::instance();
for (auto action : ac->actions()) {
action->setShortcut(ac->defaultShortcut(action));
}
loadSettings();
});
m_tableView->setModel(&m_proxy);
m_tableView->setSortingEnabled(true);
m_tableView->sortByColumn(0, Qt::AscendingOrder);
m_tableView->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
m_tableView->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
m_tableView->verticalHeader()->hide();
m_tableView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_tableView->setSelectionMode(QAbstractItemView::SingleSelection);
m_tableView->setSelectionBehavior(QAbstractItemView::SelectRows);
connect(m_tableView, &QTableView::doubleClicked, this, &ShortcutSettingsWidget::onDoubleClicked);
}
void loadSettings()
{
m_changedActions.clear();
m_filterLineEdit->clear();
m_model.setRowCount(0);
const auto& actions = ActionCollection::instance()->actions();
for (auto a : actions) {
auto name = a->toolTip().isEmpty() ? acceleratorsStrippedText(a->text()) : a->toolTip();
auto col1 = new QStandardItem(name);
col1->setData(QVariant::fromValue(a), Qt::UserRole);
auto col2 = new QStandardItem(a->shortcut().toString());
m_model.appendRow({col1, col2});
}
}
void saveSettings()
{
if (m_changedActions.count()) {
for (const auto& action : m_changedActions.keys()) {
action->setShortcut(m_changedActions.value(action));
}
ActionCollection::instance()->saveShortcuts();
}
m_changedActions.clear();
m_filterLineEdit->clear();
}
private:
static QString acceleratorsStrippedText(QString text)
{
for (int i = 0; i < text.size(); ++i) {
if (text.at(i) == QLatin1Char('&') && i + 1 < text.size() && text.at(i + 1) != QLatin1Char('&')) {
text.remove(i, 1);
}
}
return text;
}
void onDoubleClicked(QModelIndex index)
{
if (index.column() != 0) {
index = index.sibling(index.row(), 0);
}
index = m_proxy.mapToSource(index);
auto action = index.data(Qt::UserRole).value<QAction*>();
KeySequenceDialog dialog(this);
int ret = dialog.exec();
QKeySequence change;
if (ret == QDialog::Accepted) {
change = dialog.keySequence();
} else if (dialog.shouldRestoreDefault()) {
change = ActionCollection::instance()->defaultShortcut(action);
} else {
// Rejected
return;
}
auto conflict = ActionCollection::instance()->isConflictingShortcut(action, change);
bool hasConflict = false;
if (conflict) {
// we conflicted with an action inside action collection
// check if the conflicted action is updated here
if (!m_changedActions.contains(conflict)) {
hasConflict = true;
} else {
if (m_changedActions.value(conflict) == change) {
hasConflict = true;
}
}
} else if (!change.isEmpty()) {
// we did not conflict with any shortcut inside action collection
// check if we conflict with any locally modified action
for (auto chAction : m_changedActions.keys()) {
if (m_changedActions.value(chAction) == change) {
hasConflict = true;
conflict = chAction;
break;
}
}
}
if (hasConflict) {
auto conflictName =
conflict->toolTip().isEmpty() ? acceleratorsStrippedText(conflict->text()) : conflict->toolTip();
auto conflictSeq = change.toString();
auto ans = MessageBox::question(
this,
tr("Shortcut Conflict"),
tr("Shortcut %1 conflicts with '%2'. Overwrite shortcut?").arg(conflictSeq, conflictName),
MessageBox::Overwrite | MessageBox::Discard,
MessageBox::Discard);
if (ans == MessageBox::Discard) {
// Bail out before making any changes
return;
}
// Reset the conflict shortcut
m_changedActions[conflict] = {};
for (auto item : m_model.findItems(conflictSeq, Qt::MatchExactly, 1)) {
item->setText("");
}
}
m_changedActions[action] = change;
auto item = m_model.itemFromIndex(index.sibling(index.row(), 1));
item->setText(change.toString());
}
QTableView* m_tableView;
QLineEdit* m_filterLineEdit;
QPushButton* m_resetShortcutsButton;
QStandardItemModel m_model;
QSortFilterProxyModel m_proxy;
QHash<QAction*, QKeySequence> m_changedActions;
};
QString ShortcutSettingsPage::name()
{
return QObject::tr("Shortcuts");
}
QIcon ShortcutSettingsPage::icon()
{
return icons()->icon("auto-type");
}
QWidget* ShortcutSettingsPage::createWidget()
{
return new ShortcutSettingsWidget();
}
void ShortcutSettingsPage::loadSettings(QWidget* widget)
{
static_cast<ShortcutSettingsWidget*>(widget)->loadSettings();
}
void ShortcutSettingsPage::saveSettings(QWidget* widget)
{
static_cast<ShortcutSettingsWidget*>(widget)->saveSettings();
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2024 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_SHORTCUT_SETTINGSPAGE_H
#define KEEPASSXC_SHORTCUT_SETTINGSPAGE_H
#include "gui/ApplicationSettingsWidget.h"
class ShortcutSettingsPage : public ISettingsPage
{
public:
explicit ShortcutSettingsPage() = default;
~ShortcutSettingsPage() override = default;
QString name() override;
QIcon icon() override;
QWidget* createWidget() override;
void loadSettings(QWidget* widget) override;
void saveSettings(QWidget* widget) override;
};
#endif // KEEPASSXC_BROWSERSETTINGSPAGE_H

View file

@ -20,13 +20,8 @@
#include <QKeyEvent>
#include <QToolTip>
#include "autotype/AutoType.h"
ShortcutWidget::ShortcutWidget(QWidget* parent)
: QLineEdit(parent)
, m_key(static_cast<Qt::Key>(0))
, m_modifiers(nullptr)
, m_locked(false)
{
setReadOnly(true);
}
@ -41,6 +36,11 @@ Qt::KeyboardModifiers ShortcutWidget::modifiers() const
return m_modifiers;
}
QKeySequence ShortcutWidget::sequence() const
{
return (m_key == Qt::Key_unknown) ? QKeySequence() : QKeySequence(m_key | m_modifiers);
}
void ShortcutWidget::setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
{
m_key = key;
@ -48,22 +48,15 @@ void ShortcutWidget::setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers)
m_locked = true;
displayShortcut(m_key, m_modifiers);
QString error;
if (autoType()->registerGlobalShortcut(m_key, m_modifiers, &error)) {
setStyleSheet("");
} else {
QToolTip::showText(mapToGlobal(rect().bottomLeft()), error);
setStyleSheet("background-color: #FF9696;");
}
emit shortcutChanged(m_key, m_modifiers);
}
void ShortcutWidget::resetShortcut()
{
m_key = static_cast<Qt::Key>(0);
m_modifiers = nullptr;
m_key = Qt::Key_unknown;
m_modifiers = Qt::NoModifier;
m_locked = false;
autoType()->unregisterGlobalShortcut();
emit shortcutReset();
}
void ShortcutWidget::keyPressEvent(QKeyEvent* event)
@ -116,13 +109,11 @@ void ShortcutWidget::keyEvent(QKeyEvent* event)
setShortcut(key, modifiers);
} else {
resetShortcut();
setStyleSheet("");
displayShortcut(key, modifiers);
}
} else {
if (m_locked) {
resetShortcut();
setStyleSheet("");
}
displayShortcut(static_cast<Qt::Key>(0), modifiers);

View file

@ -18,6 +18,7 @@
#ifndef KEEPASSX_SHORTCUTWIDGET_H
#define KEEPASSX_SHORTCUTWIDGET_H
#include <QKeySequence>
#include <QLineEdit>
class ShortcutWidget : public QLineEdit
@ -26,10 +27,17 @@ class ShortcutWidget : public QLineEdit
public:
explicit ShortcutWidget(QWidget* parent = nullptr);
Qt::Key key() const;
Qt::KeyboardModifiers modifiers() const;
QKeySequence sequence() const;
void setShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers);
signals:
void shortcutChanged(Qt::Key key, Qt::KeyboardModifiers modifiers);
void shortcutReset();
protected:
void keyPressEvent(QKeyEvent* event) override;
void keyReleaseEvent(QKeyEvent* event) override;
@ -39,9 +47,9 @@ private:
void displayShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers);
void resetShortcut();
Qt::Key m_key;
Qt::KeyboardModifiers m_modifiers;
bool m_locked;
Qt::Key m_key = Qt::Key_unknown;
Qt::KeyboardModifiers m_modifiers = Qt::NoModifier;
bool m_locked = false;
};
#endif // KEEPASSX_SHORTCUTWIDGET_H