From 0e1c6943ace5db66bbb9d3c4983c2cd668feb71d Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Thu, 8 Oct 2026 10:42:28 +0200 Subject: [PATCH] Launchers: handle Alt+X/P/G in eventFilter, not QAction shortcuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alt+P (and the other launcher keys) collided with Kate's own Alt+ bindings; registered as ApplicationShortcut QActions this makes Qt report an ambiguous shortcut and fire neither. Go nuclear: drop the QAction shortcuts entirely and intercept Alt+X/P/G in eventFilter (accept ShortcutOverride, act on KeyPress), scoped to our window — the same ambiguity-proof technique used for the Acme keys. QActions kept for objectName/label; onRadialActivated now resolves all three launcher ids directly so the radial escape-hatch still reaches them. --- docs/PLAN.md | 16 +++++-- src/plugin/ollieplugin.cpp | 86 ++++++++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 36 deletions(-) diff --git a/docs/PLAN.md b/docs/PLAN.md index 2bff0fb..b3a19b7 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -78,9 +78,17 @@ Therefore the palette is replaced, not extended, and the replacement is built on ### M3 — KTextEditor plugin + M-x + action registry — DONE - `src/plugin/ollieplugin.{h,cpp}` + `ollieplugin.json`: a loadable `KTextEditor::Plugin` (`olliepalette.so`) built with `kcoreaddons_add_plugin`. -- `OllieView` (one per MainWindow) registers the **M-x** action, `Alt+X`, a - single modifier with the keys adjacent on a compact layout; `ApplicationShortcut` - context so it is always live. +- `OllieView` (one per MainWindow) provides the **M-x** launcher on `Alt+X`. + LAUNCHER KEYS ARE NOT QAction SHORTCUTS: `Alt+X` (M-x), `Alt+P` (Go to File) + and `Alt+G` (Go to Symbol) are intercepted in `eventFilter()` (accept the + `ShortcutOverride`, act on the `KeyPress`), scoped to this plugin's window. + Registering them as `ApplicationShortcut` QActions clashed with Kate's own + `Alt+` bindings and Qt refused to fire either ("ambiguous shortcut"). + The event-filter route cannot be ambiguous because nothing is registered — + the same technique already used for the Acme line-editing keys. The QActions + still exist (objectName/label) for command-palette harvest and as radial + escape-hatch slices (`ollie_command_palette` / `ollie_goto_file` / + `ollie_goto_symbol`, handled directly in `onRadialActivated`). - On trigger it aggregates every enabled, visible `QAction` from the window's `guiFactory()->clients()` action collections, keyed by stable `objectName`, and feeds them to `PaletteWidget`. The action's current shortcut is shown in @@ -237,7 +245,7 @@ Therefore the palette is replaced, not extended, and the replacement is built on whole path (orderless: "palette model" matches `src/palette/palettemodel.cpp`) and highlights matched characters along it; a base-name hit still ranks first because `/` is a word boundary and the length penalty favours shorter paths. - The root is `currentProjectRoot()` (explicit folder when set, else derived). + The root is `currentProjectRoot()` (Kate's loaded project, else derived). Activation opens the file via `MainWindow::openUrl` + `activateView`. Also reachable from the M-x palette (`ollie:switch:file`) and, by objectName `ollie_goto_file`, from a radial. (2 path-ranking tests in diff --git a/src/plugin/ollieplugin.cpp b/src/plugin/ollieplugin.cpp index 813c696..e0687bb 100644 --- a/src/plugin/ollieplugin.cpp +++ b/src/plugin/ollieplugin.cpp @@ -221,43 +221,30 @@ OllieView::OllieView(OlliePlugin *plugin, KTextEditor::MainWindow *mainWindow) QStringLiteral("olliepalette/frecency.json"))); m_frecency.load(); - // The one keyboard door: Alt+X (M-x). A single modifier, no chord, and the - // keys sit together on a compact layout. - auto *openAction = new QAction(i18n("Command Palette (M-x)"), this); + // The three launcher "doors". Their shortcuts are NOT registered as + // QAction/ApplicationShortcut: Kate (and other plugins) already claim many + // Alt+ combos, and a registered duplicate makes Qt refuse to fire + // either, reporting an "ambiguous shortcut". We therefore register NO + // shortcut here and instead intercept the key in eventFilter() (the same + // technique used for the Acme line-editing keys), which cannot be + // ambiguous because nothing is registered. The QActions are kept only for + // their objectName/label (command-palette harvest, radial escape-hatch). + auto *openAction = new QAction(i18n("Command Palette (M-x) [Alt+X]"), this); openAction->setObjectName(QStringLiteral("ollie_command_palette")); - openAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_X)); - openAction->setShortcutContext(Qt::ApplicationShortcut); connect(openAction, &QAction::triggered, this, &OllieView::showCommandPalette); - // Attach the shortcut to the window so it is always live. + auto *gotoFileAction = new QAction(i18n("Go to File (project) [Alt+P]"), this); + gotoFileAction->setObjectName(QStringLiteral("ollie_goto_file")); + connect(gotoFileAction, &QAction::triggered, this, &OllieView::showFileSwitcher); + + auto *gotoSymbolAction = new QAction(i18n("Go to Symbol (project) [Alt+G]"), this); + gotoSymbolAction->setObjectName(QStringLiteral("ollie_goto_symbol")); + connect(gotoSymbolAction, &QAction::triggered, this, &OllieView::showSymbolSwitcher); + + // Keep them discoverable as window actions (no shortcut attached). if (QWidget *w = m_mainWindow->window()) { w->addAction(openAction); - } - - // Project "Go to File" switcher: Alt+P (single modifier; the Sublime/VSCode - // Ctrl+P idiom adapted to one-handed use). Lists the project's files — - // "open folder == open project" — scoped to the active document's git/root - // directory. - auto *gotoFileAction = new QAction(i18n("Go to File (project)"), this); - gotoFileAction->setObjectName(QStringLiteral("ollie_goto_file")); - gotoFileAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_P)); - gotoFileAction->setShortcutContext(Qt::ApplicationShortcut); - connect(gotoFileAction, &QAction::triggered, this, &OllieView::showFileSwitcher); - if (QWidget *w = m_mainWindow->window()) { w->addAction(gotoFileAction); - } - - // Project "Go to Symbol" switcher: Alt+G (single modifier; "Go to symbol"). - // Alt+R is reserved for the radial key trigger, so the symbol switcher uses - // Alt+G instead. Lists ctags symbols across the project's files, scoped to - // the same root as the file switcher. Activation opens the file and jumps to - // the symbol's line. - auto *gotoSymbolAction = new QAction(i18n("Go to Symbol (project)"), this); - gotoSymbolAction->setObjectName(QStringLiteral("ollie_goto_symbol")); - gotoSymbolAction->setShortcut(QKeySequence(Qt::ALT | Qt::Key_G)); - gotoSymbolAction->setShortcutContext(Qt::ApplicationShortcut); - connect(gotoSymbolAction, &QAction::triggered, this, &OllieView::showSymbolSwitcher); - if (QWidget *w = m_mainWindow->window()) { w->addAction(gotoSymbolAction); } @@ -879,6 +866,33 @@ bool OllieView::eventFilter(QObject *obj, QEvent *event) return true; // consume the keypress } } + + // Launcher doors: Alt+X (M-x), Alt+P (Go to File), Alt+G (Go to Symbol). + // Handled here rather than via QAction shortcuts so they can never clash + // with Kate's own Alt+ bindings (which produced "ambiguous + // shortcut" and swallowed the key). Scope to this plugin's window so we + // do not hijack the combo in other Kate main windows. + const bool altOnly = + (ke->modifiers() & Qt::KeyboardModifierMask) == Qt::AltModifier; + if (altOnly + && (ke->key() == Qt::Key_X || ke->key() == Qt::Key_P || ke->key() == Qt::Key_G)) { + auto *w = qobject_cast(obj); + QWidget *ourWindow = m_mainWindow->window(); + const bool ours = w && ourWindow && ourWindow->isAncestorOf(w); + if (ours) { + if (event->type() == QEvent::ShortcutOverride) { + event->accept(); // suppress any registered (ambiguous) shortcut + return true; + } + switch (ke->key()) { + case Qt::Key_X: showCommandPalette(); break; + case Qt::Key_P: showFileSwitcher(); break; + case Qt::Key_G: showSymbolSwitcher(); break; + default: break; + } + return true; // consume the keypress + } + } } // Bracket-pair selection: double-clicking next to a bracket selects the @@ -1045,11 +1059,19 @@ void OllieView::showRadialAt(int defIndex, const QPoint &globalPos) void OllieView::onRadialActivated(const QString &actionId) { - // The palette is reachable from inside a radial as a special slice. + // The launcher doors are reachable from inside a radial as special slices. if (actionId == QLatin1String("ollie_command_palette")) { showCommandPalette(); return; } + if (actionId == QLatin1String("ollie_goto_file")) { + showFileSwitcher(); + return; + } + if (actionId == QLatin1String("ollie_goto_symbol")) { + showSymbolSwitcher(); + return; + } if (QAction *a = findActionByName(actionId)) { a->trigger(); }