Add Acme-style left+middle mouse chord = Cut
Left-button chords only (right button stays the radial). With the left button held while selecting, a middle-button tap cuts the current selection and is consumed, so it does not also trigger a primary-selection paste; middle-click without the left held still pastes as usual. Implemented in the shared application event filter: track the left button held inside the active view, and on a middle press while held trigger edit_cut. The filter is now installed unconditionally (it serves both the radial gesture and the chord). All 5 ctest suites green.
This commit is contained in:
parent
dca2514023
commit
d117aebb7f
10
docs/PLAN.md
10
docs/PLAN.md
|
|
@ -128,6 +128,16 @@ 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);
|
- Not yet done: free-form stroke gestures (only button-press gesture so far);
|
||||||
user-file config loading (built-in default only); frecency.
|
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. Driven by the same application event filter — it tracks the left
|
||||||
|
button held inside the view and, on a middle press while held, triggers
|
||||||
|
`edit_cut` and consumes the event. Middle-click alone (no left held) still
|
||||||
|
does the usual primary-selection paste.
|
||||||
|
- Right-button chords are intentionally **not** ported — the right button is the
|
||||||
|
radial gesture. (Revisit later.)
|
||||||
|
|
||||||
### M5 — Command vocabulary + project model + switchers — NEXT
|
### M5 — Command vocabulary + project model + switchers — NEXT
|
||||||
- `:`-verb pack via `KTextEditor::Command`: `sort`, `align`, `json`, `b64`,
|
- `:`-verb pack via `KTextEditor::Command`: `sort`, `align`, `json`, `b64`,
|
||||||
`uuid`, `case`, `pipe <shell>`, etc. These populate M-x and the radial.
|
`uuid`, `case`, `pipe <shell>`, etc. These populate M-x and the radial.
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ OllieView::OllieView(OlliePlugin *plugin, KTextEditor::MainWindow *mainWindow)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupRadials();
|
setupRadials();
|
||||||
|
|
||||||
|
// One application-wide filter serves both the mouse-button radial gesture
|
||||||
|
// and the Acme-style left+middle chord, catching presses no matter which
|
||||||
|
// internal child of the view actually receives them.
|
||||||
|
qApp->installEventFilter(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
OllieView::~OllieView()
|
OllieView::~OllieView()
|
||||||
|
|
@ -284,7 +289,6 @@ void OllieView::setupRadials()
|
||||||
m_radialDefs = RadialConfig::builtinDefault();
|
m_radialDefs = RadialConfig::builtinDefault();
|
||||||
|
|
||||||
QWidget *w = m_mainWindow->window();
|
QWidget *w = m_mainWindow->window();
|
||||||
bool wantMouseGestures = false;
|
|
||||||
|
|
||||||
for (int i = 0; i < m_radialDefs.size(); ++i) {
|
for (int i = 0; i < m_radialDefs.size(); ++i) {
|
||||||
const RadialDef &def = m_radialDefs.at(i);
|
const RadialDef &def = m_radialDefs.at(i);
|
||||||
|
|
@ -312,16 +316,9 @@ void OllieView::setupRadials()
|
||||||
}
|
}
|
||||||
if (btn != Qt::NoButton) {
|
if (btn != Qt::NoButton) {
|
||||||
m_buttonToRadial.insert(static_cast<int>(btn), i);
|
m_buttonToRadial.insert(static_cast<int>(btn), i);
|
||||||
wantMouseGestures = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single application-wide filter catches the gesture no matter which
|
|
||||||
// internal child of the view actually receives the press.
|
|
||||||
if (wantMouseGestures) {
|
|
||||||
qApp->installEventFilter(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OllieView::eventFilter(QObject *obj, QEvent *event)
|
bool OllieView::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
|
@ -350,6 +347,31 @@ bool OllieView::eventFilter(QObject *obj, QEvent *event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Acme-style left+middle chord = Cut. Button 1 (left) held while selecting
|
||||||
|
// is the anchor; a tap of button 2 (middle) cuts the current selection.
|
||||||
|
// Right-button chords are intentionally not ported (RMB is the radial).
|
||||||
|
// Middle-click alone (no left held) still does the usual primary paste.
|
||||||
|
if (event->type() == QEvent::MouseButtonRelease) {
|
||||||
|
if (static_cast<QMouseEvent *>(event)->button() == Qt::LeftButton) {
|
||||||
|
m_leftDownInView = false;
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
|
||||||
|
if (mev->button() == Qt::MiddleButton && m_leftDownInView && inView) {
|
||||||
|
if (QAction *cut = findActionByName(QStringLiteral("edit_cut"))) {
|
||||||
|
cut->trigger();
|
||||||
|
}
|
||||||
|
return true; // consume: this is the chord, not a primary paste
|
||||||
|
}
|
||||||
|
if (mev->button() == Qt::LeftButton && inView) {
|
||||||
|
m_leftDownInView = true; // anchor set; selection proceeds normally
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Suppress the context menu that would otherwise follow an RMB gesture.
|
// Suppress the context menu that would otherwise follow an RMB gesture.
|
||||||
if (event->type() == QEvent::ContextMenu
|
if (event->type() == QEvent::ContextMenu
|
||||||
&& m_buttonToRadial.contains(static_cast<int>(Qt::RightButton))) {
|
&& m_buttonToRadial.contains(static_cast<int>(Qt::RightButton))) {
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ private:
|
||||||
QList<RadialDef> m_radialDefs;
|
QList<RadialDef> m_radialDefs;
|
||||||
QHash<int, int> m_buttonToRadial; // Qt::MouseButton (int) -> def index
|
QHash<int, int> m_buttonToRadial; // Qt::MouseButton (int) -> def index
|
||||||
bool m_gestureActive = false; // an RMB drag gesture is in progress
|
bool m_gestureActive = false; // an RMB drag gesture is in progress
|
||||||
|
bool m_leftDownInView = false; // LMB held inside the view (Acme chord anchor)
|
||||||
QHash<QString, QPointer<QAction>> m_actionsById;
|
QHash<QString, QPointer<QAction>> m_actionsById;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue