1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 1998 Kurt Granroth <granroth@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-only
6*/
7
8#include "kcursor.h"
9#include "kcursor_p.h"
10
11#include <QAbstractScrollArea>
12#include <QCursor>
13#include <QEvent>
14#include <QTimer>
15#include <QWidget>
16
17void KCursor::setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter)
18{
19 KCursorPrivate::self()->setAutoHideCursor(w, enable, customEventFilter);
20}
21
22void KCursor::autoHideEventFilter(QObject *o, QEvent *e)
23{
24 KCursorPrivate::self()->eventFilter(o, e);
25}
26
27void KCursor::setHideCursorDelay(int ms)
28{
29 KCursorPrivate::self()->hideCursorDelay = ms;
30}
31
32int KCursor::hideCursorDelay()
33{
34 return KCursorPrivate::self()->hideCursorDelay;
35}
36
37// **************************************************************************
38
39KCursorPrivateAutoHideEventFilter::KCursorPrivateAutoHideEventFilter(QWidget *widget)
40 : m_widget(widget)
41 , m_wasMouseTracking(m_widget->hasMouseTracking())
42 , m_isCursorHidden(false)
43 , m_isOwnCursor(false)
44{
45 mouseWidget()->setMouseTracking(true);
46 connect(sender: &m_autoHideTimer, signal: &QTimer::timeout, context: this, slot: &KCursorPrivateAutoHideEventFilter::hideCursor);
47}
48
49KCursorPrivateAutoHideEventFilter::~KCursorPrivateAutoHideEventFilter()
50{
51 if (m_widget != nullptr) {
52 mouseWidget()->setMouseTracking(m_wasMouseTracking);
53 }
54}
55
56void KCursorPrivateAutoHideEventFilter::resetWidget()
57{
58 m_widget = nullptr;
59}
60
61void KCursorPrivateAutoHideEventFilter::hideCursor()
62{
63 m_autoHideTimer.stop();
64
65 if (m_isCursorHidden) {
66 return;
67 }
68
69 m_isCursorHidden = true;
70
71 QWidget *w = mouseWidget();
72
73 m_isOwnCursor = w->testAttribute(attribute: Qt::WA_SetCursor);
74 if (m_isOwnCursor) {
75 m_oldCursor = w->cursor();
76 }
77
78 w->setCursor(QCursor(Qt::BlankCursor));
79}
80
81void KCursorPrivateAutoHideEventFilter::unhideCursor()
82{
83 m_autoHideTimer.stop();
84
85 if (!m_isCursorHidden) {
86 return;
87 }
88
89 m_isCursorHidden = false;
90
91 QWidget *w = mouseWidget();
92
93 if (w->cursor().shape() != Qt::BlankCursor) { // someone messed with the cursor already
94 return;
95 }
96
97 if (m_isOwnCursor) {
98 w->setCursor(m_oldCursor);
99 } else {
100 w->unsetCursor();
101 }
102}
103
104// The widget which gets mouse events, and that shows the cursor
105// (that is the viewport, for a QAbstractScrollArea)
106QWidget *KCursorPrivateAutoHideEventFilter::mouseWidget() const
107{
108 QWidget *w = m_widget;
109
110 // Is w a QAbstractScrollArea ? Call setCursor on the viewport in that case.
111 QAbstractScrollArea *sv = qobject_cast<QAbstractScrollArea *>(object: w);
112 if (sv) {
113 w = sv->viewport();
114 }
115
116 return w;
117}
118
119bool KCursorPrivateAutoHideEventFilter::eventFilter(QObject *o, QEvent *e)
120{
121 Q_UNUSED(o);
122 // o is m_widget or its viewport
123 // Q_ASSERT( o == m_widget );
124
125 switch (e->type()) {
126 case QEvent::Leave:
127 case QEvent::FocusOut:
128 case QEvent::WindowDeactivate:
129 unhideCursor();
130 break;
131 case QEvent::KeyPress:
132 case QEvent::ShortcutOverride:
133 hideCursor();
134 break;
135 case QEvent::Enter:
136 case QEvent::FocusIn:
137 case QEvent::MouseButtonPress:
138 case QEvent::MouseButtonRelease:
139 case QEvent::MouseButtonDblClick:
140 case QEvent::MouseMove:
141 case QEvent::Show:
142 case QEvent::Hide:
143 case QEvent::Wheel:
144 unhideCursor();
145 if (m_widget->hasFocus()) {
146 m_autoHideTimer.setSingleShot(true);
147 m_autoHideTimer.start(msec: KCursorPrivate::self()->hideCursorDelay);
148 }
149 break;
150 default:
151 break;
152 }
153
154 return false;
155}
156
157KCursorPrivate *KCursorPrivate::s_self = nullptr;
158
159KCursorPrivate *KCursorPrivate::self()
160{
161 if (!s_self) {
162 s_self = new KCursorPrivate;
163 }
164 // WABA: Don't delete KCursorPrivate, it serves no real purpose.
165 // Even worse it causes crashes because it seems to get deleted
166 // during ~QApplication and ~QApplication doesn't seem to like it
167 // when we delete a QCursor. No idea if that is a bug itself.
168
169 return s_self;
170}
171
172KCursorPrivate::KCursorPrivate()
173{
174 hideCursorDelay = 5000; // 5s default value
175 enabled = true;
176}
177
178KCursorPrivate::~KCursorPrivate()
179{
180}
181
182void KCursorPrivate::setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter)
183{
184 if (!w || !enabled) {
185 return;
186 }
187
188 QWidget *viewport = nullptr;
189 QAbstractScrollArea *sv = qobject_cast<QAbstractScrollArea *>(object: w);
190 if (sv) {
191 viewport = sv->viewport();
192 }
193
194 if (enable) {
195 if (m_eventFilters.contains(key: w)) {
196 return;
197 }
198 KCursorPrivateAutoHideEventFilter *filter = new KCursorPrivateAutoHideEventFilter(w);
199 m_eventFilters.insert(key: w, value: filter);
200 if (viewport) {
201 m_eventFilters.insert(key: viewport, value: filter);
202 connect(sender: viewport, signal: &QObject::destroyed, context: this, slot: &KCursorPrivate::slotViewportDestroyed);
203 }
204 if (!customEventFilter) {
205 w->installEventFilter(filterObj: filter); // for key events
206 if (viewport) {
207 viewport->installEventFilter(filterObj: filter); // for mouse events
208 }
209 }
210 connect(sender: w, signal: &QObject::destroyed, context: this, slot: &KCursorPrivate::slotWidgetDestroyed);
211 } else {
212 KCursorPrivateAutoHideEventFilter *filter = m_eventFilters.take(key: w);
213 if (filter == nullptr) {
214 return;
215 }
216 w->removeEventFilter(obj: filter);
217 if (viewport) {
218 m_eventFilters.remove(key: viewport);
219 disconnect(sender: viewport, signal: &QObject::destroyed, receiver: this, slot: &KCursorPrivate::slotViewportDestroyed);
220 viewport->removeEventFilter(obj: filter);
221 }
222 delete filter;
223 disconnect(sender: w, signal: &QObject::destroyed, receiver: this, slot: &KCursorPrivate::slotWidgetDestroyed);
224 }
225}
226
227bool KCursorPrivate::eventFilter(QObject *o, QEvent *e)
228{
229 if (!enabled || e->type() == QEvent::ChildAdded) {
230 return false;
231 }
232
233 KCursorPrivateAutoHideEventFilter *filter = m_eventFilters.value(key: o);
234
235 Q_ASSERT(filter != nullptr);
236 if (filter == nullptr) {
237 return false;
238 }
239
240 return filter->eventFilter(o, e);
241}
242
243void KCursorPrivate::slotViewportDestroyed(QObject *o)
244{
245 m_eventFilters.remove(key: o);
246}
247
248void KCursorPrivate::slotWidgetDestroyed(QObject *o)
249{
250 KCursorPrivateAutoHideEventFilter *filter = m_eventFilters.take(key: o);
251
252 Q_ASSERT(filter != nullptr);
253
254 filter->resetWidget(); // so that dtor doesn't access it
255 delete filter;
256}
257
258#include "moc_kcursor_p.cpp"
259

source code of kwidgetsaddons/src/kcursor.cpp