Chord: add Acme left+right = Paste

The paste chord (Acme 1-3) now works: with the left button held, a right tap
pastes over the selection via edit_paste (standard KDE clipboard). This is
conflict-free because the radial gesture is already suppressed while the left
button is held — right alone opens the radial, right while left-held pastes.
The paired right release is swallowed so it doesn't leak a context menu or
other action. All 5 ctest suites green.
This commit is contained in:
Levi Neely 2026-10-07 17:19:57 +02:00
parent 87f5587bd4
commit 3129662f31
3 changed files with 34 additions and 21 deletions

View File

@ -128,20 +128,18 @@ Therefore the palette is replaced, not extended, and the replacement is built on
- Not yet done: free-form stroke gestures (only button-press gesture so far);
user-file config loading (built-in default only); frecency.
### Mouse chording (Acme-style) — DONE (first increment)
- Left+middle chord = **Cut**: with the left button held while selecting
(button 1 anchor), a tap of the middle button (button 2) cuts the current
selection via `edit_cut`, which uses the **standard KDE clipboard**
(Klipper-backed), not the X11 primary selection.
- **Middle-click primary paste is suppressed** inside the editor. Kate pastes
the primary selection on the middle event; the filter consumes the middle
button on **both press and release** (swallowing only the press let the paste
leak through on release). So middle-click no longer pastes in the editor —
paste is the normal `Ctrl+V` / palette paste from the KDE clipboard.
- Right-button chords are intentionally **not** ported — the right button is the
radial gesture. (Revisit later.)
- The radial gesture is suppressed while the left button is held, so an RMB tap
mid-selection (or during a left+middle chord) does not pop the radial.
### Mouse chording (Acme-style) — DONE
- Left-button chords (button 1 held while selecting is the anchor):
**left + middle = Cut**, **left + right = Paste**. Both use `edit_cut` /
`edit_paste` → the **standard KDE clipboard** (Klipper-backed), not the X11
primary selection.
- The right-button paste chord is free of conflict because the radial gesture is
already suppressed while the left button is held: right alone = radial, right
while left-held = paste.
- **Middle-click primary paste is suppressed** inside the editor — the middle
button is consumed on both press and release (the paste fires on release, so
swallowing only the press leaked it). Paste in the editor is the KDE-clipboard
paste (`Ctrl+V`, palette, or the left+right chord).
### M5 — Command vocabulary + project model + switchers — NEXT
- `:`-verb pack via `KTextEditor::Command`: `sort`, `align`, `json`, `b64`,

View File

@ -347,13 +347,14 @@ bool OllieView::eventFilter(QObject *obj, QEvent *event)
}
}
// Acme-style left+middle chord = Cut, and suppression of X11 primary-
// selection paste. Middle-button events inside the view are consumed on
// BOTH press and release (Kate pastes the primary selection on the middle
// event; swallowing only the press lets the paste leak through on release).
// When the left button is held, the middle tap cuts the current selection
// via edit_cut, which uses the standard KDE clipboard (Klipper-backed).
// Right-button chords are intentionally not ported (RMB is the radial).
// Acme-style left-button chords, plus suppression of X11 primary-selection
// paste. Button 1 (left) held while selecting is the anchor:
// left + middle tap = Cut, left + right tap = Paste.
// Both use edit_cut / edit_paste, which use the standard KDE clipboard
// (Klipper-backed). The right-button chord is free because the radial
// gesture is already suppressed while the left button is held. Middle-button
// events in the view are consumed on BOTH press and release to kill the
// middle-click primary paste (it fires on release, not just press).
if (event->type() == QEvent::MouseButtonRelease) {
auto *mev = static_cast<QMouseEvent *>(event);
if (mev->button() == Qt::LeftButton) {
@ -363,12 +364,25 @@ bool OllieView::eventFilter(QObject *obj, QEvent *event)
m_swallowMiddleRelease = false;
return true; // suppress the primary paste that fires on release
}
if (mev->button() == Qt::RightButton && m_swallowRightRelease) {
m_swallowRightRelease = false;
return true; // paired release of a paste chord
}
} else if (event->type() == QEvent::MouseButtonPress) {
auto *mev = static_cast<QMouseEvent *>(event);
KTextEditor::View *v = m_mainWindow->activeView();
auto *w = qobject_cast<QWidget *>(obj);
const bool inView = v && w && v->isAncestorOf(w);
// left + right tap = Paste over the selection.
if (mev->button() == Qt::RightButton && m_leftDownInView && inView) {
if (QAction *paste = findActionByName(QStringLiteral("edit_paste"))) {
paste->trigger();
}
m_swallowRightRelease = true;
return true; // consume: chord, not a radial / context menu
}
if (mev->button() == Qt::MiddleButton && inView) {
if (m_leftDownInView) {
if (QAction *cut = findActionByName(QStringLiteral("edit_cut"))) {

View File

@ -91,6 +91,7 @@ private:
bool m_gestureActive = false; // an RMB drag gesture is in progress
bool m_leftDownInView = false; // LMB held inside the view (Acme chord anchor)
bool m_swallowMiddleRelease = false; // swallow the release paired with a consumed middle press
bool m_swallowRightRelease = false; // swallow the release paired with a left+right paste chord
QHash<QString, QPointer<QAction>> m_actionsById;
};