Sloppy focus KTextEditor plugin for Kate split panes
Focus follows mouse across Kate split views. Configurable focus-stealing prevention parameters: - Focus delay (default 150ms) - Minimum stay time (default 50ms) - Ignore modal dialogs - Ignore floating widgets - Optional raise on focus Uses qApp event filter to catch Enter events on all widgets, walks up to find KTextEditor::View, and calls setFocus() to activate the view in its current split pane without opening new tabs.
This commit is contained in:
commit
a641cbfc00
|
|
@ -0,0 +1 @@
|
||||||
|
build/
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(sloppyfocus VERSION 0.1.0 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
|
||||||
|
find_package(ECM 5.80 REQUIRED NO_MODULE)
|
||||||
|
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
|
||||||
|
|
||||||
|
# --- Detect Qt6/KF6 or fall back to Qt5/KF5 ---
|
||||||
|
find_package(KF6TextEditor QUIET CONFIG)
|
||||||
|
if(KF6TextEditor_FOUND)
|
||||||
|
set(QT_MAJOR_VERSION 6)
|
||||||
|
else()
|
||||||
|
set(QT_MAJOR_VERSION 5)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include(KDEInstallDirs)
|
||||||
|
include(KDECMakeSettings)
|
||||||
|
include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||||
|
|
||||||
|
if(QT_MAJOR_VERSION EQUAL 6)
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS Core Widgets)
|
||||||
|
find_package(KF6I18n REQUIRED)
|
||||||
|
find_package(KF6CoreAddons REQUIRED)
|
||||||
|
set(KF_LINK_LIBS
|
||||||
|
KF6::TextEditor
|
||||||
|
KF6::I18n
|
||||||
|
Qt6::Core
|
||||||
|
Qt6::Widgets
|
||||||
|
)
|
||||||
|
set(PLUGIN_INSTALL_NS "kf6/ktexteditor")
|
||||||
|
message(STATUS "Building Sloppy Focus with KF6/Qt6")
|
||||||
|
else()
|
||||||
|
find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
|
||||||
|
find_package(KF5TextEditor REQUIRED)
|
||||||
|
find_package(KF5I18n REQUIRED)
|
||||||
|
find_package(KF5CoreAddons REQUIRED)
|
||||||
|
set(KF_LINK_LIBS
|
||||||
|
KF5::TextEditor
|
||||||
|
KF5::I18n
|
||||||
|
Qt5::Core
|
||||||
|
Qt5::Widgets
|
||||||
|
)
|
||||||
|
set(PLUGIN_INSTALL_NS "ktexteditor")
|
||||||
|
message(STATUS "Building Sloppy Focus with KF5/Qt5")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
kcoreaddons_add_plugin(sloppyfocus
|
||||||
|
SOURCES
|
||||||
|
sloppyfocus.cpp
|
||||||
|
INSTALL_NAMESPACE "${PLUGIN_INSTALL_NS}"
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(sloppyfocus ${KF_LINK_LIBS})
|
||||||
|
target_include_directories(sloppyfocus PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
.PHONY: all clean install
|
||||||
|
|
||||||
|
BUILD_DIR := build
|
||||||
|
|
||||||
|
all: $(BUILD_DIR)/Makefile
|
||||||
|
@cd $(BUILD_DIR) && make -j$$(nproc)
|
||||||
|
|
||||||
|
$(BUILD_DIR)/Makefile: CMakeLists.txt
|
||||||
|
@mkdir -p $(BUILD_DIR)
|
||||||
|
@cd $(BUILD_DIR) && cmake .. -DCMAKE_INSTALL_PREFIX=/usr -Wno-dev
|
||||||
|
|
||||||
|
install: all
|
||||||
|
cd $(BUILD_DIR) && sudo make install
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(BUILD_DIR)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"KPlugin": {
|
||||||
|
"Id": "sloppyfocus",
|
||||||
|
"Name": "Sloppy Focus",
|
||||||
|
"Description": "Focus follows mouse for Kate editor views with configurable focus-stealing prevention",
|
||||||
|
"Icon": "input-mouse",
|
||||||
|
"Authors": [
|
||||||
|
{
|
||||||
|
"Name": "lkn"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"X-KDE-PluginInfo-EnabledByDefault": true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,366 @@
|
||||||
|
#include "sloppyfocus.h"
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
#include <KPluginFactory>
|
||||||
|
#include <KSharedConfig>
|
||||||
|
#include <KConfigGroup>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QGroupBox>
|
||||||
|
#include <QSpinBox>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QIcon>
|
||||||
|
|
||||||
|
K_PLUGIN_FACTORY_WITH_JSON(SloppyFocusPluginFactory,
|
||||||
|
"metadata.json",
|
||||||
|
registerPlugin<SloppyFocusPlugin>();)
|
||||||
|
|
||||||
|
static const char *CONFIG_GROUP = "SloppyFocus";
|
||||||
|
static const char *KEY_FOCUS_DELAY = "FocusDelay";
|
||||||
|
static const char *KEY_MIN_STAY = "MinimumStayTime";
|
||||||
|
static const char *KEY_RAISE = "RaiseOnFocus";
|
||||||
|
static const char *KEY_IGNORE_DIALOGS = "IgnoreDialogs";
|
||||||
|
static const char *KEY_IGNORE_FLOATING = "IgnoreFloatingWidgets";
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SloppyFocusPlugin
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
SloppyFocusPlugin::SloppyFocusPlugin(QObject *parent, const QVariantList &args)
|
||||||
|
: KTextEditor::Plugin(parent)
|
||||||
|
{
|
||||||
|
Q_UNUSED(args)
|
||||||
|
readConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
SloppyFocusPlugin::~SloppyFocusPlugin() = default;
|
||||||
|
|
||||||
|
QObject *SloppyFocusPlugin::createView(KTextEditor::MainWindow *mainWindow)
|
||||||
|
{
|
||||||
|
return new SloppyFocusView(this, mainWindow);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SloppyFocusPlugin::configPages() const
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
KTextEditor::ConfigPage *SloppyFocusPlugin::configPage(int number, QWidget *parent)
|
||||||
|
{
|
||||||
|
if (number == 0) {
|
||||||
|
return new SloppyFocusConfigPage(this, parent);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusPlugin::setFocusDelay(int ms)
|
||||||
|
{
|
||||||
|
m_focusDelay = qBound(0, ms, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusPlugin::setMinimumStayTime(int ms)
|
||||||
|
{
|
||||||
|
m_minimumStayTime = qBound(0, ms, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusPlugin::setRaiseOnFocus(bool raise)
|
||||||
|
{
|
||||||
|
m_raiseOnFocus = raise;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusPlugin::setIgnoreDialogs(bool ignore)
|
||||||
|
{
|
||||||
|
m_ignoreDialogs = ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusPlugin::setIgnoreFloatingWidgets(bool ignore)
|
||||||
|
{
|
||||||
|
m_ignoreFloatingWidgets = ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusPlugin::readConfig()
|
||||||
|
{
|
||||||
|
KConfigGroup cg(KSharedConfig::openConfig(), CONFIG_GROUP);
|
||||||
|
m_focusDelay = cg.readEntry(KEY_FOCUS_DELAY, 150);
|
||||||
|
m_minimumStayTime = cg.readEntry(KEY_MIN_STAY, 50);
|
||||||
|
m_raiseOnFocus = cg.readEntry(KEY_RAISE, false);
|
||||||
|
m_ignoreDialogs = cg.readEntry(KEY_IGNORE_DIALOGS, true);
|
||||||
|
m_ignoreFloatingWidgets = cg.readEntry(KEY_IGNORE_FLOATING, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusPlugin::writeConfig()
|
||||||
|
{
|
||||||
|
KConfigGroup cg(KSharedConfig::openConfig(), CONFIG_GROUP);
|
||||||
|
cg.writeEntry(KEY_FOCUS_DELAY, m_focusDelay);
|
||||||
|
cg.writeEntry(KEY_MIN_STAY, m_minimumStayTime);
|
||||||
|
cg.writeEntry(KEY_RAISE, m_raiseOnFocus);
|
||||||
|
cg.writeEntry(KEY_IGNORE_DIALOGS, m_ignoreDialogs);
|
||||||
|
cg.writeEntry(KEY_IGNORE_FLOATING, m_ignoreFloatingWidgets);
|
||||||
|
cg.sync();
|
||||||
|
Q_EMIT configChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SloppyFocusView — event filter and focus switching logic
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
SloppyFocusView::SloppyFocusView(SloppyFocusPlugin *plugin, KTextEditor::MainWindow *mainWindow)
|
||||||
|
: QObject(mainWindow->window())
|
||||||
|
, m_plugin(plugin)
|
||||||
|
, m_mainWindow(mainWindow)
|
||||||
|
{
|
||||||
|
m_focusTimer.setSingleShot(true);
|
||||||
|
connect(&m_focusTimer, &QTimer::timeout, this, &SloppyFocusView::activatePendingView);
|
||||||
|
connect(m_plugin, &SloppyFocusPlugin::configChanged, this, &SloppyFocusView::onConfigChanged);
|
||||||
|
|
||||||
|
m_lastFocusChange.start();
|
||||||
|
|
||||||
|
// Install event filter on QApplication to see Enter events for ALL widgets.
|
||||||
|
// A filter on just the main window won't propagate to children.
|
||||||
|
qApp->installEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
SloppyFocusView::~SloppyFocusView()
|
||||||
|
{
|
||||||
|
m_focusTimer.stop();
|
||||||
|
qApp->removeEventFilter(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusView::onConfigChanged()
|
||||||
|
{
|
||||||
|
// Timer interval is updated on next trigger; nothing else needed
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SloppyFocusView::eventFilter(QObject *obj, QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() != QEvent::Enter) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *widget = qobject_cast<QWidget *>(obj);
|
||||||
|
if (!widget) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only handle widgets belonging to our main window
|
||||||
|
if (widget->window() != m_mainWindow->window()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the KTextEditor::View ancestor of the entered widget
|
||||||
|
KTextEditor::View *view = viewForWidget(widget);
|
||||||
|
if (!view) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't re-trigger for the view that already has focus
|
||||||
|
if (view == m_currentView) {
|
||||||
|
m_focusTimer.stop();
|
||||||
|
m_pendingView = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus-stealing prevention: check minimum stay time
|
||||||
|
if (m_plugin->minimumStayTime() > 0 &&
|
||||||
|
m_lastFocusChange.elapsed() < m_plugin->minimumStayTime()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we should ignore due to active dialog
|
||||||
|
if (shouldIgnoreFocus()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule focus change after delay
|
||||||
|
m_pendingView = view;
|
||||||
|
m_focusTimer.start(m_plugin->focusDelay());
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusView::activatePendingView()
|
||||||
|
{
|
||||||
|
if (!m_pendingView) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (!view->isVisible()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify mouse is still within the view's geometry
|
||||||
|
QPoint globalPos = QCursor::pos();
|
||||||
|
QRect viewRect = view->rect();
|
||||||
|
QPoint viewTopLeft = view->mapToGlobal(viewRect.topLeft());
|
||||||
|
QRect globalRect(viewTopLeft, viewRect.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;
|
||||||
|
m_lastFocusChange.restart();
|
||||||
|
|
||||||
|
if (m_plugin->raiseOnFocus()) {
|
||||||
|
view->raise();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KTextEditor::View *SloppyFocusView::viewForWidget(QWidget *widget) const
|
||||||
|
{
|
||||||
|
// Walk up the widget hierarchy looking for a KTextEditor::View
|
||||||
|
QWidget *w = widget;
|
||||||
|
while (w) {
|
||||||
|
if (auto *view = qobject_cast<KTextEditor::View *>(w)) {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
w = w->parentWidget();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SloppyFocusView::shouldIgnoreFocus() const
|
||||||
|
{
|
||||||
|
// Ignore if a modal dialog is active
|
||||||
|
if (m_plugin->ignoreDialogs()) {
|
||||||
|
if (QApplication::activeModalWidget()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore if a floating/popup widget has focus
|
||||||
|
if (m_plugin->ignoreFloatingWidgets()) {
|
||||||
|
QWidget *focused = QApplication::focusWidget();
|
||||||
|
if (focused && focused->window() != m_mainWindow->window()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// SloppyFocusConfigPage
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
SloppyFocusConfigPage::SloppyFocusConfigPage(SloppyFocusPlugin *plugin, QWidget *parent)
|
||||||
|
: KTextEditor::ConfigPage(parent)
|
||||||
|
, m_plugin(plugin)
|
||||||
|
{
|
||||||
|
auto *layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
// Focus timing group
|
||||||
|
auto *timingGroup = new QGroupBox(i18n("Focus Timing"), this);
|
||||||
|
auto *timingLayout = new QFormLayout(timingGroup);
|
||||||
|
|
||||||
|
m_focusDelaySpin = new QSpinBox(this);
|
||||||
|
m_focusDelaySpin->setRange(0, 5000);
|
||||||
|
m_focusDelaySpin->setSuffix(i18n(" ms"));
|
||||||
|
m_focusDelaySpin->setToolTip(i18n("Time the mouse must hover over a view before focus switches. "
|
||||||
|
"Set to 0 for immediate focus change."));
|
||||||
|
timingLayout->addRow(i18n("Focus delay:"), m_focusDelaySpin);
|
||||||
|
|
||||||
|
m_minimumStaySpin = new QSpinBox(this);
|
||||||
|
m_minimumStaySpin->setRange(0, 5000);
|
||||||
|
m_minimumStaySpin->setSuffix(i18n(" ms"));
|
||||||
|
m_minimumStaySpin->setToolTip(i18n("Minimum time focus must remain on a view before another "
|
||||||
|
"switch is allowed. Prevents rapid bouncing."));
|
||||||
|
timingLayout->addRow(i18n("Minimum stay time:"), m_minimumStaySpin);
|
||||||
|
|
||||||
|
layout->addWidget(timingGroup);
|
||||||
|
|
||||||
|
// Behavior group
|
||||||
|
auto *behaviorGroup = new QGroupBox(i18n("Focus-Stealing Prevention"), this);
|
||||||
|
auto *behaviorLayout = new QVBoxLayout(behaviorGroup);
|
||||||
|
|
||||||
|
m_raiseOnFocusCheck = new QCheckBox(i18n("Raise view on focus"), this);
|
||||||
|
m_raiseOnFocusCheck->setToolTip(i18n("Raise the view widget when it receives sloppy focus"));
|
||||||
|
behaviorLayout->addWidget(m_raiseOnFocusCheck);
|
||||||
|
|
||||||
|
m_ignoreDialogsCheck = new QCheckBox(i18n("Ignore when dialogs are open"), this);
|
||||||
|
m_ignoreDialogsCheck->setToolTip(i18n("Disable sloppy focus while a modal dialog is active"));
|
||||||
|
behaviorLayout->addWidget(m_ignoreDialogsCheck);
|
||||||
|
|
||||||
|
m_ignoreFloatingCheck = new QCheckBox(i18n("Ignore when floating widgets have focus"), this);
|
||||||
|
m_ignoreFloatingCheck->setToolTip(i18n("Disable sloppy focus when focus is in a different "
|
||||||
|
"window (e.g., completion popups, tooltips)"));
|
||||||
|
behaviorLayout->addWidget(m_ignoreFloatingCheck);
|
||||||
|
|
||||||
|
layout->addWidget(behaviorGroup);
|
||||||
|
layout->addStretch();
|
||||||
|
|
||||||
|
// Connect change signals
|
||||||
|
connect(m_focusDelaySpin, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||||
|
this, &SloppyFocusConfigPage::changed);
|
||||||
|
connect(m_minimumStaySpin, QOverload<int>::of(&QSpinBox::valueChanged),
|
||||||
|
this, &SloppyFocusConfigPage::changed);
|
||||||
|
connect(m_raiseOnFocusCheck, &QCheckBox::toggled,
|
||||||
|
this, &SloppyFocusConfigPage::changed);
|
||||||
|
connect(m_ignoreDialogsCheck, &QCheckBox::toggled,
|
||||||
|
this, &SloppyFocusConfigPage::changed);
|
||||||
|
connect(m_ignoreFloatingCheck, &QCheckBox::toggled,
|
||||||
|
this, &SloppyFocusConfigPage::changed);
|
||||||
|
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
SloppyFocusConfigPage::~SloppyFocusConfigPage() = default;
|
||||||
|
|
||||||
|
QString SloppyFocusConfigPage::name() const
|
||||||
|
{
|
||||||
|
return i18n("Sloppy Focus");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SloppyFocusConfigPage::fullName() const
|
||||||
|
{
|
||||||
|
return i18n("Sloppy Focus (Focus Follows Mouse)");
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon SloppyFocusConfigPage::icon() const
|
||||||
|
{
|
||||||
|
return QIcon::fromTheme(QStringLiteral("input-mouse"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusConfigPage::apply()
|
||||||
|
{
|
||||||
|
m_plugin->setFocusDelay(m_focusDelaySpin->value());
|
||||||
|
m_plugin->setMinimumStayTime(m_minimumStaySpin->value());
|
||||||
|
m_plugin->setRaiseOnFocus(m_raiseOnFocusCheck->isChecked());
|
||||||
|
m_plugin->setIgnoreDialogs(m_ignoreDialogsCheck->isChecked());
|
||||||
|
m_plugin->setIgnoreFloatingWidgets(m_ignoreFloatingCheck->isChecked());
|
||||||
|
m_plugin->writeConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusConfigPage::defaults()
|
||||||
|
{
|
||||||
|
m_focusDelaySpin->setValue(150);
|
||||||
|
m_minimumStaySpin->setValue(50);
|
||||||
|
m_raiseOnFocusCheck->setChecked(false);
|
||||||
|
m_ignoreDialogsCheck->setChecked(true);
|
||||||
|
m_ignoreFloatingCheck->setChecked(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SloppyFocusConfigPage::reset()
|
||||||
|
{
|
||||||
|
m_focusDelaySpin->setValue(m_plugin->focusDelay());
|
||||||
|
m_minimumStaySpin->setValue(m_plugin->minimumStayTime());
|
||||||
|
m_raiseOnFocusCheck->setChecked(m_plugin->raiseOnFocus());
|
||||||
|
m_ignoreDialogsCheck->setChecked(m_plugin->ignoreDialogs());
|
||||||
|
m_ignoreFloatingCheck->setChecked(m_plugin->ignoreFloatingWidgets());
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "sloppyfocus.moc"
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
#ifndef SLOPPYFOCUS_H
|
||||||
|
#define SLOPPYFOCUS_H
|
||||||
|
|
||||||
|
#include <KTextEditor/Plugin>
|
||||||
|
#include <KTextEditor/MainWindow>
|
||||||
|
#include <KTextEditor/ConfigPage>
|
||||||
|
#include <KTextEditor/View>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QElapsedTimer>
|
||||||
|
|
||||||
|
class SloppyFocusPlugin : public KTextEditor::Plugin
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SloppyFocusPlugin(QObject *parent, const QVariantList &args);
|
||||||
|
~SloppyFocusPlugin() override;
|
||||||
|
|
||||||
|
QObject *createView(KTextEditor::MainWindow *mainWindow) override;
|
||||||
|
int configPages() const override;
|
||||||
|
KTextEditor::ConfigPage *configPage(int number, QWidget *parent) override;
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
int focusDelay() const { return m_focusDelay; }
|
||||||
|
int minimumStayTime() const { return m_minimumStayTime; }
|
||||||
|
bool raiseOnFocus() const { return m_raiseOnFocus; }
|
||||||
|
bool ignoreDialogs() const { return m_ignoreDialogs; }
|
||||||
|
bool ignoreFloatingWidgets() const { return m_ignoreFloatingWidgets; }
|
||||||
|
|
||||||
|
void setFocusDelay(int ms);
|
||||||
|
void setMinimumStayTime(int ms);
|
||||||
|
void setRaiseOnFocus(bool raise);
|
||||||
|
void setIgnoreDialogs(bool ignore);
|
||||||
|
void setIgnoreFloatingWidgets(bool ignore);
|
||||||
|
|
||||||
|
void readConfig();
|
||||||
|
void writeConfig();
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void configChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_focusDelay = 150;
|
||||||
|
int m_minimumStayTime = 50;
|
||||||
|
bool m_raiseOnFocus = false;
|
||||||
|
bool m_ignoreDialogs = true;
|
||||||
|
bool m_ignoreFloatingWidgets = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SloppyFocusView : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SloppyFocusView(SloppyFocusPlugin *plugin, KTextEditor::MainWindow *mainWindow);
|
||||||
|
~SloppyFocusView() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *obj, QEvent *event) override;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void activatePendingView();
|
||||||
|
void onConfigChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
KTextEditor::View *viewForWidget(QWidget *widget) const;
|
||||||
|
bool shouldIgnoreFocus() const;
|
||||||
|
|
||||||
|
SloppyFocusPlugin *m_plugin;
|
||||||
|
KTextEditor::MainWindow *m_mainWindow;
|
||||||
|
QTimer m_focusTimer;
|
||||||
|
QElapsedTimer m_lastFocusChange;
|
||||||
|
QPointer<KTextEditor::View> m_pendingView;
|
||||||
|
QPointer<KTextEditor::View> m_currentView;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SloppyFocusConfigPage : public KTextEditor::ConfigPage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit SloppyFocusConfigPage(SloppyFocusPlugin *plugin, QWidget *parent);
|
||||||
|
~SloppyFocusConfigPage() override;
|
||||||
|
|
||||||
|
QString name() const override;
|
||||||
|
QString fullName() const override;
|
||||||
|
QIcon icon() const override;
|
||||||
|
|
||||||
|
void apply() override;
|
||||||
|
void defaults() override;
|
||||||
|
void reset() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SloppyFocusPlugin *m_plugin;
|
||||||
|
class QSpinBox *m_focusDelaySpin;
|
||||||
|
class QSpinBox *m_minimumStaySpin;
|
||||||
|
class QCheckBox *m_raiseOnFocusCheck;
|
||||||
|
class QCheckBox *m_ignoreDialogsCheck;
|
||||||
|
class QCheckBox *m_ignoreFloatingCheck;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SLOPPYFOCUS_H
|
||||||
Loading…
Reference in New Issue