diff --git a/docs/PLAN.md b/docs/PLAN.md index 8dab9fa..73e3888 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -135,6 +135,14 @@ Therefore the palette is replaced, not extended, and the replacement is built on and by mouse buttons/gestures; the config carries both a key `trigger` and a mouse `button` per radial. - Not yet done: free-form stroke gestures (only button-press gesture so far). +- Built-in radials: an **edit** radial (Alt+R / right button) of in-place + actions, and a **window** radial (**middle button**) for split management + (`view_split_vert|horiz`, `view_close_current_space|others`, + `view_split_toggle`, and a "Focus" sub-branch of `go_*_split_view`). A plain + middle press (no left button held) routes to this radial; the Acme chords are + all **left+something**, so a middle press that is part of a chord + (left held) still cuts and never opens the radial. The X11 middle-click + primary paste is suppressed on release either way. - User-file radial config — DONE. `RadialConfig::load()` reads `$XDG_CONFIG_HOME/olliepalette/radials.json` when present and non-empty, otherwise returns `builtinDefault()`; a present-but-broken file also falls diff --git a/docs/radials.example.json b/docs/radials.example.json index 797aecb..791e56b 100644 --- a/docs/radials.example.json +++ b/docs/radials.example.json @@ -25,6 +25,28 @@ ] } ] + }, + { + "name": "window", + "button": "MiddleButton", + "slices": [ + { "label": "Split Vertical", "action": "view_split_vert" }, + { "label": "Split Horizontal", "action": "view_split_horiz" }, + { "label": "Close Split", "action": "view_close_current_space" }, + { "label": "Close Others", "action": "view_close_others" }, + { "label": "Toggle Orientation", "action": "view_split_toggle" }, + { + "label": "Focus", + "slices": [ + { "label": "Left", "action": "go_left_split_view" }, + { "label": "Right", "action": "go_right_split_view" }, + { "label": "Up", "action": "go_upward_split_view" }, + { "label": "Down", "action": "go_downward_split_view" }, + { "label": "Next", "action": "go_next_split_view" }, + { "label": "Previous", "action": "go_prev_split_view" } + ] + } + ] } ] } diff --git a/src/plugin/ollieplugin.cpp b/src/plugin/ollieplugin.cpp index aab8219..2d6a3d8 100644 --- a/src/plugin/ollieplugin.cpp +++ b/src/plugin/ollieplugin.cpp @@ -982,6 +982,7 @@ bool OllieView::eventFilter(QObject *obj, QEvent *event) return true; case QEvent::MouseButtonRelease: m_gestureActive = false; + m_swallowMiddleRelease = false; // the gesture consumed this release if (m_radial) { m_radial->driveReleaseGlobal( static_cast(event)->globalPosition().toPoint()); @@ -1030,14 +1031,23 @@ bool OllieView::eventFilter(QObject *obj, QEvent *event) if (mev->button() == Qt::MiddleButton && inView) { if (m_leftDownInView) { + // left + middle = Cut the selection (Acme chord). Consume both + // press and release; never a radial. if (QAction *cut = findActionByName(QStringLiteral("edit_cut"))) { cut->trigger(); } + m_swallowMiddleRelease = true; + return true; } - // Consume press (and its release) to suppress middle-click primary - // paste entirely inside the editor. + // A plain middle press (no chord): always suppress the X11 primary + // paste that fires on release. If the middle button is bound to a + // radial, fall through to the radial dispatch below; otherwise + // consume the press here too. m_swallowMiddleRelease = true; - return true; + if (!m_buttonToRadial.contains(static_cast(Qt::MiddleButton))) { + return true; + } + // else: do not return — let the radial-trigger dispatch handle it. } if (mev->button() == Qt::LeftButton && inView) { m_leftDownInView = true; // anchor set; selection proceeds normally diff --git a/src/radial/radialconfig.cpp b/src/radial/radialconfig.cpp index b70898f..8140e40 100644 --- a/src/radial/radialconfig.cpp +++ b/src/radial/radialconfig.cpp @@ -171,7 +171,37 @@ QList RadialConfig::builtinDefault() more, }; - return {edit}; + // A second radial for window/split management, triggered by the middle + // mouse button. Split ops are inherently about window layout, not the caret, + // so a dedicated radial keeps them off the keyboard and out of the edit + // radial. MiddleButton is safe: every Acme chord is left+something, and a + // plain middle press (no left held) is routed here, not to a chord. Leaf ids + // are Kate's view-manager action objectNames. + RadialDef window; + window.name = QStringLiteral("window"); + window.mouseButton = QStringLiteral("MiddleButton"); + + RadialNode focus; + focus.label = QStringLiteral("Focus"); + focus.children = { + leaf(QStringLiteral("Left"), QStringLiteral("go_left_split_view")), + leaf(QStringLiteral("Right"), QStringLiteral("go_right_split_view")), + leaf(QStringLiteral("Up"), QStringLiteral("go_upward_split_view")), + leaf(QStringLiteral("Down"), QStringLiteral("go_downward_split_view")), + leaf(QStringLiteral("Next"), QStringLiteral("go_next_split_view")), + leaf(QStringLiteral("Previous"), QStringLiteral("go_prev_split_view")), + }; + + window.root.children = { + leaf(QStringLiteral("Split Vertical"), QStringLiteral("view_split_vert")), + leaf(QStringLiteral("Split Horizontal"), QStringLiteral("view_split_horiz")), + leaf(QStringLiteral("Close Split"), QStringLiteral("view_close_current_space")), + leaf(QStringLiteral("Close Others"), QStringLiteral("view_close_others")), + leaf(QStringLiteral("Toggle Orientation"), QStringLiteral("view_split_toggle")), + focus, + }; + + return {edit, window}; } } // namespace katecustom diff --git a/src/radial/test_radialconfig.cpp b/src/radial/test_radialconfig.cpp index e189935..f48d46c 100644 --- a/src/radial/test_radialconfig.cpp +++ b/src/radial/test_radialconfig.cpp @@ -129,6 +129,34 @@ private Q_SLOTS: QVERIFY(hasBranch); } + void builtinHasWindowRadialOnMiddleButton() + { + const QList defs = RadialConfig::builtinDefault(); + QCOMPARE(defs.size(), 2); + // Find the window radial (second def), triggered by the middle button. + const RadialDef *window = nullptr; + for (const RadialDef &d : defs) { + if (d.name == QStringLiteral("window")) { + window = &d; + } + } + QVERIFY(window); + QCOMPARE(window->mouseButton, QStringLiteral("MiddleButton")); + + // It must expose the core split ops by their Kate action objectNames. + QStringList ids; + for (const RadialNode &n : window->root.children) { + ids << n.actionId; + for (const RadialNode &c : n.children) { + ids << c.actionId; + } + } + QVERIFY(ids.contains(QStringLiteral("view_split_vert"))); + QVERIFY(ids.contains(QStringLiteral("view_split_horiz"))); + QVERIFY(ids.contains(QStringLiteral("view_close_current_space"))); + QVERIFY(ids.contains(QStringLiteral("go_left_split_view"))); + } + void builtinDefaultHasLauncherSlices() { // The "More" branch exposes the Go-to-File / Go-to-Symbol switchers and