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