From be18599c9bc08098e99861e1460e37a5a81115c4 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Mon, 20 Jul 2026 15:01:25 +0200 Subject: [PATCH] Extend sloppy focus to all panes (tool views, terminals, etc.) Generalize focus tracking from KTextEditor::View-only to any focusable widget pane. paneForWidget() walks up the hierarchy: - KTextEditor::View takes priority (editor splits) - Falls back to any widget with StrongFocus/WheelFocus policy (terminals, file browser, build output, plugin panels, etc.) This means hovering over any Kate pane will give it keyboard focus, not just editor splits. --- sloppyfocus.cpp | 73 ++++++++++++++++++++++++++++--------------------- sloppyfocus.h | 8 +++--- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/sloppyfocus.cpp b/sloppyfocus.cpp index 0a6b80c..a891830 100644 --- a/sloppyfocus.cpp +++ b/sloppyfocus.cpp @@ -116,7 +116,7 @@ SloppyFocusView::SloppyFocusView(SloppyFocusPlugin *plugin, KTextEditor::MainWin , m_mainWindow(mainWindow) { m_focusTimer.setSingleShot(true); - connect(&m_focusTimer, &QTimer::timeout, this, &SloppyFocusView::activatePendingView); + connect(&m_focusTimer, &QTimer::timeout, this, &SloppyFocusView::activatePendingPane); connect(m_plugin, &SloppyFocusPlugin::configChanged, this, &SloppyFocusView::onConfigChanged); m_lastFocusChange.start(); @@ -153,16 +153,16 @@ bool SloppyFocusView::eventFilter(QObject *obj, QEvent *event) return false; } - // Find the KTextEditor::View ancestor of the entered widget - KTextEditor::View *view = viewForWidget(widget); - if (!view) { + // Find the focusable pane (editor view, tool view, terminal, etc.) + QWidget *pane = paneForWidget(widget); + if (!pane) { return false; } - // Don't re-trigger for the view that already has focus - if (view == m_currentView) { + // Don't re-trigger for the pane that already has focus + if (pane == m_currentPane) { m_focusTimer.stop(); - m_pendingView = nullptr; + m_pendingPane = nullptr; return false; } @@ -178,59 +178,70 @@ bool SloppyFocusView::eventFilter(QObject *obj, QEvent *event) } // Schedule focus change after delay - m_pendingView = view; + m_pendingPane = pane; m_focusTimer.start(m_plugin->focusDelay()); return false; } -void SloppyFocusView::activatePendingView() +void SloppyFocusView::activatePendingPane() { - if (!m_pendingView) { + if (!m_pendingPane) { return; } - // Final check: is the mouse still over this view? - // (The view might have been destroyed or mouse moved away) - KTextEditor::View *view = m_pendingView; - m_pendingView = nullptr; + QWidget *pane = m_pendingPane; + m_pendingPane = nullptr; - if (!view->isVisible()) { + if (!pane->isVisible()) { return; } - // Verify mouse is still within the view's geometry + // Verify mouse is still within the pane's geometry QPoint globalPos = QCursor::pos(); - QRect viewRect = view->rect(); - QPoint viewTopLeft = view->mapToGlobal(viewRect.topLeft()); - QRect globalRect(viewTopLeft, viewRect.size()); + QPoint paneTopLeft = pane->mapToGlobal(QPoint(0, 0)); + QRect globalRect(paneTopLeft, pane->size()); if (!globalRect.contains(globalPos)) { return; } - // Perform the focus switch — just move keyboard focus to the view - // in its current split pane. Do NOT use activateView(document) as that - // can open the document in a different view space. - view->setFocus(); - m_currentView = view; + // Move keyboard focus to the pane + pane->setFocus(); + m_currentPane = pane; m_lastFocusChange.restart(); if (m_plugin->raiseOnFocus()) { - view->raise(); + pane->raise(); } } -KTextEditor::View *SloppyFocusView::viewForWidget(QWidget *widget) const +QWidget *SloppyFocusView::paneForWidget(QWidget *widget) const { - // Walk up the widget hierarchy looking for a KTextEditor::View + // Walk up the widget hierarchy to find the nearest focusable pane. + // Priority: KTextEditor::View first, then any widget with a keyboard + // focus policy (tool views, terminals, file browsers, etc.). QWidget *w = widget; - while (w) { - if (auto *view = qobject_cast(w)) { - return view; + QWidget *focusCandidate = nullptr; + + while (w && w != m_mainWindow->window()) { + // KTextEditor::View is always a valid pane + if (qobject_cast(w)) { + return w; } + + // Track the deepest widget that accepts keyboard focus as a + // fallback. This catches tool views, terminals, tree views, etc. + if (!focusCandidate) { + auto policy = w->focusPolicy(); + if (policy == Qt::StrongFocus || policy == Qt::WheelFocus) { + focusCandidate = w; + } + } + w = w->parentWidget(); } - return nullptr; + + return focusCandidate; } bool SloppyFocusView::shouldIgnoreFocus() const diff --git a/sloppyfocus.h b/sloppyfocus.h index b0f1718..d7edc95 100644 --- a/sloppyfocus.h +++ b/sloppyfocus.h @@ -62,19 +62,19 @@ protected: bool eventFilter(QObject *obj, QEvent *event) override; private Q_SLOTS: - void activatePendingView(); + void activatePendingPane(); void onConfigChanged(); private: - KTextEditor::View *viewForWidget(QWidget *widget) const; + QWidget *paneForWidget(QWidget *widget) const; bool shouldIgnoreFocus() const; SloppyFocusPlugin *m_plugin; KTextEditor::MainWindow *m_mainWindow; QTimer m_focusTimer; QElapsedTimer m_lastFocusChange; - QPointer m_pendingView; - QPointer m_currentView; + QPointer m_pendingPane; + QPointer m_currentPane; }; class SloppyFocusConfigPage : public KTextEditor::ConfigPage