diff --git a/src/app/clipboardserver.cpp b/src/app/clipboardserver.cpp index 3a637572fd..295b9bc88c 100644 --- a/src/app/clipboardserver.cpp +++ b/src/app/clipboardserver.cpp @@ -684,7 +684,7 @@ void ClipboardServer::loadSettings(AppConfig *appConfig) m_sharedData->editor = appConfig->option(); m_sharedData->maxItems = appConfig->option(); m_sharedData->textWrap = appConfig->option(); - m_sharedData->viMode = appConfig->option(); + m_sharedData->navigationStyle = appConfig->option(); m_sharedData->saveOnReturnKey = !appConfig->option(); m_sharedData->moveItemOnReturnKey = appConfig->option(); m_sharedData->showSimpleItems = appConfig->option(); diff --git a/src/common/appconfig.h b/src/common/appconfig.h index 795944384c..c229d895d5 100644 --- a/src/common/appconfig.h +++ b/src/common/appconfig.h @@ -3,6 +3,7 @@ #ifndef APPCONFIG_H #define APPCONFIG_H +#include "common/navigationstyle.h" #include "common/settings.h" class QString; @@ -120,8 +121,8 @@ struct confirm_exit : Config { static Value defaultValue() { return true; } }; -struct vi : Config { - static QString name() { return QStringLiteral("vi"); } +struct navigation_style : Config { + static QString name() { return QStringLiteral("navigation_style"); } }; struct save_filter_history : Config { diff --git a/src/common/common.cpp b/src/common/common.cpp index decb41ba78..e145074c97 100644 --- a/src/common/common.cpp +++ b/src/common/common.cpp @@ -726,6 +726,71 @@ bool handleViKey(QKeyEvent *event, QObject *eventReceiver) return true; } +bool handleEmacsKey(QKeyEvent *event, QObject *eventReceiver) +{ + int key = event->key(); + Qt::KeyboardModifiers mods = event->modifiers(); + + switch ( key ) { + case Qt::Key_V: + if (mods & Qt::ControlModifier) { + key = Qt::Key_PageDown; + mods = mods & ~Qt::ControlModifier; + break; + } + if (mods & Qt::AltModifier) { + key = Qt::Key_PageUp; + mods = mods & ~Qt::AltModifier; + break; + } + return false; + break; + case Qt::Key_N: + if (mods & Qt::ControlModifier) { + key = Qt::Key_Down; + mods = mods & ~Qt::ControlModifier; + break; + } + return false; + case Qt::Key_P: + if (mods & Qt::ControlModifier) { + key = Qt::Key_Up; + mods = mods & ~Qt::ControlModifier; + break; + } + return false; + case Qt::Key_Less: + if ((mods & Qt::AltModifier)) { + key = Qt::Key_Home; + mods = mods & ~(Qt::ShiftModifier | Qt::AltModifier); + break; + } + return false; + case Qt::Key_Greater: + if ((mods & Qt::AltModifier)) { + key = Qt::Key_End; + mods = mods & ~(Qt::ShiftModifier | Qt::AltModifier); + break; + } + return false; + case Qt::Key_G: + if (mods & Qt::ControlModifier) { + key = Qt::Key_Escape; + mods = mods & ~Qt::ControlModifier; + break; + } + return false; + default: + return false; + } + + QKeyEvent event2(QEvent::KeyPress, key, mods, event->text()); + QCoreApplication::sendEvent(eventReceiver, &event2); + event->accept(); + + return true; +} + bool canDropToTab(const QDropEvent &event) { const auto &data = *event.mimeData(); diff --git a/src/common/common.h b/src/common/common.h index 573659d7b4..e521a93b56 100644 --- a/src/common/common.h +++ b/src/common/common.h @@ -78,6 +78,11 @@ bool isClipboardData(const QVariantMap &data); */ bool handleViKey(QKeyEvent *event, QObject *eventReceiver); +/** + * Handle key for Emacs mode. + */ +bool handleEmacsKey(QKeyEvent *event, QObject *eventReceiver); + /** * Return true only if tabs can accept the drag'n'drop event. */ diff --git a/src/common/navigationstyle.h b/src/common/navigationstyle.h new file mode 100644 index 0000000000..13f0196a0d --- /dev/null +++ b/src/common/navigationstyle.h @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +enum class NavigationStyle { + Default, + Vi, + Emacs +}; diff --git a/src/gui/clipboardbrowser.cpp b/src/gui/clipboardbrowser.cpp index 02dacaaad6..2816106cf8 100644 --- a/src/gui/clipboardbrowser.cpp +++ b/src/gui/clipboardbrowser.cpp @@ -1378,11 +1378,15 @@ void ClipboardBrowser::keyPressEvent(QKeyEvent *event) return; // translate keys for vi mode - if (m_sharedData->viMode && handleViKey(event, this)) { + if (m_sharedData->navigationStyle == NavigationStyle::Vi && handleViKey(event, this)) { d.updateIfNeeded(); return; } + // translate keys for emacs mode + if (m_sharedData->navigationStyle == NavigationStyle::Emacs && handleEmacsKey(event, this)) + return; + const Qt::KeyboardModifiers mods = event->modifiers(); if (mods == Qt::AltModifier) diff --git a/src/gui/clipboardbrowsershared.h b/src/gui/clipboardbrowsershared.h index 5d0ff429e1..c38a7e5258 100644 --- a/src/gui/clipboardbrowsershared.h +++ b/src/gui/clipboardbrowsershared.h @@ -2,6 +2,7 @@ #ifndef CLIPBOARDBROWSERSHARED_H #define CLIPBOARDBROWSERSHARED_H +#include "common/navigationstyle.h" #include "gui/menuitems.h" #include "gui/theme.h" @@ -17,7 +18,7 @@ struct ClipboardBrowserShared { QString editor; int maxItems = 100; bool textWrap = true; - bool viMode = false; + NavigationStyle navigationStyle = NavigationStyle::Default; bool saveOnReturnKey = false; bool moveItemOnReturnKey = false; bool showSimpleItems = false; diff --git a/src/gui/configurationmanager.cpp b/src/gui/configurationmanager.cpp index b855340553..40cd3daa5f 100644 --- a/src/gui/configurationmanager.cpp +++ b/src/gui/configurationmanager.cpp @@ -257,7 +257,7 @@ void ConfigurationManager::initOptions() bind(m_tabHistory->checkBoxMove); bind(m_tabGeneral->checkBoxClip); bind(m_tabGeneral->checkBoxConfirmExit); - bind(m_tabGeneral->checkBoxViMode); + bind(m_tabGeneral->comboBoxNavigationStyle); bind(m_tabGeneral->checkBoxSaveFilterHistory); bind(m_tabGeneral->checkBoxAutocompleteCommands); bind(m_tabGeneral->checkBoxAlwaysOnTop); @@ -376,6 +376,11 @@ void ConfigurationManager::bind(const QString &optionKey, QComboBox *obj, int de m_options[optionKey] = Option(defaultValue, "currentIndex", obj); } +void ConfigurationManager::bind(const QString &optionKey, QComboBox *obj, NavigationStyle defaultValue) +{ + m_options[optionKey] = Option(static_cast(defaultValue), "currentIndex", obj); +} + void ConfigurationManager::bind(const QString &optionKey, const QVariant &defaultValue, const char *description) { m_options[optionKey] = Option(defaultValue, description); diff --git a/src/gui/configurationmanager.h b/src/gui/configurationmanager.h index d9688977ea..b7336050ba 100644 --- a/src/gui/configurationmanager.h +++ b/src/gui/configurationmanager.h @@ -32,6 +32,7 @@ class QComboBox; class QLineEdit; class QListWidgetItem; class QSpinBox; +enum class NavigationStyle; /** * Configuration dialog. @@ -109,6 +110,7 @@ class ConfigurationManager final : public QDialog void bind(const QString &optionKey, QSpinBox *obj, int defaultValue); void bind(const QString &optionKey, QLineEdit *obj, const QString &defaultValue); void bind(const QString &optionKey, QComboBox *obj, int defaultValue); + void bind(const QString &optionKey, QComboBox *obj, NavigationStyle defaultValue); void bind(const QString &optionKey, const QVariant &defaultValue, const char *description); void updateTabComboBoxes(); diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 4ebf849d74..26182ce703 100644 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -1054,7 +1054,7 @@ void MainWindow::setScriptOverrides(const QVector &overrides, int actionId) bool MainWindow::isScriptOverridden(int id) const { - return + return // Assume everything is overridden until collectOverrides() finishes (m_actionCollectOverrides && m_actionCollectOverrides->isRunning() && m_overrides.isEmpty()) || std::binary_search(m_overrides.begin(), m_overrides.end(), id); @@ -1345,7 +1345,7 @@ void MainWindow::onSearchShowRequest(const QString &text) enterSearchMode(); - if (!m_options.viMode || text != "/") { + if (m_options.navigationStyle != NavigationStyle::Vi || text != "/") { ui->searchBar->setText(text); ui->searchBar->end(false); } @@ -2530,7 +2530,7 @@ void MainWindow::keyPressEvent(QKeyEvent *event) if (m_options.hideTabs && key == Qt::Key_Alt) setHideTabs(false); - if (m_options.viMode) { + if (m_options.navigationStyle == NavigationStyle::Vi) { if (modifiers == Qt::ControlModifier && key == Qt::Key_BracketLeft) { onEscape(); return; @@ -2557,6 +2557,18 @@ void MainWindow::keyPressEvent(QKeyEvent *event) } } + if (m_options.navigationStyle == NavigationStyle::Emacs) { + if ((modifiers == Qt::ControlModifier && key == Qt::Key_G) + || (key == Qt::Key_Escape)) { + onEscape(); + return; + } + + if (browseMode() && c && handleEmacsKey(event, c)) { + return; + } + } + if ( event->matches(QKeySequence::NextChild) ) { nextTab(); return; @@ -2702,10 +2714,9 @@ void MainWindow::loadSettings(QSettings &settings, AppConfig *appConfig) setAlwaysOnTop(this, alwaysOnTop); setAlwaysOnTop(m_commandDialog.data(), alwaysOnTop); - // Vi mode - m_options.viMode = appConfig->option(); - m_trayMenu->setViModeEnabled(m_options.viMode); - m_menu->setViModeEnabled(m_options.viMode); + m_options.navigationStyle = appConfig->option(); + m_trayMenu->setNavigationStyle(m_options.navigationStyle); + m_menu->setNavigationStyle(m_options.navigationStyle); // Number search m_trayMenu->setNumberSearchEnabled(m_sharedData->numberSearch); diff --git a/src/gui/mainwindow.h b/src/gui/mainwindow.h index b922251e43..742bc70a63 100644 --- a/src/gui/mainwindow.h +++ b/src/gui/mainwindow.h @@ -5,6 +5,7 @@ #include "common/clipboardmode.h" #include "common/command.h" +#include "common/navigationstyle.h" #include "gui/clipboardbrowsershared.h" #include "gui/menuitems.h" #include "item/persistentdisplayitem.h" @@ -34,7 +35,6 @@ class Theme; class TrayMenu; class ToolBar; class QModelIndex; -struct MainWindowOptions; struct NotificationButton; Q_DECLARE_METATYPE(QPersistentModelIndex) @@ -71,7 +71,7 @@ struct MainWindowOptions { bool activatePastes() const { return itemActivationCommands & ActivatePastes; } bool confirmExit = true; - bool viMode = false; + NavigationStyle navigationStyle = NavigationStyle::Default; bool trayCommands = false; bool trayCurrentTab = false; QString trayTabName; diff --git a/src/gui/traymenu.cpp b/src/gui/traymenu.cpp index 93a6e1dc69..c6be4bf9ae 100644 --- a/src/gui/traymenu.cpp +++ b/src/gui/traymenu.cpp @@ -58,7 +58,6 @@ TrayMenu::TrayMenu(QWidget *parent) : QMenu(parent) , m_clipboardItemActionCount(0) , m_omitPaste(false) - , m_viMode(false) , m_numberSearch(false) { m_clipboardItemActionsSeparator = addSeparator(); @@ -105,8 +104,13 @@ bool TrayMenu::updateIconFromData(QAction *act, const QVariantMap &data) QAction *TrayMenu::addClipboardItemAction(const QVariantMap &data, bool showImages) { // Show search text at top of the menu. - if ( m_clipboardItemActionCount == 0 && m_searchText.isEmpty() ) - setSearchMenuItem( m_viMode ? tr("Press '/' to search") : tr("Type to search") ); + if ( m_clipboardItemActionCount == 0 && m_searchText.isEmpty() ) { + if (m_navigationStyle == NavigationStyle::Vi) { + setSearchMenuItem( tr("Press '/' to search") ); + } else { + setSearchMenuItem( tr("Type to search") ); + } + } QAction *act = addAction(QString()); m_clipboardActions.append(act); @@ -207,9 +211,9 @@ void TrayMenu::clearAllActions() m_searchText.clear(); } -void TrayMenu::setViModeEnabled(bool enabled) +void TrayMenu::setNavigationStyle(NavigationStyle style) { - m_viMode = enabled; + m_navigationStyle = style; } void TrayMenu::setNumberSearchEnabled(bool enabled) @@ -220,9 +224,14 @@ void TrayMenu::setNumberSearchEnabled(bool enabled) void TrayMenu::keyPressEvent(QKeyEvent *event) { const int key = event->key(); + Qt::KeyboardModifiers mods = event->modifiers(); m_omitPaste = false; - if ( m_viMode && m_searchText.isEmpty() && handleViKey(event, this) ) { + bool not_searching = m_searchText.isEmpty(); + + if ( not_searching && m_navigationStyle == NavigationStyle::Vi && handleViKey(event, this) ) { + return; + } else if ( not_searching && m_navigationStyle == NavigationStyle::Emacs && handleEmacsKey(event, this) ) { return; } else { // Movement in tray menu. @@ -255,7 +264,7 @@ void TrayMenu::keyPressEvent(QKeyEvent *event) default: // Type text for search. if ( (m_clipboardItemActionCount > 0 || !m_searchText.isEmpty()) - && (!m_viMode || !m_searchText.isEmpty() || key == Qt::Key_Slash) + && (m_navigationStyle != NavigationStyle::Vi || !m_searchText.isEmpty() || key == Qt::Key_Slash) && !event->modifiers().testFlag(Qt::AltModifier) && !event->modifiers().testFlag(Qt::ControlModifier) ) { @@ -341,7 +350,7 @@ void TrayMenu::search(const QString &text) return; m_searchText = text; - emit searchRequest(m_viMode ? m_searchText.mid(1) : m_searchText); + emit searchRequest((m_navigationStyle == NavigationStyle::Vi) ? m_searchText.mid(1) : m_searchText); } void TrayMenu::markItemInClipboard(const QVariantMap &clipboardData) diff --git a/src/gui/traymenu.h b/src/gui/traymenu.h index 3ed68f0d2b..e7643f444e 100644 --- a/src/gui/traymenu.h +++ b/src/gui/traymenu.h @@ -3,6 +3,8 @@ #ifndef TRAYMENU_H #define TRAYMENU_H +#include "common/navigationstyle.h" + #include #include #include @@ -36,8 +38,7 @@ class TrayMenu final : public QMenu /** Clear clipboard item actions and curstom actions. */ void clearAllActions(); - /** Handle Vi shortcuts. */ - void setViModeEnabled(bool enabled); + void setNavigationStyle(NavigationStyle style); /** Enable searching for numbers. */ void setNumberSearchEnabled(bool enabled); @@ -82,8 +83,8 @@ class TrayMenu final : public QMenu int m_clipboardItemActionCount; bool m_omitPaste; - bool m_viMode; bool m_numberSearch; + NavigationStyle m_navigationStyle = NavigationStyle::Default; QString m_searchText; diff --git a/src/ui/configtabgeneral.ui b/src/ui/configtabgeneral.ui index 8fa47b4b9b..5ff2b7d70d 100644 --- a/src/ui/configtabgeneral.ui +++ b/src/ui/configtabgeneral.ui @@ -151,14 +151,56 @@ - - - Support for Vi navigation keys (H, J, K, L and more), slash (/) key to search - - - &Vi style navigation + + + 0 - + + + + Navigation style / Keymap: + + + comboBoxNavigationStyle + + + + + + + Support for Vi navigation (keys H, J, K, L, / and more) and Emacs navigation (Ctrl+N, P, V and more) + + + + Default + + + + + Vi + + + + + Emacs + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + @@ -284,7 +326,7 @@ checkBoxOpenWindowsOnCurrentScreen checkBoxConfirmExit checkBoxAutostart - checkBoxViMode + comboBoxNavigationStyle checkBoxSaveFilterHistory checkBoxAutocompleteCommands checkBoxClip