Ticket #791: mythstroke.2.cpp

File mythstroke.2.cpp, 9.3 KB (added by mfgalizi@…, 20 years ago)

Support for held gestures

Line 
1/* -*- myth -*- */
2/**
3 * @file mythstroke.cpp
4 * @author Micah F. Galizia <mfgalizi@csd.uwo.ca>
5 * @brief A C++ ripoff of the stroke library, modified for MythTV.
6 *
7 * Copyright (C) 2005 Micah Galizia
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2, or (at
12 * your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful, but
15 * WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 * 02111-1307, USA
23 *
24 * This library contains code originally obtained from the libstroke
25 * library, which was written by Mark F. Willey. If I am in offense
26 * of any copyright or anything, please let me know and I will make
27 * the appropriate fixes.
28 */
29#ifndef MYTHSTROKE_CPP
30#define MYTHSTROKE_CPP
31
32using namespace std;
33
34#include <math.h>
35
36#include <qmutex.h>
37#include <qmap.h>
38
39#include "mythstroke.h"
40
41
42/**
43 * @class MythStrokePrivate
44 * @brief Private information used only by a stroke class.
45 */
46class MythStrokePrivate {
47
48public:
49 QMutex m;
50 QMap <QString, MythGestureEvent::Gesture> sequences;
51};
52
53
54
55/* comments in header */
56MythStroke::MythStroke(size_t max_points, size_t min_points,
57 size_t max_sequence, size_t scale_ratio,
58 float bin_percent) :
59 m_recording(false), min_x(10000), max_x(-1), min_y(10000), max_y(-1),
60 max_points(max_points), min_points(min_points), max_sequence(max_sequence),
61 scale_ratio(scale_ratio), bin_percent(bin_percent)
62{
63 /* default to an invalid event */
64 last_gesture = MythGestureEvent::MaxGesture;
65
66 /* create new private information */
67 p = new MythStrokePrivate();
68
69 /* Click */
70 p->sequences.insert("5", MythGestureEvent::Click);
71
72 /* Lines */
73 p->sequences.insert("456", MythGestureEvent::Right);
74 p->sequences.insert("654", MythGestureEvent::Left);
75 p->sequences.insert("258", MythGestureEvent::Down);
76 p->sequences.insert("852", MythGestureEvent::Up);
77
78 /* Diagonals */
79 p->sequences.insert("951", MythGestureEvent::UpLeft);
80 p->sequences.insert("753", MythGestureEvent::UpRight);
81 p->sequences.insert("159", MythGestureEvent::DownRight);
82 p->sequences.insert("357", MythGestureEvent::DownLeft);
83
84 /* Double Lines*/
85 p->sequences.insert("96321",MythGestureEvent::UpThenLeft);
86 p->sequences.insert("74123",MythGestureEvent::UpThenRight);
87 p->sequences.insert("36987",MythGestureEvent::DownThenLeft);
88 p->sequences.insert("14789",MythGestureEvent::DownThenRight);
89 p->sequences.insert("32147",MythGestureEvent::LeftThenDown);
90 p->sequences.insert("98741",MythGestureEvent::LeftThenUp);
91 p->sequences.insert("12369",MythGestureEvent::RightThenDown);
92 p->sequences.insert("78963",MythGestureEvent::RightThenUp);
93
94
95
96}
97
98/* comments in header */
99void MythStroke::adjustExtremes(int x, int y)
100{
101 if (x < min_x) min_x = x;
102 if (x > max_x) max_x = x;
103 if (y < min_y) min_y = y;
104 if (y > max_y) max_y = y;
105}
106
107bool MythStroke::recording(void) const
108{
109 bool temp;
110 p->m.lock();
111 temp = m_recording;
112 p->m.unlock();
113 return temp;
114}
115
116/* comments in header */
117void MythStroke::start(void)
118{
119 this->p->m.lock();
120 this->m_recording = true;
121 this->p->m.unlock();
122}
123
124/* comments in header */
125void MythStroke::stop(void)
126{
127 p->m.lock();
128
129 if (m_recording)
130 {
131 this->m_recording = false;
132
133 /* translate before resetting maximums */
134 last_gesture = p->sequences[translate()];
135
136 min_x = min_y = 10000;
137 max_x = max_y = -1;
138 }
139
140 p->m.unlock();
141}
142
143
144MythGestureEvent *MythStroke::gesture(void) const
145{
146 return new MythGestureEvent(last_gesture);
147}
148
149
150/* comments in header */
151int determineBin (const QPoint & p, int x1, int x2, int y1, int y2)
152{
153 int bin_num = 1;
154 if (p.x() > x1) bin_num += 1;
155 if (p.x() > x2) bin_num += 1;
156 if (p.y() > y1) bin_num += 3;
157 if (p.y() > y2) bin_num += 3;
158
159 return bin_num;
160}
161
162
163
164/* comments in header */
165QString MythStroke::translate(void)
166{
167 size_t total_points = points.count();
168
169 if (total_points > max_points)
170 {
171 points.clear();
172 return "0";
173 }
174
175 /* treat any stroke with less than the minimum number of points as
176 * a click (not a drag), which is the center bin */
177 if (total_points < min_points)
178 {
179 points.clear();
180 return "5";
181 }
182
183 QString sequence;
184
185 /* number of bins recorded in the stroke */
186 size_t sequence_count = 0;
187
188 /* points-->sequence translation scratch variables */
189 int prev_bin = 0;
190 int current_bin = 0;
191 int bin_count = 0;
192
193 /*flag indicating the start of a stroke - always count it in the sequence*/
194 bool first_bin = true;
195
196 /* bin boundary and size variables */
197 int delta_x, delta_y;
198 int bound_x_1, bound_x_2;
199 int bound_y_1, bound_y_2;
200
201 /* determine size of grid */
202 delta_x = max_x - min_x;
203 delta_y = max_y - min_y;
204
205 /* calculate bin boundary positions */
206 bound_x_1 = min_x + (delta_x / 3);
207 bound_x_2 = min_x + 2 * (delta_x / 3);
208
209 bound_y_1 = min_y + (delta_y / 3);
210 bound_y_2 = min_y + 2 * (delta_y / 3);
211
212 if (delta_x > scale_ratio * delta_y)
213 {
214 bound_y_1 = (max_y + min_y - delta_x) / 2 + (delta_x / 3);
215 bound_y_2 = (max_y + min_y - delta_x) / 2 + 2 * (delta_x / 3);
216 }
217 else if (delta_y > scale_ratio * delta_x)
218 {
219 bound_x_1 = (max_x + min_x - delta_y) / 2 + (delta_y / 3);
220 bound_x_2 = (max_x + min_x - delta_y) / 2 + 2 * (delta_y / 3);
221 }
222
223 /* build string by placing points in bins, collapsing bins and
224 discarding those with too few points... */
225
226 while (!points.empty())
227 {
228
229 QPoint p = points.front();
230 points.pop_front();
231
232 /* figure out which bin the point falls in */
233 current_bin = determineBin(p, bound_x_1, bound_x_2, bound_y_1,
234 bound_y_2);
235
236 /* if this is the first point, consider it the previous bin, too. */
237 prev_bin = (prev_bin == 0) ? current_bin : prev_bin;
238
239 if (prev_bin == current_bin) bin_count++;
240 else {
241
242 /* we are moving to a new bin -- consider adding to the
243 sequence */
244 if ((bin_count > (total_points * bin_percent)) || first_bin)
245 {
246 first_bin = false;
247 sequence += '0' + prev_bin;
248 sequence_count ++;
249 }
250
251 /* restart counting points in the new bin */
252 bin_count = 0;
253 prev_bin = current_bin;
254 }
255 }
256
257 /* add the last run of points to the sequence */
258 sequence += '0' + current_bin;
259 sequence_count++;
260
261 /* bail out on error cases */
262 if (sequence_count > max_sequence) sequence = "0";
263
264 return sequence;
265}
266
267
268
269/* comments in header */
270bool MythStroke::record(const QPoint & p)
271{
272 /* only record if we haven't exceeded the maximum points */
273 if ((points.size() >= max_points) || !recording())
274 return false;
275
276
277 if (points.size() == 0)
278 {
279 points.push_back(p);
280 return true;
281 }
282
283 /* interpolate between last and current point */
284 int delx = p.x() - points.back().x();
285 int dely = p.y() - points.back().y();
286
287 /* step by the greatest delta direction */
288 if (abs(delx) > abs(dely))
289 {
290 float iy = points.back().y();
291
292 /* go from the last point to the current, whatever direction
293 * it may be */
294 for (float ix = points.back().x();
295 (delx > 0) ? (ix < p.x()) : (ix > p.x());
296 ix += (delx > 0) ? 1 : -1)
297 {
298 /* step the other axis by the correct increment */
299 iy += fabs(((float) dely / (float) delx))
300 * (float) ((dely < 0) ? -1.0 : 1.0);
301
302 points.push_back(QPoint((int)ix, (int)iy));
303
304 adjustExtremes((int)ix, (int)iy);
305 }
306 }
307 else /* same thing, but for dely larger than delx case... */
308 {
309 float ix = points.back().x();
310
311 /* go from the last point to the current, whatever direction
312 it may be */
313 for (float iy = points.back().y();
314 (dely > 0) ? (iy < p.y()) : (iy > p.y());
315 iy += (dely > 0) ? 1 : -1)
316 {
317 /* step the other axis by the correct increment */
318 ix += fabs(((float) delx / (float) dely))
319 * (float) ((delx < 0) ? -1.0 : 1.0);
320
321 /* add the interpolated point */
322 points.push_back(QPoint((int)ix, (int)iy));
323
324 adjustExtremes((int)ix, (int)iy);
325 }
326 }
327
328 points.push_back(p);
329
330 return true;
331}
332
333
334static char *gesturename[] = {
335 "Up",
336 "Down",
337 "Left",
338 "Right",
339 "UpLeft",
340 "UpRight",
341 "DownLeft",
342 "DownRight",
343 "UpThenLeft",
344 "UpThenRight",
345 "DownThenLeft",
346 "DownThenRight",
347 "LeftThenUp",
348 "LeftThenDown",
349 "RightThenUp",
350 "RightThenDown",
351 "Click",
352 "MaxGesture"
353};
354
355
356
357
358/* comments in header */
359MythGestureEvent::operator QString() const
360{
361 return gesturename[_gesture];
362}
363
364
365
366#endif /* MYTHSTROKE_CPP */