1 /*
2  * This file is part of the mouse gesture package.
3  * Copyright (C) 2006 Johan Thelin <e8johan@gmail.com>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or
7  * without modification, are permitted provided that the
8  * following conditions are met:
9  *
10  *   - Redistributions of source code must retain the above
11  *     copyright notice, this list of conditions and the
12  *     following disclaimer.
13  *   - Redistributions in binary form must reproduce the
14  *     above copyright notice, this list of conditions and
15  *     the following disclaimer in the documentation and/or
16  *     other materials provided with the distribution.
17  *   - The names of its contributors may be used to endorse
18  *     or promote products derived from this software without
19  *     specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
22  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
23  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
24  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
30  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
32  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34  * POSSIBILITY OF SUCH DAMAGE.
35  *
36  */
37 
38 #include "mousegesturerecognizer.h"
39 
40 using namespace Gesture;
41 
42 // Private data structure
43 struct MouseGestureRecognizer::Private {
44     PosList positions;
45     GestureList gestures;
46 
47     int minimumMovement2;
48     double minimumMatch;
49 
50     bool allowDiagonals;
51 };
52 
53 // Class implementation
54 
MouseGestureRecognizer(int minimumMovement,double minimumMatch,bool allowDiagonals)55 MouseGestureRecognizer::MouseGestureRecognizer(int minimumMovement, double minimumMatch, bool allowDiagonals)
56 {
57     d = new Private;
58     d->minimumMovement2 = minimumMovement * minimumMovement;
59     d->minimumMatch = minimumMatch;
60 
61     d->allowDiagonals = allowDiagonals;
62 }
63 
~MouseGestureRecognizer()64 MouseGestureRecognizer::~MouseGestureRecognizer()
65 {
66     delete d;
67 }
68 
addGestureDefinition(const GestureDefinition & gesture)69 void MouseGestureRecognizer::addGestureDefinition(const GestureDefinition &gesture)
70 {
71     d->gestures.push_back(gesture);
72 }
73 
clearGestureDefinitions()74 void MouseGestureRecognizer::clearGestureDefinitions()
75 {
76     d->gestures.clear();
77 }
78 
startGesture(int x,int y)79 void MouseGestureRecognizer::startGesture(int x, int y)
80 {
81     d->positions.clear();
82     d->positions.push_back(Pos(x, y));
83 }
84 
endGesture(int x,int y)85 bool MouseGestureRecognizer::endGesture(int x, int y)
86 {
87     bool matched = false;
88 
89     if (x != d->positions.back().x || y != d->positions.back().y) {
90         d->positions.push_back(Pos(x, y));
91     }
92 
93     int dx = x - d->positions.at(0).x;
94     int dy = y - d->positions.at(0).y;
95 
96     if (dx * dx + dy * dy < d->minimumMovement2) {
97         return false;
98     }
99 
100     if (d->positions.size() > 1) {
101         matched = recognizeGesture();
102     }
103 
104     d->positions.clear();
105 
106     return matched;
107 }
108 
abortGesture()109 void MouseGestureRecognizer::abortGesture()
110 {
111     d->positions.clear();
112 }
113 
addPoint(int x,int y)114 void MouseGestureRecognizer::addPoint(int x, int y)
115 {
116     int dx, dy;
117 
118     dx = x - d->positions.back().x;
119     dy = y - d->positions.back().y;
120 
121     if (dx * dx + dy * dy >= d->minimumMovement2) {
122         d->positions.push_back(Pos(x, y));
123     }
124 }
125 
currentPath() const126 PosList MouseGestureRecognizer::currentPath() const
127 {
128     return d->positions;
129 }
130 
recognizeGesture()131 bool MouseGestureRecognizer::recognizeGesture()
132 {
133     PosList directions = simplify(limitDirections(d->positions, d->allowDiagonals));
134     double minLength = calcLength(directions) * d->minimumMatch;
135 
136     while (directions.size() > 0 && calcLength(directions) > minLength) {
137         for (GestureList::const_iterator gi = d->gestures.begin(); gi != d->gestures.end(); ++gi) {
138             if (gi->directions.size() == directions.size()) {
139                 bool match = true;
140                 PosList::const_iterator pi = directions.begin();
141                 for (DirectionList::const_iterator di = gi->directions.begin(); di != gi->directions.end() && match; ++di, ++pi) {
142                     switch (*di) {
143                     case UpLeft:
144                         if (!(pi->y < 0 && pi->x < 0)) {
145                             match = false;
146                         }
147 
148                         break;
149                     case UpRight:
150                         if (!(pi->y < 0 && pi->x > 0)) {
151                             match = false;
152                         }
153 
154                         break;
155                     case DownLeft:
156                         if (!(pi->y > 0 && pi->x < 0)) {
157                             match = false;
158                         }
159 
160                         break;
161                     case DownRight:
162                         if (!(pi->y > 0 && pi->x > 0)) {
163                             match = false;
164                         }
165 
166                         break;
167                     case Up:
168                         if (pi->y >= 0 || pi->x != 0) {
169                             match = false;
170                         }
171 
172                         break;
173                     case Down:
174                         if (pi->y <= 0 || pi->x != 0) {
175                             match = false;
176                         }
177 
178                         break;
179                     case Left:
180                         if (pi->x >= 0 || pi->y != 0) {
181                             match = false;
182                         }
183 
184                         break;
185                     case Right:
186                         if (pi->x <= 0 || pi->y != 0) {
187                             match = false;
188                         }
189 
190                         break;
191                     case AnyHorizontal:
192                         if (pi->x == 0 || pi->y != 0) {
193                             match = false;
194                         }
195 
196                         break;
197                     case AnyVertical:
198                         if (pi->y == 0 || pi->x != 0) {
199                             match = false;
200                         }
201 
202                         break;
203                     case NoMatch:
204                         match = false;
205 
206                         break;
207                     }
208                 }
209 
210                 if (match) {
211                     gi->callbackClass->callback();
212                     return true;
213                 }
214             }
215         }
216 
217         directions = simplify(removeShortest(directions));
218     }
219 
220     for (GestureList::const_iterator gi = d->gestures.begin(); gi != d->gestures.end(); ++gi) {
221         if (gi->directions.size() == 1) {
222             if (gi->directions.back() == NoMatch) {
223                 gi->callbackClass->callback();
224                 return true;
225             }
226         }
227     }
228 
229     return false;
230 }
231 
232 // Support functions implementation
233 
234 /*
235  *  limitDirections - limits the directions of a list to up, down, left or right.
236  *
237  *  Notice! This function converts the list to a set of relative moves instead of a set of screen coordinates.
238  */
limitDirections(const PosList & positions,bool allowDiagonals)239 PosList MouseGestureRecognizer::limitDirections(const PosList &positions, bool allowDiagonals)
240 {
241     PosList res;
242     int lastx, lasty;
243     bool firstTime = true;
244 
245     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
246         if (firstTime) {
247             lastx = ii->x;
248             lasty = ii->y;
249 
250             firstTime = false;
251         }
252         else {
253             int dx, dy;
254 
255             dx = ii->x - lastx;
256             dy = ii->y - lasty;
257 
258             const int directions[8][2] = { {0, 15}, {0, -15}, {15, 0}, { -15, 0}, {10, 10}, { -10, 10}, { -10, -10}, {10, -10} };
259             int maxValue = 0;
260             int maxIndex = -1;
261 
262             for (int i = 0; i < (allowDiagonals ? 8 : 4); i++) {
263                 int value = dx * directions[i][0] + dy * directions[i][1];
264                 if (value > maxValue) {
265                     maxValue = value;
266                     maxIndex = i;
267                 }
268             }
269 
270             if (maxIndex == -1) {
271                 dx = dy = 0;
272             }
273             else {
274                 dx = directions[maxIndex][0]; // * abs(sqrt(maxValue))
275                 dy = directions[maxIndex][1]; // * abs(sqrt(maxValue))
276             }
277 
278             res.push_back(Pos(dx, dy));
279 
280             lastx = ii->x;
281             lasty = ii->y;
282         }
283     }
284 
285     return res;
286 }
287 
288 /*
289  *  simplify - joins together continuous movements in the same direction.
290  *
291  *  Notice! This function expects a list of limited directions.
292  */
simplify(const PosList & positions)293 PosList MouseGestureRecognizer::simplify(const PosList &positions)
294 {
295     PosList res;
296     int lastdx = 0, lastdy = 0;
297     bool firstTime = true;
298 
299     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
300         if (firstTime) {
301             lastdx = ii->x;
302             lastdy = ii->y;
303 
304             firstTime = false;
305         }
306         else {
307             bool joined = false;
308 
309             //horizontal lines
310             if (((lastdx > 0 && ii->x > 0) || (lastdx < 0 && ii->x < 0)) && (lastdy == 0 && ii->y == 0)) {
311                 lastdx += ii->x;
312                 joined = true;
313             }
314             //vertical
315             if (((lastdy > 0 && ii->y > 0) || (lastdy < 0 && ii->y < 0)) && (lastdx == 0 && ii->x == 0)) {
316                 lastdy += ii->y;
317                 joined = true;
318             }
319             //down right/left
320             if (((lastdx > 0 && ii->x > 0) || (lastdx < 0 && ii->x < 0)) && (lastdy > 0 && ii->y > 0)) {
321                 lastdx += ii->x;
322                 lastdy += ii->y;
323                 joined = true;
324             }
325             //up left/right
326             if (((lastdx > 0 && ii->x > 0) || (lastdx < 0 && ii->x < 0)) && (lastdy < 0 && ii->y < 0)) {
327                 lastdx += ii->x;
328                 lastdy += ii->y;
329                 joined = true;
330             }
331 
332             if (!joined) {
333                 res.push_back(Pos(lastdx, lastdy));
334 
335                 lastdx = ii->x;
336                 lastdy = ii->y;
337             }
338         }
339     }
340 
341     if (lastdx != 0 || lastdy != 0) {
342         res.push_back(Pos(lastdx, lastdy));
343     }
344 
345     return res;
346 }
347 
348 /*
349  *  removeShortest - removes the shortest segment from a list of movements.
350  *
351  *  If there are several equally short segments, the first one is removed.
352  */
removeShortest(const PosList & positions)353 PosList MouseGestureRecognizer::removeShortest(const PosList &positions)
354 {
355     PosList res;
356 
357     int shortestSoFar;
358     PosList::const_iterator shortest;
359     bool firstTime = true;
360 
361     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
362         if (firstTime) {
363             shortestSoFar = ii->x * ii->x + ii->y * ii->y;
364             shortest = ii;
365 
366             firstTime = false;
367         }
368         else {
369             if ((ii->x * ii->x + ii->y * ii->y) < shortestSoFar) {
370                 shortestSoFar = ii->x * ii->x + ii->y * ii->y;
371                 shortest = ii;
372             }
373         }
374     }
375 
376     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
377         if (ii != shortest) {
378             res.push_back(*ii);
379         }
380     }
381 
382     return res;
383 }
384 
385 /*
386  *  calcLength - calculates the total length of the movements from a list of relative movements.
387  */
calcLength(const PosList & positions)388 int MouseGestureRecognizer::calcLength(const PosList &positions)
389 {
390     int res = 0;
391 
392     for (PosList::const_iterator ii = positions.begin(); ii != positions.end(); ++ii) {
393         if (ii->x > 0) {
394             res += ii->x;
395         }
396         else if (ii->x < 0) {
397             res -= ii->x;
398         }
399         else if (ii->y > 0) {
400             res += ii->y;
401         }
402         else {
403             res -= ii->y;
404         }
405     }
406 
407     return res;
408 }
409 
410