Ticket #791: mythstroke.2.cpp

File mythstroke.2.cpp, 9.3 KB (added by mfgalizi@…, 14 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 */