1 /* This file is part of the KDE libraries
2 Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com)
3
4 This library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public
6 License as published by the Free Software Foundation; either
7 version 2 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public License
15 along with this library; see the file COPYING.LIB. If not, write to
16 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301, USA.
18 */
19
20 #include "kgesturemap_p.h"
21
22 #include <qapplication.h>
23 #include <QAction>
24 #include <QActionEvent>
25 #include <QDebug>
26
27 /*
28 This is a class for internal use by the KDE libraries only. This class
29 may change or go away without notice so don't try to use it in non-kdelibs
30 code.
31 */
32
33 class KGestureMapContainer
34 {
35 public:
36 KGestureMap gestureMap;
37 };
38
Q_GLOBAL_STATIC(KGestureMapContainer,g_instance)39 Q_GLOBAL_STATIC(KGestureMapContainer, g_instance)
40
41 KGestureMap::~KGestureMap()
42 {
43 }
44
self()45 KGestureMap *KGestureMap::self()
46 {
47 return &g_instance()->gestureMap;
48 }
49
KGestureMap()50 KGestureMap::KGestureMap()
51 {
52 m_gestureTimeout.setSingleShot(true);
53 connect(&m_gestureTimeout, SIGNAL(timeout()), this, SLOT(stopAcquisition()));
54 //It would be nice to install the filter on demand. Unfortunately,
55 //undesired behavior might result due to changing invocation
56 //orders of different event filters.
57 if (qApp) {
58 qApp->installEventFilter(this);
59 }
60 }
61
setShapeGesture(QAction * act,const KShapeGesture & gesture)62 void KGestureMap::setShapeGesture(QAction *act, const KShapeGesture &gesture)
63 {
64 if (!gesture.isValid() || !act) {
65 return;
66 }
67 qDebug() << "KGestureMap::addGesture(KShapeGesture ...)";
68 if (m_shapeGestures.contains(gesture)) {
69 qWarning() << "Replacing an action for a gesture already taken";
70 }
71 m_shapeGestures.insert(gesture, act);
72 }
73
setRockerGesture(QAction * act,const KRockerGesture & gesture)74 void KGestureMap::setRockerGesture(QAction *act, const KRockerGesture &gesture)
75 {
76 if (!gesture.isValid() || !act) {
77 return;
78 }
79 qDebug() << "KGestureMap::addGesture(KRockerGesture ...)";
80 if (m_rockerGestures.contains(gesture)) {
81 qWarning() << "Replacing an action for a gesture already taken";
82 }
83 m_rockerGestures.insert(gesture, act);
84 }
85
setDefaultShapeGesture(QAction * act,const KShapeGesture & gesture)86 void KGestureMap::setDefaultShapeGesture(QAction *act, const KShapeGesture &gesture)
87 {
88 if (!gesture.isValid() || !act) {
89 return;
90 }
91 qDebug() << "KGestureMap::addGesture(KShapeGesture ...)";
92 if (m_defaultShapeGestures.contains(gesture)) {
93 qWarning() << "Replacing an action for a gesture already taken";
94 }
95 m_defaultShapeGestures.insert(gesture, act);
96 }
97
setDefaultRockerGesture(QAction * act,const KRockerGesture & gesture)98 void KGestureMap::setDefaultRockerGesture(QAction *act, const KRockerGesture &gesture)
99 {
100 if (!gesture.isValid() || !act) {
101 return;
102 }
103 qDebug() << "KGestureMap::addGesture(KRockerGesture ...)";
104 if (m_defaultRockerGestures.contains(gesture)) {
105 qWarning() << "Replacing an action for a gesture already taken";
106 }
107 m_defaultRockerGestures.insert(gesture, act);
108 }
109
removeAllGestures(QAction * kact)110 void KGestureMap::removeAllGestures(QAction *kact)
111 {
112 KShapeGesture activeGesture;
113 ShapeGestureHash::iterator si = m_shapeGestures.begin();
114 ShapeGestureHash::iterator send = m_shapeGestures.end();
115 for (; si != send; ++si) {
116 if (si.value() == kact) {
117 m_shapeGestures.remove(si.key());
118 break;
119 }
120 }
121
122 si = m_defaultShapeGestures.begin();
123 send = m_defaultShapeGestures.end();
124 for (; si != send; ++si) {
125 if (si.value() == kact) {
126 m_defaultShapeGestures.remove(si.key());
127 break;
128 }
129 }
130
131 RockerGestureHash::iterator ri = m_rockerGestures.begin();
132 RockerGestureHash::iterator rend = m_rockerGestures.end();
133 for (; ri != rend; ++ri) {
134 if (ri.value() == kact) {
135 m_rockerGestures.remove(ri.key());
136 break;
137 }
138 }
139
140 ri = m_defaultRockerGestures.begin();
141 rend = m_defaultRockerGestures.end();
142 for (; ri != rend; ++ri) {
143 if (ri.value() == kact) {
144 m_defaultRockerGestures.remove(ri.key());
145 break;
146 }
147 }
148 }
149
findAction(const KShapeGesture & gesture) const150 QAction *KGestureMap::findAction(const KShapeGesture &gesture) const
151 {
152 return m_shapeGestures.value(gesture);
153 }
154
findAction(const KRockerGesture & gesture) const155 QAction *KGestureMap::findAction(const KRockerGesture &gesture) const
156 {
157 return m_rockerGestures.value(gesture);
158 }
159
installEventFilterOnMe(QApplication * app)160 void KGestureMap::installEventFilterOnMe(QApplication *app)
161 {
162 app->installEventFilter(this);
163 }
164
shapeGesture(const QAction * kact) const165 KShapeGesture KGestureMap::shapeGesture(const QAction *kact) const
166 {
167 KShapeGesture activeGesture;
168 ShapeGestureHash::const_iterator it = m_shapeGestures.constBegin();
169 ShapeGestureHash::const_iterator end = m_shapeGestures.constEnd();
170 for (; it != end; ++it) {
171 if (it.value() == kact) {
172 activeGesture = it.key();
173 break;
174 }
175 }
176 return activeGesture;
177 }
178
defaultShapeGesture(const QAction * kact) const179 KShapeGesture KGestureMap::defaultShapeGesture(const QAction *kact) const
180 {
181 KShapeGesture defaultGesture;
182 ShapeGestureHash::const_iterator it = m_defaultShapeGestures.constBegin();
183 ShapeGestureHash::const_iterator end = m_defaultShapeGestures.constEnd();
184 for (; it != end; ++it) {
185 if (it.value() == kact) {
186 defaultGesture = it.key();
187 break;
188 }
189 }
190 return defaultGesture;
191 }
192
rockerGesture(const QAction * kact) const193 KRockerGesture KGestureMap::rockerGesture(const QAction *kact) const
194 {
195 KRockerGesture activeGesture;
196 RockerGestureHash::const_iterator it = m_rockerGestures.constBegin();
197 RockerGestureHash::const_iterator end = m_rockerGestures.constEnd();
198 for (; it != end; ++it) {
199 if (it.value() == kact) {
200 activeGesture = it.key();
201 break;
202 }
203 }
204 return activeGesture;
205 }
206
defaultRockerGesture(const QAction * kact) const207 KRockerGesture KGestureMap::defaultRockerGesture(const QAction *kact) const
208 {
209 KRockerGesture defaultGesture;
210 RockerGestureHash::const_iterator it = m_defaultRockerGestures.constBegin();
211 RockerGestureHash::const_iterator end = m_defaultRockerGestures.constEnd();
212 for (; it != end; ++it) {
213 if (it.value() == kact) {
214 defaultGesture = it.key();
215 break;
216 }
217 }
218 return defaultGesture;
219 }
220
bitCount(int n)221 inline int KGestureMap::bitCount(int n)
222 {
223 int count = 0;
224 while (n) {
225 n &= (n - 1);
226 count++;
227 }
228 return count;
229 }
230
handleAction(QAction * kact)231 void KGestureMap::handleAction(QAction *kact)
232 {
233 if (!kact) {
234 return;
235 }
236 qDebug() << "handleAction";
237 //TODO: only activate in the action's context, just like keyboard shortcuts
238 kact->trigger();
239 return;
240 }
241
matchShapeGesture()242 void KGestureMap::matchShapeGesture()
243 {
244 //TODO: tune and tweak until satisfied with result :)
245 m_shapeGesture.setShape(m_points);
246 float dist, minDist = 20.0;
247 QAction *bestMatch = 0;
248
249 for (QHash<KShapeGesture, QAction *>::const_iterator it = m_shapeGestures.constBegin();
250 it != m_shapeGestures.constEnd(); ++it) {
251 dist = m_shapeGesture.distance(it.key(), 1000.0);
252 if (dist < minDist) {
253 minDist = dist;
254 bestMatch = it.value();
255 }
256 }
257 handleAction(bestMatch);
258 }
259
260 //slot
stopAcquisition()261 void KGestureMap::stopAcquisition()
262 {
263 m_gestureTimeout.stop();
264 m_acquiring = false;
265 }
266
267 //TODO: Probably kwin, kded and others should not have a gesture map.
268 //Maybe making them friends and providing a private "die()" function would work.
269 /*
270 * Act on rocker gestures immediately and collect movement data for evaluation.
271 * The decision when to consume and when to relay an event is quite tricky.
272 * I decided to only consume clicks that belong to completed rocker gestures.
273 * A user might e.g. go back in a browser several times using rocker gestures,
274 * thus changing what's under the cursor every time. This might lead to
275 * unintended clicks on links where there was free space before.
276 */
277
eventFilter(QObject * obj,QEvent * e)278 bool KGestureMap::eventFilter(QObject *obj, QEvent *e)
279 {
280 //disable until it does not interfere with other input any more
281 return false;
282 Q_UNUSED(obj);
283 int type = e->type();
284
285 //catch right-clicks disguised as context menu events. if we ignore a
286 //context menu event caused by a right-click, it should get resent
287 //as a right-click event, according to documentation.
288 //### this is preliminary
289 if (type == QEvent::ContextMenu) {
290 QContextMenuEvent *cme = static_cast<QContextMenuEvent *>(e);
291 if (cme->reason() == QContextMenuEvent::Mouse) {
292 cme->ignore();
293 return true;
294 }
295 return false;
296 }
297
298 if (type < QEvent::MouseButtonPress || type > QEvent::MouseMove) {
299 return false;
300 }
301
302 QMouseEvent *me = static_cast<QMouseEvent *>(e);
303 if (type == QEvent::MouseButtonPress) {
304 int nButtonsDown = bitCount(me->buttons());
305 qDebug() << "number of buttons down:" << nButtonsDown;
306
307 //right button down starts gesture acquisition
308 if (nButtonsDown == 1 && me->button() == Qt::RightButton) {
309 //"startAcquisition()"
310 m_acquiring = true;
311 m_gestureTimeout.start(4000);
312 qDebug() << "========================";
313 m_points.clear();
314 m_points.append(me->pos());
315 return true;
316 } else if (nButtonsDown != 2) {
317 return false;
318 }
319
320 //rocker gestures. do not trigger any movement gestures from now on.
321 stopAcquisition();
322 int buttonHeld = me->buttons() ^ me->button();
323 m_rockerGesture.setButtons(static_cast<Qt::MouseButton>(buttonHeld), me->button());
324 QAction *match = m_rockerGestures.value(m_rockerGesture);
325 if (!match) {
326 return false;
327 }
328 handleAction(match);
329 return true;
330 }
331
332 if (m_acquiring) {
333 if (type == QEvent::MouseMove) {
334 m_points.append(me->pos());
335 //abort to avoid using too much memory. 1010 points should be enough
336 //for everyone! :)
337 //next reallocation of m_points would happen at 1012 items
338 if (m_points.size() > 1010) {
339 stopAcquisition();
340 }
341 return true;
342 } else if (type == QEvent::MouseButtonRelease && me->button() == Qt::RightButton) {
343 stopAcquisition();
344
345 //TODO: pre-selection of gestures by length (optimization), if necessary
346 //possibly apply other heuristics
347 //then try all remaining gestures for sufficiently small distance
348 int dist = 0;
349 for (int i = 1; i < m_points.size(); i++) {
350 dist += (m_points[i] - m_points[i - 1]).manhattanLength();
351 if (dist > 40) {
352 matchShapeGesture();
353 return true;
354 }
355 //this was probably a small glitch while right-clicking if we get here.
356 //TODO: open the context menu or do whatever happens on right-click (how?)
357 }
358 return false;
359 }
360 }
361 return false;
362 }
363
364