KDEUI
kgesturemap.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE libraries 00002 Copyright (C) 2006,2007 Andreas Hartmetz (ahartmetz@gmail.com) 00003 00004 This library is free software; you can redistribute it and/or 00005 modify it under the terms of the GNU Library General Public 00006 License as published by the Free Software Foundation; either 00007 version 2 of the License, or (at your option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include "kgesturemap.h" 00021 00022 #include <kapplication.h> 00023 #include <kaction.h> 00024 #include <QtGui/QActionEvent> 00025 00026 #include <kglobal.h> 00027 00028 #include <kdebug.h> 00029 00030 /* 00031 This is a class for internal use by the KDE libraries only. This class 00032 may change or go away without notice so don't try to use it in non-kdelibs 00033 code. 00034 */ 00035 00036 class KGestureMapContainer { 00037 public: 00038 KGestureMap gestureMap; 00039 }; 00040 00041 00042 K_GLOBAL_STATIC(KGestureMapContainer, g_instance) 00043 00044 00045 KGestureMap::~KGestureMap() 00046 { 00047 } 00048 00049 00050 KGestureMap *KGestureMap::self() 00051 { 00052 return &g_instance->gestureMap; 00053 } 00054 00055 00056 KGestureMap::KGestureMap() 00057 { 00058 m_gestureTimeout.setSingleShot(true); 00059 connect(&m_gestureTimeout, SIGNAL(timeout()), this, SLOT(stopAcquisition())); 00060 //It would be nice to install the filter on demand. Unfortunately, 00061 //undesired behavior might result due to changing invocation 00062 //orders of different event filters. 00063 if (qApp) 00064 qApp->installEventFilter(this); 00065 } 00066 00067 00068 void KGestureMap::addGesture(const KShapeGesture &gesture, KAction *act) 00069 { 00070 if (!gesture.isValid() || !act) 00071 return; 00072 kDebug(283) << "KGestureMap::addGesture(KShapeGesture ...)"; 00073 if (!m_shapeGestures.contains(gesture)) 00074 m_shapeGestures.insert(gesture, act); 00075 else 00076 kDebug(283) << "Tried to register an action for a gesture already taken"; 00077 } 00078 00079 00080 void KGestureMap::addGesture(const KRockerGesture &gesture, KAction *act) 00081 { 00082 if (!gesture.isValid() || !act) 00083 return; 00084 kDebug(283) << "KGestureMap::addGesture(KRockerGesture ...)"; 00085 if (!m_rockerGestures.contains(gesture)) 00086 m_rockerGestures.insert(gesture, act); 00087 else 00088 kDebug(283) << "Tried to register an action for a gesture already taken"; 00089 } 00090 00091 00092 void KGestureMap::removeGesture(const KShapeGesture &gesture, KAction *act) 00093 { 00094 if (!gesture.isValid()) 00095 return; 00096 kDebug(283) << "KGestureMap::removeGesture(KShapeGesture ...)"; 00097 KAction *oldAct = m_shapeGestures.value(gesture); 00098 if (oldAct == act || !act /*wildcard*/) 00099 m_shapeGestures.remove(gesture); 00100 } 00101 00102 00103 void KGestureMap::removeGesture(const KRockerGesture &gesture, KAction *act) 00104 { 00105 if (!gesture.isValid()) 00106 return; 00107 kDebug(283) << "KGestureMap::removeGesture(KRockerGesture ...)"; 00108 KAction *oldAct = m_rockerGestures.value(gesture); 00109 if (oldAct == act || !act /*wildcard*/) 00110 m_rockerGestures.remove(gesture); 00111 } 00112 00113 00114 KAction *KGestureMap::findAction(const KShapeGesture &gesture) const 00115 { 00116 return m_shapeGestures.value(gesture); 00117 } 00118 00119 00120 KAction *KGestureMap::findAction(const KRockerGesture &gesture) const 00121 { 00122 return m_rockerGestures.value(gesture); 00123 } 00124 00125 00126 void KGestureMap::installEventFilterOnMe(KApplication *app) 00127 { 00128 app->installEventFilter(this); 00129 } 00130 00131 00132 inline int KGestureMap::bitCount(int n) 00133 { 00134 int count = 0; 00135 while (n) { 00136 n &= (n - 1); 00137 count++; 00138 } 00139 return count; 00140 } 00141 00142 00143 void KGestureMap::handleAction(KAction *kact) 00144 { 00145 if (!kact) 00146 return; 00147 kDebug(283) << "handleAction"; 00148 //TODO: only activate in the action's context, just like keyboard shortcuts 00149 kact->trigger(); 00150 return; 00151 } 00152 00153 00154 void KGestureMap::matchShapeGesture() 00155 { 00156 //TODO: tune and tweak until satisfied with result :) 00157 m_shapeGesture.setShape(m_points); 00158 float dist, minDist = 20.0; 00159 KAction *bestMatch = 0; 00160 00161 for (QHash<KShapeGesture, KAction *>::const_iterator it = m_shapeGestures.constBegin(); 00162 it != m_shapeGestures.constEnd(); ++it) { 00163 dist = m_shapeGesture.distance(it.key(), 1000.0); 00164 if (dist < minDist) { 00165 minDist = dist; 00166 bestMatch = it.value(); 00167 } 00168 } 00169 handleAction(bestMatch); 00170 } 00171 00172 00173 //slot 00174 void KGestureMap::stopAcquisition() 00175 { 00176 m_gestureTimeout.stop(); 00177 m_acquiring = false; 00178 } 00179 00180 00181 //TODO: Probably kwin, kded and others should not have a gesture map. 00182 //Maybe making them friends and providing a private "die()" function would work. 00183 /* 00184 * Act on rocker gestures immediately and collect movement data for evaluation. 00185 * The decision when to consume and when to relay an event is quite tricky. 00186 * I decided to only consume clicks that belong to completed rocker gestures. 00187 * A user might e.g. go back in a browser several times using rocker gestures, 00188 * thus changing what's under the cursor every time. This might lead to 00189 * unintended clicks on links where there was free space before. 00190 */ 00191 00192 bool KGestureMap::eventFilter(QObject *obj, QEvent *e) 00193 { 00194 //disable until it does not interfere with other input any more 00195 return false; 00196 Q_UNUSED(obj); 00197 int type = e->type(); 00198 00199 //catch right-clicks disguised as context menu events. if we ignore a 00200 //context menu event caused by a right-click, it should get resent 00201 //as a right-click event, according to documentation. 00202 //### this is preliminary 00203 if (type == QEvent::ContextMenu) { 00204 QContextMenuEvent *cme = static_cast<QContextMenuEvent *>(e); 00205 if (cme->reason() == QContextMenuEvent::Mouse) { 00206 cme->ignore(); 00207 return true; 00208 } 00209 return false; 00210 } 00211 00212 if (type < QEvent::MouseButtonPress || type > QEvent::MouseMove) 00213 return false; 00214 00215 QMouseEvent *me = static_cast<QMouseEvent *>(e); 00216 if (type == QEvent::MouseButtonPress) { 00217 int nButtonsDown = bitCount(me->buttons()); 00218 kDebug(283) << "number of buttons down:" << nButtonsDown; 00219 00220 //right button down starts gesture acquisition 00221 if (nButtonsDown == 1 && me->button() == Qt::RightButton) { 00222 //"startAcquisition()" 00223 m_acquiring = true; 00224 m_gestureTimeout.start(4000); 00225 kDebug(283) << "========================"; 00226 m_points.clear(); 00227 m_points.append(me->pos()); 00228 return true; 00229 } else if (nButtonsDown != 2) 00230 return false; 00231 00232 //rocker gestures. do not trigger any movement gestures from now on. 00233 stopAcquisition(); 00234 int buttonHeld = me->buttons() ^ me->button(); 00235 m_rockerGesture.setButtons(static_cast<Qt::MouseButton>(buttonHeld), me->button()); 00236 KAction *match = m_rockerGestures.value(m_rockerGesture); 00237 if (!match) 00238 return false; 00239 handleAction(match); 00240 return true; 00241 } 00242 00243 if (m_acquiring) { 00244 if (type == QEvent::MouseMove) { 00245 m_points.append(me->pos()); 00246 //abort to avoid using too much memory. 1010 points should be enough 00247 //for everyone! :) 00248 //next reallocation of m_points would happen at 1012 items 00249 if (m_points.size() > 1010) 00250 stopAcquisition(); 00251 return true; 00252 } else if (type == QEvent::MouseButtonRelease && me->button() == Qt::RightButton) { 00253 stopAcquisition(); 00254 00255 //TODO: pre-selection of gestures by length (optimization), if necessary 00256 //possibly apply other heuristics 00257 //then try all remaining gestures for sufficiently small distance 00258 int dist = 0; 00259 for (int i = 1; i < m_points.size(); i++) { 00260 dist += (m_points[i] - m_points[i-1]).manhattanLength(); 00261 if (dist > 40) { 00262 matchShapeGesture(); 00263 return true; 00264 } 00265 //this was probably a small glitch while right-clicking if we get here. 00266 //TODO: open the context menu or do whatever happens on right-click (how?) 00267 } 00268 return false; 00269 } 00270 } 00271 return false; 00272 } 00273 00274 #include "kgesturemap.moc"
KDE 4.6 API Reference