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