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 | |
17 | void KCursor::setAutoHideCursor(QWidget *w, bool enable, bool customEventFilter) |
18 | { |
19 | KCursorPrivate::self()->setAutoHideCursor(w, enable, customEventFilter); |
20 | } |
21 | |
22 | void KCursor::autoHideEventFilter(QObject *o, QEvent *e) |
23 | { |
24 | KCursorPrivate::self()->eventFilter(o, e); |
25 | } |
26 | |
27 | void KCursor::setHideCursorDelay(int ms) |
28 | { |
29 | KCursorPrivate::self()->hideCursorDelay = ms; |
30 | } |
31 | |
32 | int KCursor::hideCursorDelay() |
33 | { |
34 | return KCursorPrivate::self()->hideCursorDelay; |
35 | } |
36 | |
37 | // ************************************************************************** |
38 | |
39 | KCursorPrivateAutoHideEventFilter::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 | |
49 | KCursorPrivateAutoHideEventFilter::~KCursorPrivateAutoHideEventFilter() |
50 | { |
51 | if (m_widget != nullptr) { |
52 | mouseWidget()->setMouseTracking(m_wasMouseTracking); |
53 | } |
54 | } |
55 | |
56 | void KCursorPrivateAutoHideEventFilter::resetWidget() |
57 | { |
58 | m_widget = nullptr; |
59 | } |
60 | |
61 | void 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 | |
81 | void 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) |
106 | QWidget *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 | |
119 | bool 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 | |
157 | KCursorPrivate *KCursorPrivate::s_self = nullptr; |
158 | |
159 | KCursorPrivate *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 | |
172 | KCursorPrivate::KCursorPrivate() |
173 | { |
174 | hideCursorDelay = 5000; // 5s default value |
175 | enabled = true; |
176 | } |
177 | |
178 | KCursorPrivate::~KCursorPrivate() |
179 | { |
180 | } |
181 | |
182 | void 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 | |
227 | bool 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 | |
243 | void KCursorPrivate::slotViewportDestroyed(QObject *o) |
244 | { |
245 | m_eventFilters.remove(key: o); |
246 | } |
247 | |
248 | void 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 | |