File indexing completed on 2024-12-22 04:14:05
0001 /* This file is part of the KDE libraries 0002 SPDX-FileCopyrightText: 2006, 2007 Andreas Hartmetz (ahartmetz@gmail.com) 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "kgesturemap_p.h" 0008 0009 #include <qapplication.h> 0010 #include <QAction> 0011 #include <QActionEvent> 0012 #include <QDebug> 0013 0014 /* 0015 This is a class for internal use by the KDE libraries only. This class 0016 may change or go away without notice so don't try to use it in non-kdelibs 0017 code. 0018 */ 0019 0020 class KisKGestureMapContainer 0021 { 0022 public: 0023 KisKGestureMap gestureMap; 0024 }; 0025 0026 Q_GLOBAL_STATIC(KisKGestureMapContainer, g_instance) 0027 0028 KisKGestureMap::~KisKGestureMap() 0029 { 0030 } 0031 0032 KisKGestureMap *KisKGestureMap::self() 0033 { 0034 return &g_instance()->gestureMap; 0035 } 0036 0037 KisKGestureMap::KisKGestureMap() 0038 { 0039 m_gestureTimeout.setSingleShot(true); 0040 connect(&m_gestureTimeout, SIGNAL(timeout()), this, SLOT(stopAcquisition())); 0041 //It would be nice to install the filter on demand. Unfortunately, 0042 //undesired behavior might result due to changing invocation 0043 //orders of different event filters. 0044 if (qApp) { 0045 qApp->installEventFilter(this); 0046 } 0047 } 0048 0049 void KisKGestureMap::setShapeGesture(QAction *act, const KisKShapeGesture &gesture) 0050 { 0051 if (!gesture.isValid() || !act) { 0052 return; 0053 } 0054 qDebug() << "KisKGestureMap::addGesture(KisKShapeGesture ...)"; 0055 if (m_shapeGestures.contains(gesture)) { 0056 qWarning() << "Replacing an action for a gesture already taken"; 0057 } 0058 m_shapeGestures.insert(gesture, act); 0059 } 0060 0061 void KisKGestureMap::setRockerGesture(QAction *act, const KisKRockerGesture &gesture) 0062 { 0063 if (!gesture.isValid() || !act) { 0064 return; 0065 } 0066 qDebug() << "KisKGestureMap::addGesture(KisKRockerGesture ...)"; 0067 if (m_rockerGestures.contains(gesture)) { 0068 qWarning() << "Replacing an action for a gesture already taken"; 0069 } 0070 m_rockerGestures.insert(gesture, act); 0071 } 0072 0073 void KisKGestureMap::setDefaultShapeGesture(QAction *act, const KisKShapeGesture &gesture) 0074 { 0075 if (!gesture.isValid() || !act) { 0076 return; 0077 } 0078 qDebug() << "KisKGestureMap::addGesture(KisKShapeGesture ...)"; 0079 if (m_defaultShapeGestures.contains(gesture)) { 0080 qWarning() << "Replacing an action for a gesture already taken"; 0081 } 0082 m_defaultShapeGestures.insert(gesture, act); 0083 } 0084 0085 void KisKGestureMap::setDefaultRockerGesture(QAction *act, const KisKRockerGesture &gesture) 0086 { 0087 if (!gesture.isValid() || !act) { 0088 return; 0089 } 0090 qDebug() << "KisKGestureMap::addGesture(KisKRockerGesture ...)"; 0091 if (m_defaultRockerGestures.contains(gesture)) { 0092 qWarning() << "Replacing an action for a gesture already taken"; 0093 } 0094 m_defaultRockerGestures.insert(gesture, act); 0095 } 0096 0097 void KisKGestureMap::removeAllGestures(QAction *kact) 0098 { 0099 KisKShapeGesture activeGesture; 0100 ShapeGestureHash::iterator si = m_shapeGestures.begin(); 0101 ShapeGestureHash::iterator send = m_shapeGestures.end(); 0102 for (; si != send; ++si) { 0103 if (si.value() == kact) { 0104 m_shapeGestures.remove(si.key()); 0105 break; 0106 } 0107 } 0108 0109 si = m_defaultShapeGestures.begin(); 0110 send = m_defaultShapeGestures.end(); 0111 for (; si != send; ++si) { 0112 if (si.value() == kact) { 0113 m_defaultShapeGestures.remove(si.key()); 0114 break; 0115 } 0116 } 0117 0118 RockerGestureHash::iterator ri = m_rockerGestures.begin(); 0119 RockerGestureHash::iterator rend = m_rockerGestures.end(); 0120 for (; ri != rend; ++ri) { 0121 if (ri.value() == kact) { 0122 m_rockerGestures.remove(ri.key()); 0123 break; 0124 } 0125 } 0126 0127 ri = m_defaultRockerGestures.begin(); 0128 rend = m_defaultRockerGestures.end(); 0129 for (; ri != rend; ++ri) { 0130 if (ri.value() == kact) { 0131 m_defaultRockerGestures.remove(ri.key()); 0132 break; 0133 } 0134 } 0135 } 0136 0137 QAction *KisKGestureMap::findAction(const KisKShapeGesture &gesture) const 0138 { 0139 return m_shapeGestures.value(gesture); 0140 } 0141 0142 QAction *KisKGestureMap::findAction(const KisKRockerGesture &gesture) const 0143 { 0144 return m_rockerGestures.value(gesture); 0145 } 0146 0147 void KisKGestureMap::installEventFilterOnMe(QApplication *app) 0148 { 0149 app->installEventFilter(this); 0150 } 0151 0152 KisKShapeGesture KisKGestureMap::shapeGesture(const QAction *kact) const 0153 { 0154 KisKShapeGesture activeGesture; 0155 ShapeGestureHash::const_iterator it = m_shapeGestures.constBegin(); 0156 ShapeGestureHash::const_iterator end = m_shapeGestures.constEnd(); 0157 for (; it != end; ++it) { 0158 if (it.value() == kact) { 0159 activeGesture = it.key(); 0160 break; 0161 } 0162 } 0163 return activeGesture; 0164 } 0165 0166 KisKShapeGesture KisKGestureMap::defaultShapeGesture(const QAction *kact) const 0167 { 0168 KisKShapeGesture defaultGesture; 0169 ShapeGestureHash::const_iterator it = m_defaultShapeGestures.constBegin(); 0170 ShapeGestureHash::const_iterator end = m_defaultShapeGestures.constEnd(); 0171 for (; it != end; ++it) { 0172 if (it.value() == kact) { 0173 defaultGesture = it.key(); 0174 break; 0175 } 0176 } 0177 return defaultGesture; 0178 } 0179 0180 KisKRockerGesture KisKGestureMap::rockerGesture(const QAction *kact) const 0181 { 0182 KisKRockerGesture activeGesture; 0183 RockerGestureHash::const_iterator it = m_rockerGestures.constBegin(); 0184 RockerGestureHash::const_iterator end = m_rockerGestures.constEnd(); 0185 for (; it != end; ++it) { 0186 if (it.value() == kact) { 0187 activeGesture = it.key(); 0188 break; 0189 } 0190 } 0191 return activeGesture; 0192 } 0193 0194 KisKRockerGesture KisKGestureMap::defaultRockerGesture(const QAction *kact) const 0195 { 0196 KisKRockerGesture defaultGesture; 0197 RockerGestureHash::const_iterator it = m_defaultRockerGestures.constBegin(); 0198 RockerGestureHash::const_iterator end = m_defaultRockerGestures.constEnd(); 0199 for (; it != end; ++it) { 0200 if (it.value() == kact) { 0201 defaultGesture = it.key(); 0202 break; 0203 } 0204 } 0205 return defaultGesture; 0206 } 0207 0208 inline int KisKGestureMap::bitCount(int n) 0209 { 0210 int count = 0; 0211 while (n) { 0212 n &= (n - 1); 0213 count++; 0214 } 0215 return count; 0216 } 0217 0218 void KisKGestureMap::handleAction(QAction *kact) 0219 { 0220 if (!kact) { 0221 return; 0222 } 0223 qDebug() << "handleAction"; 0224 //TODO: only activate in the action's context, just like keyboard shortcuts 0225 kact->trigger(); 0226 return; 0227 } 0228 0229 void KisKGestureMap::matchShapeGesture() 0230 { 0231 //TODO: tune and tweak until satisfied with result :) 0232 m_shapeGesture.setShape(m_points); 0233 float dist, minDist = 20.0; 0234 QAction *bestMatch = 0; 0235 0236 for (QHash<KisKShapeGesture, QAction *>::const_iterator it = m_shapeGestures.constBegin(); 0237 it != m_shapeGestures.constEnd(); ++it) { 0238 dist = m_shapeGesture.distance(it.key(), 1000.0); 0239 if (dist < minDist) { 0240 minDist = dist; 0241 bestMatch = it.value(); 0242 } 0243 } 0244 handleAction(bestMatch); 0245 } 0246 0247 //slot 0248 void KisKGestureMap::stopAcquisition() 0249 { 0250 m_gestureTimeout.stop(); 0251 m_acquiring = false; 0252 } 0253 0254 //TODO: Probably kwin, kded and others should not have a gesture map. 0255 //Maybe making them friends and providing a private "die()" function would work. 0256 /* 0257 * Act on rocker gestures immediately and collect movement data for evaluation. 0258 * The decision when to consume and when to relay an event is quite tricky. 0259 * I decided to only consume clicks that belong to completed rocker gestures. 0260 * A user might e.g. go back in a browser several times using rocker gestures, 0261 * thus changing what's under the cursor every time. This might lead to 0262 * unintended clicks on links where there was free space before. 0263 */ 0264 0265 bool KisKGestureMap::eventFilter(QObject *obj, QEvent *e) 0266 { 0267 //disable until it does not interfere with other input any more 0268 return false; 0269 Q_UNUSED(obj); 0270 int type = e->type(); 0271 0272 //catch right-clicks disguised as context menu events. if we ignore a 0273 //context menu event caused by a right-click, it should get resent 0274 //as a right-click event, according to documentation. 0275 //### this is preliminary 0276 if (type == QEvent::ContextMenu) { 0277 QContextMenuEvent *cme = static_cast<QContextMenuEvent *>(e); 0278 if (cme->reason() == QContextMenuEvent::Mouse) { 0279 cme->ignore(); 0280 return true; 0281 } 0282 return false; 0283 } 0284 0285 if (type < QEvent::MouseButtonPress || type > QEvent::MouseMove) { 0286 return false; 0287 } 0288 0289 QMouseEvent *me = static_cast<QMouseEvent *>(e); 0290 if (type == QEvent::MouseButtonPress) { 0291 int nButtonsDown = bitCount(me->buttons()); 0292 qDebug() << "number of buttons down:" << nButtonsDown; 0293 0294 //right button down starts gesture acquisition 0295 if (nButtonsDown == 1 && me->button() == Qt::RightButton) { 0296 //"startAcquisition()" 0297 m_acquiring = true; 0298 m_gestureTimeout.start(4000); 0299 qDebug() << "========================"; 0300 m_points.clear(); 0301 m_points.append(me->pos()); 0302 return true; 0303 } else if (nButtonsDown != 2) { 0304 return false; 0305 } 0306 0307 //rocker gestures. do not trigger any movement gestures from now on. 0308 stopAcquisition(); 0309 int buttonHeld = me->buttons() ^ me->button(); 0310 m_rockerGesture.setButtons(static_cast<Qt::MouseButton>(buttonHeld), me->button()); 0311 QAction *match = m_rockerGestures.value(m_rockerGesture); 0312 if (!match) { 0313 return false; 0314 } 0315 handleAction(match); 0316 return true; 0317 } 0318 0319 if (m_acquiring) { 0320 if (type == QEvent::MouseMove) { 0321 m_points.append(me->pos()); 0322 //abort to avoid using too much memory. 1010 points should be enough 0323 //for everyone! :) 0324 //next reallocation of m_points would happen at 1012 items 0325 if (m_points.size() > 1010) { 0326 stopAcquisition(); 0327 } 0328 return true; 0329 } else if (type == QEvent::MouseButtonRelease && me->button() == Qt::RightButton) { 0330 stopAcquisition(); 0331 0332 //TODO: pre-selection of gestures by length (optimization), if necessary 0333 //possibly apply other heuristics 0334 //then try all remaining gestures for sufficiently small distance 0335 int dist = 0; 0336 for (int i = 1; i < m_points.size(); i++) { 0337 dist += (m_points[i] - m_points[i - 1]).manhattanLength(); 0338 if (dist > 40) { 0339 matchShapeGesture(); 0340 return true; 0341 } 0342 //this was probably a small glitch while right-clicking if we get here. 0343 //TODO: open the context menu or do whatever happens on right-click (how?) 0344 } 0345 return false; 0346 } 0347 } 0348 return false; 0349 } 0350