2 * Copyright (C) 2008 David Greaves
4 * This software is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public License
6 * as published by the Free Software Foundation; either version 2.1 of
7 * the License, or (at your option) any later version.
9 * This software is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this software; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 //#define DEBUG_SHOPPER 1
21 #include "shopper.h" // automake, i8n, gettext
22 #include "GestureReader.h"
24 // This class is used to record gesture events
26 // There are 2 main functions:
30 // This records a path of points
31 // These can be press, release and move
32 // as some gestures may have multiple strokes
36 // Based on an idea by Johan Thelin
38 // smooths/dejitters the points on entry
39 // Then creates a stroke pattern
40 // Then matches against the supplied list
42 // If this fails then the stroke pattern is simplified
45 #define STROKECODELEN 4 // length of code strings including \0 (pad short codes with \0s, not ' ')
46 // Lookup table for GestureReader::Point::Direction
47 static const char* StrokeCodes
=". \0\0u \0\0r \0\0d \0\0l \0\0ur \0dr \0dl \0ul \0";
48 // Lookup table for GestureReader::Point::pointType
49 //static const char* TouchCodes =".. \0R \0\0P \0\0* \0\0. \0\0"; // release/press
52 GestureReader::Point::Point():
59 GestureReader::Point::Point( int _x
, int _y
, int _len2
,
60 Point::Direction _dir
, Point::Type _type
) :
61 x(_x
), y(_y
), len2(_len2
), direction(_dir
), type(_type
)
64 struct GestureReader::Private
66 QList
<Point
> points
; // this is replaced during smoothing
69 int smallestStrokeLen
; // calculated during generateStrokes...
73 // Fwd declare local private helper
74 GestureReader::Point::Direction
calcDirection(int dx
, int dy
);
76 // class GestureReader : public QObject
78 GestureReader::GestureReader()
81 p
->SmoothLimit2
= 900; // 30^2 - no segments < 30 pixels long
84 GestureReader::~GestureReader()
89 ////////////////////////////////////////////////////////////////
91 void GestureReader::start(int x
, int y
)
95 p
->points
.append( Point(x
, y
, 0, Point::None
, Point::Start
) );
100 void GestureReader::addPoint(int x
, int y
, Point::Type type
)
103 int dx
= x
- p
->lastX
;
104 int dy
= y
- p
->lastY
;
105 int len2
= dx
*dx
+ dy
*dy
;
106 if (len2
< p
->SmoothLimit2
) { // ignore jitter
107 // If we jittered to an Release/Press then use the last X/Y
108 if (type
== Point::Press
|| type
== Point::Release
)
109 p
->points
.append( Point(p
->lastX
, p
->lastY
, 0, Point::None
, type
) );
113 // Ensure all movement is recorded using Point::Move and up/down is static.
114 p
->points
.append( Point(x
, y
, len2
, calcDirection( dx
, dy
), Point::Move
) );
116 // Movement registered and recorded.
120 // an Release/Press if needed
121 if (type
== Point::Press
|| type
== Point::Release
)
122 p
->points
.append( Point(x
,y
,0, Point::None
, type
) );
126 ////////////////////////////////////////////////////////////////
127 // silly helper functions
128 int abs(int Nbr
){ return (Nbr
>= 0 ? Nbr
: -Nbr
); }
132 if (Nbr
== 1) return 1;
134 int Number
= Nbr
/ 2;
136 do {lastGuess
= Number
; Number
= (Number
+ Nbr
/ Number
) / 2;
137 } while( abs(Number
- lastGuess
) > 1);
141 ////////////////////////////////////////////////////////////////
144 GestureReader::Point::Direction
calcDirection(int dx
, int dy
)
147 // Handle the 0 cases to avoid problems....
150 return GestureReader::Point::Down
;
152 return GestureReader::Point::Up
;
153 } else if (dy
== 0) {
155 return GestureReader::Point::Right
;
157 return GestureReader::Point::Left
;
160 // tan(theta) = opposite / adjacent
161 // scale by 10000 for a little accuracy - it's rough but good enough
162 int ratio
= 10000 * dx
/ -dy
;
164 // Nb using -ve dy - this means the model below is based on a
165 // traditional set of axis, X increasing to right, Y increasing
168 if ( ratio
< -22998 ) //tan -66.5 = -2.2998
170 GestureReader::Point::Right
:
171 GestureReader::Point::Left
;
173 if ( ratio
< -4142 ) //tan -22.5 = -0.4142
175 GestureReader::Point::DownRight
:
176 GestureReader::Point::UpLeft
;
178 if ( ratio
< 0 ) // axis
180 GestureReader::Point::Down
:
181 GestureReader::Point::Up
;
183 if ( ratio
< 4142 ) //tan -22.5 = -0.4142
185 GestureReader::Point::Up
:
186 GestureReader::Point::Down
;
188 if ( ratio
< 22998 ) //tan 66.5 = 2.2998
190 GestureReader::Point::UpRight
:
191 GestureReader::Point::DownLeft
;
194 GestureReader::Point::Right
:
195 GestureReader::Point::Left
;
199 Gesture
GestureReader::matchOne(QList
<Gesture
> gs
)
203 // foreach (Point o, p->points) {
204 // DEBUG("\n\nPath " << "x,y : "<<o.x<<","<<o.y<< " len2 : "<<o.len2
205 // << " dir : "<<StrokeCodes+o.direction * STROKECODELEN
206 // << " type : "<<TouchCodes+o.type * STROKECODELEN);
210 do { // until simplified
212 // This generates strokes and condenses the list at the same time.
213 myShape
= generateStrokes();
215 foreach (Gesture g
, gs
) {
216 // has to be one passed in as they are enriched with
218 DEBUG("Compare g.shape(" << g
.shape() <<") == '"<< myShape
<<"'" );
219 if (g
.shape() == myShape
) {
224 // Now remove shortest paths
227 QMutableListIterator
<Point
> *i
= new QMutableListIterator
<Point
>(p
->points
);
228 while (i
->hasNext()) {
229 if (i
->next().type
== Point::Move
) {
230 if (i
->value().len2
== p
->smallestStrokeLen
) {
232 p
->smallestStrokeLen
= 0; // reset to 0 so we only remove 1 path
234 n
++; // count undeleted paths
241 // foreach (Point o, p->points) {
242 // DEBUG("\n\nPath " << "x,y : "<<o.x<<","<<o.y<< " len2 : "<<o.len2
243 // << " dir : "<<StrokeCodes+o.direction * STROKECODELEN
244 // << " type : "<<TouchCodes+o.type * STROKECODELEN);
247 } while (n
>= 1); // simplify down to 1 stroke
252 QString
GestureReader::generateStrokes()
257 QList
<Point
> condensed
;
259 p
->smallestStrokeLen
= 999999999;
260 { // this block ensures the iterator goes out of scope and destructs before we delete the list
261 QListIterator
<Point
> o(p
->points
);
262 while (o
.hasNext()) {
264 if (oo
.type
!= Point::Move
) {
265 condensed
.append(oo
);
266 // write out the touch code
267 // Don't do multi-stroke .... yet...
268 // strokes += TouchCodes+o.value().type * STROKECODELEN;
270 // DEBUG("Condensing ... len:" << oo.len2);
272 && o
.peekNext().type
== Point::Move
273 && o
.peekNext().direction
== oo
.direction
)
275 oo
.len2
= sqr(oo
.len2
) + sqr(o
.peekNext().len2
); // join
277 // DEBUG("removing path len: " << o.peekNext().len2);
280 // DEBUG("Condensed ... len:" << oo.len2);
281 condensed
.append(oo
);
282 // write out the stroke
283 strokes
+= StrokeCodes
+oo
.direction
* STROKECODELEN
;
284 // Whilst we're here, keep an eye out for the shortest stroke
285 if (oo
.len2
&& oo
.len2
< p
->smallestStrokeLen
) p
->smallestStrokeLen
= oo
.len2
;
290 p
->points
= condensed
;
291 DEBUG("Found " << strokes
);