File indexing completed on 2025-01-05 04:35:06

0001 /*
0002  * This file is part of the mouse gesture package.
0003  * Copyright (C) 2006 Johan Thelin <e8johan@gmail.com>
0004  * All rights reserved.
0005  *
0006  * Redistribution and use in source and binary forms, with or
0007  * without modification, are permitted provided that the
0008  * following conditions are met:
0009  *
0010  *   - Redistributions of source code must retain the above
0011  *     copyright notice, this list of conditions and the
0012  *     following disclaimer.
0013  *   - Redistributions in binary form must reproduce the
0014  *     above copyright notice, this list of conditions and
0015  *     the following disclaimer in the documentation and/or
0016  *     other materials provided with the distribution.
0017  *   - The names of its contributors may be used to endorse
0018  *     or promote products derived from this software without
0019  *     specific prior written permission.
0020  *
0021  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
0022  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
0023  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
0024  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0025  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
0026  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
0027  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0028  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
0029  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
0030  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
0031  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0032  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0033  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
0034  * POSSIBILITY OF SUCH DAMAGE.
0035  *
0036  */
0037 
0038 #include "mousegesturerecognizer.h"
0039 
0040 using namespace Gesture;
0041 
0042 // Private data structure
0043 struct MouseGestureRecognizer::Private {
0044     PosList positions;
0045     GestureList gestures;
0046 
0047     int minimumMovement2;
0048     double minimumMatch;
0049 
0050     bool allowDiagonals;
0051 };
0052 
0053 // Class implementation
0054 
0055 MouseGestureRecognizer::MouseGestureRecognizer(int minimumMovement, double minimumMatch, bool allowDiagonals)
0056 {
0057     d = new Private;
0058     d->minimumMovement2 = minimumMovement * minimumMovement;
0059     d->minimumMatch = minimumMatch;
0060 
0061     d->allowDiagonals = allowDiagonals;
0062 }
0063 
0064 MouseGestureRecognizer::~MouseGestureRecognizer()
0065 {
0066     delete d;
0067 }
0068 
0069 void MouseGestureRecognizer::addGestureDefinition(const GestureDefinition &gesture)
0070 {
0071     d->gestures.push_back(gesture);
0072 }
0073 
0074 void MouseGestureRecognizer::clearGestureDefinitions()
0075 {
0076     d->gestures.clear();
0077 }
0078 
0079 void MouseGestureRecognizer::startGesture(int x, int y)
0080 {
0081     d->positions.clear();
0082     d->positions.push_back(Pos(x, y));
0083 }
0084 
0085 bool MouseGestureRecognizer::endGesture(int x, int y)
0086 {
0087     bool matched = false;
0088 
0089     if (x != d->positions.back().x || y != d->positions.back().y) {
0090         d->positions.push_back(Pos(x, y));
0091     }
0092 
0093     int dx = x - d->positions.at(0).x;
0094     int dy = y - d->positions.at(0).y;
0095 
0096     if (dx * dx + dy * dy < d->minimumMovement2) {
0097         return false;
0098     }
0099 
0100     if (d->positions.size() > 1) {
0101         matched = recognizeGesture();
0102     }
0103 
0104     d->positions.clear();
0105 
0106     return matched;
0107 }
0108 
0109 void MouseGestureRecognizer::abortGesture()
0110 {
0111     d->positions.clear();
0112 }
0113 
0114 void MouseGestureRecognizer::addPoint(int x, int y)
0115 {
0116     int dx, dy;
0117 
0118     dx = x - d->positions.back().x;
0119     dy = y - d->positions.back().y;
0120 
0121     if (dx * dx + dy * dy >= d->minimumMovement2) {
0122         d->positions.push_back(Pos(x, y));
0123     }
0124 }
0125 
0126 PosList MouseGestureRecognizer::currentPath() const
0127 {
0128     return d->positions;
0129 }
0130 
0131 bool MouseGestureRecognizer::recognizeGesture()
0132 {
0133     PosList directions = simplify(limitDirections(d->positions, d->allowDiagonals));
0134     double minLength = calcLength(directions) * d->minimumMatch;
0135 
0136     while (directions.size() > 0 && calcLength(directions) > minLength) {
0137         for (GestureList::const_iterator gi = d->gestures.begin(); gi != d->gestures.end(); ++gi) {
0138             if (gi->directions.size() == directions.size()) {
0139                 bool match = true;
0140                 PosList::const_iterator pi = directions.begin();
0141                 for (DirectionList::const_iterator di = gi->directions.begin(); di != gi->directions.end() && match; ++di, ++pi) {
0142                     switch (*di) {
0143                     case UpLeft:
0144                         if (!(pi->y < 0 && pi->x < 0)) {
0145                             match = false;
0146                         }
0147 
0148                         break;
0149                     case UpRight:
0150                         if (!(pi->y < 0 && pi->x > 0)) {
0151                             match = false;
0152                         }
0153 
0154                         break;
0155                     case DownLeft:
0156                         if (!(pi->y > 0 && pi->x < 0)) {
0157                             match = false;
0158                         }
0159 
0160                         break;
0161                     case DownRight:
0162                         if (!(pi->y > 0 && pi->x > 0)) {
0163                             match = false;
0164                         }
0165 
0166                         break;
0167                     case Up:
0168                         if (pi->y >= 0 || pi->x != 0) {
0169                             match = false;
0170                         }
0171 
0172                         break;
0173                     case Down:
0174                         if (pi->y <= 0 || pi->x != 0) {
0175                             match = false;
0176                         }
0177 
0178                         break;
0179                     case Left:
0180                         if (pi->x >= 0 || pi->y != 0) {
0181                             match = false;
0182                         }
0183 
0184                         break;
0185                     case Right:
0186                         if (pi->x <= 0 || pi->y != 0) {
0187                             match = false;
0188                         }
0189 
0190                         break;
0191                     case AnyHorizontal:
0192                         if (pi->x == 0 || pi->y != 0) {
0193                             match = false;
0194                         }
0195 
0196                         break;
0197                     case AnyVertical:
0198                         if (pi->y == 0 || pi->x != 0) {
0199                             match = false;
0200                         }
0201 
0202                         break;
0203                     case NoMatch:
0204                         match = false;
0205 
0206                         break;
0207                     }
0208                 }
0209 
0210                 if (match) {
0211                     gi->callbackClass->callback();
0212                     return true;
0213                 }
0214             }
0215         }
0216 
0217         directions = simplify(removeShortest(directions));
0218     }
0219 
0220     for (GestureList::const_iterator gi = d->gestures.begin(); gi != d->gestures.end(); ++gi) {
0221         if (gi->directions.size() == 1) {
0222             if (gi->directions.back() == NoMatch) {
0223                 gi->callbackClass->callback();
0224                 return true;
0225             }
0226         }
0227     }
0228 
0229     return false;
0230 }
0231 
0232 // Support functions implementation
0233 
0234 /*
0235  *  limitDirections - limits the directions of a list to up, down, left or right.
0236  *
0237  *  Notice! This function converts the list to a set of relative moves instead of a set of screen coordinates.
0238  */
0239 PosList MouseGestureRecognizer::limitDirections(const PosList &positions, bool allowDiagonals)
0240 {
0241     PosList res;
0242     int lastx, lasty;
0243     bool firstTime = true;
0244 
0245     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
0246         if (firstTime) {
0247             lastx = ii->x;
0248             lasty = ii->y;
0249 
0250             firstTime = false;
0251         }
0252         else {
0253             int dx, dy;
0254 
0255             dx = ii->x - lastx;
0256             dy = ii->y - lasty;
0257 
0258             const int directions[8][2] = { {0, 15}, {0, -15}, {15, 0}, { -15, 0}, {10, 10}, { -10, 10}, { -10, -10}, {10, -10} };
0259             int maxValue = 0;
0260             int maxIndex = -1;
0261 
0262             for (int i = 0; i < (allowDiagonals ? 8 : 4); i++) {
0263                 int value = dx * directions[i][0] + dy * directions[i][1];
0264                 if (value > maxValue) {
0265                     maxValue = value;
0266                     maxIndex = i;
0267                 }
0268             }
0269 
0270             if (maxIndex == -1) {
0271                 dx = dy = 0;
0272             }
0273             else {
0274                 dx = directions[maxIndex][0]; // * abs(sqrt(maxValue))
0275                 dy = directions[maxIndex][1]; // * abs(sqrt(maxValue))
0276             }
0277 
0278             res.push_back(Pos(dx, dy));
0279 
0280             lastx = ii->x;
0281             lasty = ii->y;
0282         }
0283     }
0284 
0285     return res;
0286 }
0287 
0288 /*
0289  *  simplify - joins together continuous movements in the same direction.
0290  *
0291  *  Notice! This function expects a list of limited directions.
0292  */
0293 PosList MouseGestureRecognizer::simplify(const PosList &positions)
0294 {
0295     PosList res;
0296     int lastdx = 0, lastdy = 0;
0297     bool firstTime = true;
0298 
0299     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
0300         if (firstTime) {
0301             lastdx = ii->x;
0302             lastdy = ii->y;
0303 
0304             firstTime = false;
0305         }
0306         else {
0307             bool joined = false;
0308 
0309             //horizontal lines
0310             if (((lastdx > 0 && ii->x > 0) || (lastdx < 0 && ii->x < 0)) && (lastdy == 0 && ii->y == 0)) {
0311                 lastdx += ii->x;
0312                 joined = true;
0313             }
0314             //vertical
0315             if (((lastdy > 0 && ii->y > 0) || (lastdy < 0 && ii->y < 0)) && (lastdx == 0 && ii->x == 0)) {
0316                 lastdy += ii->y;
0317                 joined = true;
0318             }
0319             //down right/left
0320             if (((lastdx > 0 && ii->x > 0) || (lastdx < 0 && ii->x < 0)) && (lastdy > 0 && ii->y > 0)) {
0321                 lastdx += ii->x;
0322                 lastdy += ii->y;
0323                 joined = true;
0324             }
0325             //up left/right
0326             if (((lastdx > 0 && ii->x > 0) || (lastdx < 0 && ii->x < 0)) && (lastdy < 0 && ii->y < 0)) {
0327                 lastdx += ii->x;
0328                 lastdy += ii->y;
0329                 joined = true;
0330             }
0331 
0332             if (!joined) {
0333                 res.push_back(Pos(lastdx, lastdy));
0334 
0335                 lastdx = ii->x;
0336                 lastdy = ii->y;
0337             }
0338         }
0339     }
0340 
0341     if (lastdx != 0 || lastdy != 0) {
0342         res.push_back(Pos(lastdx, lastdy));
0343     }
0344 
0345     return res;
0346 }
0347 
0348 /*
0349  *  removeShortest - removes the shortest segment from a list of movements.
0350  *
0351  *  If there are several equally short segments, the first one is removed.
0352  */
0353 PosList MouseGestureRecognizer::removeShortest(const PosList &positions)
0354 {
0355     PosList res;
0356 
0357     int shortestSoFar;
0358     PosList::const_iterator shortest;
0359     bool firstTime = true;
0360 
0361     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
0362         if (firstTime) {
0363             shortestSoFar = ii->x * ii->x + ii->y * ii->y;
0364             shortest = ii;
0365 
0366             firstTime = false;
0367         }
0368         else {
0369             if ((ii->x * ii->x + ii->y * ii->y) < shortestSoFar) {
0370                 shortestSoFar = ii->x * ii->x + ii->y * ii->y;
0371                 shortest = ii;
0372             }
0373         }
0374     }
0375 
0376     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
0377         if (ii != shortest) {
0378             res.push_back(*ii);
0379         }
0380     }
0381 
0382     return res;
0383 }
0384 
0385 /*
0386  *  calcLength - calculates the total length of the movements from a list of relative movements.
0387  */
0388 int MouseGestureRecognizer::calcLength(const PosList &positions)
0389 {
0390     int res = 0;
0391 
0392     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
0393         if (ii->x > 0) {
0394             res += ii->x;
0395         }
0396         else if (ii->x < 0) {
0397             res -= ii->x;
0398         }
0399         else if (ii->y > 0) {
0400             res += ii->y;
0401         }
0402         else {
0403             res -= ii->y;
0404         }
0405     }
0406 
0407     return res;
0408 }
0409