|
1 /* |
|
2 * Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of the License "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: Gesture helper implementation |
|
15 * |
|
16 */ |
|
17 #include <e32math.h> |
|
18 #include "pinchgesturerecogniser.h" |
|
19 #include "GenericSimpleGesture.h" |
|
20 #include <rt_uievent.h> |
|
21 #include "filelogger.h" |
|
22 #include "statemachine.h" // for stmUiEventEngine::Distance(dx,dy) |
|
23 |
|
24 using namespace stmGesture ; |
|
25 |
|
26 extern long Mm2Pixels(long mm) ; |
|
27 |
|
28 |
|
29 CPinchGestureRecogniser::CPinchGestureRecogniser(MGestureListener* aListener) : |
|
30 CGestureRecogniser(aListener) |
|
31 { |
|
32 m_pinching = false ; |
|
33 m_pinchingspeed = 3.5 ; // by default something suitable for capacitive |
|
34 m_holdseen = false ; |
|
35 } |
|
36 |
|
37 CPinchGestureRecogniser* CPinchGestureRecogniser::NewL(MGestureListener* aListener) |
|
38 { |
|
39 CPinchGestureRecogniser* self = new (ELeave) CPinchGestureRecogniser(aListener) ; |
|
40 return self; |
|
41 } |
|
42 |
|
43 CPinchGestureRecogniser::~CPinchGestureRecogniser() |
|
44 { |
|
45 } |
|
46 |
|
47 TGestureRecognitionState CPinchGestureRecogniser::recognise(int numOfActiveStreams, |
|
48 MGestureEngineIf* pge) |
|
49 { |
|
50 TGestureRecognitionState state = ENotMyGesture; |
|
51 // Check if we are enabled or not |
|
52 if (!m_gestureEnabled) return state ; |
|
53 |
|
54 if (m_loggingenabled) |
|
55 { |
|
56 LOGARG("CPinchGestureRecogniser: %d %d %d ", m_pinching, m_holdseen, numOfActiveStreams) ; |
|
57 } |
|
58 #if !defined(ADVANCED_POINTER_EVENTS) |
|
59 // Look at the events to see if it looks like pinch in single touch |
|
60 // WARNING: this code is a hack : in single touch capacitive touch device (like Alvin with 52.50) it works so-and-so, |
|
61 // because the pointer events were reported from the corners of the rectangle formed by two fingers pressing. |
|
62 // In resistive touch device like Tube or Ivalo the reported points are somewhere int he middle between the fingers |
|
63 // and jumping a lot, so it is very difficult to get it right. |
|
64 if (numOfActiveStreams == 1) |
|
65 { |
|
66 // Then look at the event stream, first we need to see a hold and then a fast jump |
|
67 const stmUiEventEngine::MUiEvent* puie = pge->getUiEvents(0); |
|
68 int countOfEvents = puie->countOfEvents() ; |
|
69 stmUiEventEngine::TUiEventCode eventCode = puie->Code() ; |
|
70 |
|
71 if (countOfEvents > 0 ) // how many events |
|
72 { |
|
73 if (m_loggingenabled) |
|
74 { |
|
75 LOGARG("CPinchGestureRecogniser: %d %d %d %d %d, m: %d b: %d", |
|
76 m_pinching, m_holdseen, numOfActiveStreams, countOfEvents, eventCode, |
|
77 int(m_m), int(m_b)) ; |
|
78 } |
|
79 |
|
80 if (m_pinching) |
|
81 { |
|
82 // We have entered pinching state, lets move one of the points unless it is a release |
|
83 if (eventCode == stmUiEventEngine::ERelease) |
|
84 { |
|
85 m_pinching = false ; |
|
86 m_holdseen = false ; |
|
87 } |
|
88 else |
|
89 { |
|
90 bool pointIgnored = true ; // for logging purposes |
|
91 int currentLength = m_loggingenabled ? |
|
92 stmUiEventEngine::Distance(m_pinchstart, m_pinchend) : 0; |
|
93 |
|
94 TPoint oStart(m_pinchstart) ; |
|
95 TPoint oEnd(m_pinchend) ; |
|
96 int difference = 0 ; |
|
97 state = ELockToThisGesture ; |
|
98 const TPoint& tp = puie->CurrentXY(); |
|
99 // calculate the distance of the new point from the stored vector |
|
100 int d1 = ((m_pinchstart.iX-tp.iX)*(m_pinchstart.iX-tp.iX)) + |
|
101 ((m_pinchstart.iY-tp.iY)*(m_pinchstart.iY-tp.iY)) ; |
|
102 int d2 = ((m_pinchend.iX-tp.iX)*(m_pinchend.iX-tp.iX)) + |
|
103 ((m_pinchend.iY-tp.iY)*(m_pinchend.iY-tp.iY)) ; |
|
104 // check also if the Y coordinate happens to be near the hold point, |
|
105 // this seems to be the case at least with alvin, we keep getting two points, |
|
106 // where one is near the Y coordinate of the hold point |
|
107 int diffY = Abs(tp.iY-m_pinchstart.iY) ; |
|
108 |
|
109 if (d1 < d2 || diffY < 12) |
|
110 { |
|
111 // the detected point is near the first point, |
|
112 // or the detected point is about on the same horizontal line with the hold point |
|
113 // do not do anything, but keep the gesture |
|
114 } |
|
115 else |
|
116 { |
|
117 pointIgnored = false ; |
|
118 // the detected point is close to the other end, then adjust the stored vector |
|
119 int xd = m_pinchend.iX-tp.iX ; |
|
120 int yd = m_pinchend.iY-tp.iY ; |
|
121 if (xd < 0 ) xd = - xd ; |
|
122 if (yd < 0 ) yd = - yd ; |
|
123 // look which coordinate is closer to the original and use that |
|
124 if (xd < yd) |
|
125 { |
|
126 // calculate new point based on the X value |
|
127 m_pinchend.iX = tp.iX ; |
|
128 m_pinchend.iY = m_m*m_pinchend.iX + m_b ; |
|
129 if (m_pinchend.iY < 0) m_pinchend.iY = 0 ; |
|
130 } |
|
131 else |
|
132 { |
|
133 if (m_m != 0) |
|
134 { |
|
135 m_pinchend.iY = tp.iY ; |
|
136 m_pinchend.iX = (m_pinchend.iY - m_b)/m_m ; |
|
137 if (m_pinchend.iX <0 ) m_pinchend.iX = 0 ; |
|
138 } |
|
139 else |
|
140 { |
|
141 m_pinchend.iX = tp.iX ; |
|
142 m_pinchend.iY = m_m*m_pinchend.iX + m_b ; |
|
143 if (m_pinchend.iY < 0) m_pinchend.iY = 0 ; |
|
144 } |
|
145 } |
|
146 float newd = calculateDistance() ; |
|
147 // check if the difference is too big and adjust accordingly |
|
148 // the method also updates the m_ddistance |
|
149 difference = adjustPinchMove(m_ddistance, newd) ; |
|
150 // Now we have a pinch gesture with size as details |
|
151 stmGesture::TTwoPointGesture pgest(KUid, m_pinchstart, m_pinchend); |
|
152 pgest.setLogging(m_loggingenabled); |
|
153 pgest.setDetails(difference) ; |
|
154 // inform the listener |
|
155 m_listener->gestureEnter(pgest); |
|
156 } |
|
157 if (m_loggingenabled) |
|
158 { |
|
159 int newLength = stmUiEventEngine::Distance(m_pinchstart, m_pinchend); |
|
160 float speedX = puie->speedX() ; |
|
161 float speedY = puie->speedY() ; |
|
162 |
|
163 LOGARG("CPinchGestureRecogniser: %d: o: %d, n: %d, d: %d (%d,%d) " \ |
|
164 "speed %f (%d,%d : %d,%d) (from: (%d,%d : %d,%d) (m: %f b: %f)", |
|
165 pointIgnored, |
|
166 currentLength, newLength, difference, |
|
167 tp.iX, tp.iY, double(speedX), |
|
168 m_pinchstart.iX, m_pinchstart.iY, m_pinchend.iX, m_pinchend.iY, |
|
169 oStart.iX, oStart.iY, oEnd.iX, oEnd.iY, |
|
170 double(m_m), double(m_b)) ; |
|
171 |
|
172 } |
|
173 |
|
174 } |
|
175 } |
|
176 else if (eventCode == stmUiEventEngine::EMove) // The last one is move and we were not pinching |
|
177 { |
|
178 if (m_loggingenabled) |
|
179 { |
|
180 LOGARG("CPinchGestureRecogniser: %d: num %d code %d", m_pinching, countOfEvents, eventCode); |
|
181 } |
|
182 stmUiEventEngine::MUiEvent* puieFirst = puie->previousEvent(); |
|
183 |
|
184 // check if we have seen hold |
|
185 if (m_holdseen) |
|
186 { |
|
187 const TPoint& tp1 = puie->CurrentXY() ; |
|
188 float speedX = puie->speedX() ; |
|
189 float speedY = puie->speedY() ; |
|
190 if (m_loggingenabled) |
|
191 { |
|
192 LOGARG("CPinchGestureRecogniser: tp1: %d %d hold %d %d, speed %f", |
|
193 tp1.iX, tp1.iY, |
|
194 m_holdseenAtPos.iX, m_holdseenAtPos.iY, double(speedX) ); |
|
195 } |
|
196 // is the speed extremely high so that it looks like other finger pressing in different location? |
|
197 if ( (speedX > m_pinchingspeed) || (speedY > m_pinchingspeed) ) |
|
198 { |
|
199 TInt64 tstamp = puie->timestamp() ; |
|
200 TTime now(tstamp) ; |
|
201 TTimeIntervalMicroSeconds tim = now.MicroSecondsFrom(m_holdseenAtTime) ; |
|
202 m_pinching = true; |
|
203 m_pinchstart = m_holdseenAtPos; |
|
204 m_pinchend = tp1; |
|
205 calculateZoomingLine(); |
|
206 m_ddistance = calculateDistance(); |
|
207 state = ELockToThisGesture ; // NOTE: once pinch is started, it will stay until release |
|
208 // create the first pich gesture which does not yet resize anything |
|
209 stmGesture::TTwoPointGesture pgest(KUid, m_pinchstart, m_pinchend); |
|
210 pgest.setLogging(m_loggingenabled); |
|
211 pgest.setDetails(0) ; |
|
212 // inform the listener |
|
213 m_listener->gestureEnter(pgest); |
|
214 } |
|
215 } |
|
216 } |
|
217 } |
|
218 if (!m_pinching) |
|
219 { |
|
220 if (m_loggingenabled) |
|
221 { |
|
222 LOGARG("CPinchGestureRecogniser: not pinching %d", puie); |
|
223 } |
|
224 if (puie && puie->Code() == stmUiEventEngine::EHold) // The last one is hold and we were not pinching |
|
225 { |
|
226 m_holdseen = true; |
|
227 m_holdseenAtPos = puie->CurrentXY(); |
|
228 m_holdseenAtTime = puie->timestamp() ; |
|
229 if (m_loggingenabled) |
|
230 { |
|
231 LOGARG("CPinchGestureRecogniser: hold seen at(%d, %d) at %Ld", |
|
232 m_holdseenAtPos.iX, m_holdseenAtPos.iY, m_holdseenAtTime.Int64()); |
|
233 } |
|
234 } |
|
235 } |
|
236 if (puie && puie->Code() == stmUiEventEngine::ETouch) // The last one is touch |
|
237 { |
|
238 m_holdseen = false; |
|
239 } |
|
240 else if (puie && puie->Code() == stmUiEventEngine::ERelease) // The last one is release |
|
241 { |
|
242 m_holdseen = false; |
|
243 } |
|
244 } |
|
245 #else |
|
246 // This is the multi touch case: two event streams needs to be there; this is the real pinch zoom |
|
247 if (numOfActiveStreams == 2) |
|
248 { |
|
249 const stmUiEventEngine::MUiEvent* puie1 = pge->getUiEvents(0); |
|
250 const stmUiEventEngine::MUiEvent* puie2 = pge->getUiEvents(1); |
|
251 stmUiEventEngine::TUiEventCode eventCode1 = puie1->Code() ; |
|
252 stmUiEventEngine::TUiEventCode eventCode2 = puie2->Code() ; |
|
253 |
|
254 if (m_loggingenabled) |
|
255 { |
|
256 TPoint p1 = puie1->CurrentXY() ; |
|
257 TPoint p2 = puie2->CurrentXY() ; |
|
258 LOGARG("CPinchGestureRecogniser: two streams: %s at [%d,%d], %s at [%d,%d]", |
|
259 stmUiEventEngine::EventName(eventCode1), p1.iX, p1.iY, |
|
260 stmUiEventEngine::EventName(eventCode1), p2.iX, p2.iY |
|
261 ) ; |
|
262 |
|
263 } |
|
264 |
|
265 |
|
266 if (!m_pinching) |
|
267 { |
|
268 // This means we start pinching, the events can be any combination of ETouch, EMove, EHold |
|
269 if ( ( eventCode1 == stmUiEventEngine::ETouch || |
|
270 eventCode1 == stmUiEventEngine::EMove || |
|
271 eventCode1 == stmUiEventEngine::EHold |
|
272 ) && |
|
273 ( eventCode2 == stmUiEventEngine::ETouch || |
|
274 eventCode2 == stmUiEventEngine::EMove || |
|
275 eventCode2 == stmUiEventEngine::EHold ) |
|
276 ) |
|
277 { |
|
278 // This is valid pinching start |
|
279 m_pinching = true ; |
|
280 // get the start and end position for the picnhing vector |
|
281 m_pinchstart = puie1->CurrentXY() ; |
|
282 m_pinchend = puie2->CurrentXY() ; |
|
283 calculateZoomingLine(); |
|
284 m_ddistance = calculateDistance(); |
|
285 state = ELockToThisGesture ; // NOTE: once pich is started, it will stay until release |
|
286 if (m_loggingenabled) |
|
287 { |
|
288 LOGARG("CPinchGestureRecogniser: pinch start: [%d,%d][%d,%d]", |
|
289 m_pinchstart.iX, m_pinchstart.iY, m_pinchend.iX, m_pinchend.iY) ; |
|
290 |
|
291 } |
|
292 // create the first pich gesture which does not yet resize anything |
|
293 stmGesture::TTwoPointGesture pgest(KUid, m_pinchstart, m_pinchend); |
|
294 pgest.setLogging(m_loggingenabled); |
|
295 pgest.setDetails(0) ; |
|
296 // inform the listener |
|
297 m_listener->gestureEnter(pgest); |
|
298 } |
|
299 else |
|
300 { |
|
301 // Not a valid pinching start, do nothing (maybe it were easier to just check if one of the events is ERelease) |
|
302 } |
|
303 } |
|
304 else |
|
305 { |
|
306 // We have entered pinching state, lets move one of the points unless it is a release |
|
307 if (eventCode1 == stmUiEventEngine::ERelease || eventCode2 == stmUiEventEngine::ERelease) |
|
308 { |
|
309 release(pge); |
|
310 } |
|
311 else |
|
312 { |
|
313 state = ELockToThisGesture ; |
|
314 |
|
315 // get the start and end position for the picnhing vector |
|
316 m_pinchstart = puie1->CurrentXY() ; |
|
317 m_pinchend = puie2->CurrentXY() ; |
|
318 float newd = calculateDistance() ; |
|
319 // check if the difference is too big and adjust accordingly |
|
320 // the method also updates the m_ddistance |
|
321 int difference = adjustPinchMove(m_ddistance, newd) ; |
|
322 // Now we have a pinch gesture with size |
|
323 if (m_loggingenabled) |
|
324 { |
|
325 LOGARG("CPinchGestureRecogniser: pinch: [%d,%d][%d,%d], diff %d", |
|
326 m_pinchstart.iX, m_pinchstart.iY, m_pinchend.iX, m_pinchend.iY, difference) ; |
|
327 |
|
328 } |
|
329 |
|
330 stmGesture::TTwoPointGesture pgest(KUid, m_pinchstart, m_pinchend); |
|
331 pgest.setLogging(m_loggingenabled); |
|
332 pgest.setDetails(difference) ; |
|
333 // inform the listener |
|
334 m_listener->gestureEnter(pgest); |
|
335 } |
|
336 } |
|
337 |
|
338 } |
|
339 #endif |
|
340 |
|
341 if (state == ENotMyGesture) |
|
342 { |
|
343 if (m_loggingenabled) |
|
344 { |
|
345 LOGARG("CPinchGestureRecogniser: NotMyGesture %d %d %d ", |
|
346 m_pinching, m_holdseen, numOfActiveStreams) ; |
|
347 } |
|
348 // if it was not our gesture, then the state can not be pinching... |
|
349 m_pinching = false ; |
|
350 } |
|
351 return state; |
|
352 } |
|
353 |
|
354 void CPinchGestureRecogniser::release(MGestureEngineIf* /*ge*/) |
|
355 { |
|
356 m_pinching = false ; |
|
357 m_listener->gestureExit(KUid) ; |
|
358 } |
|
359 |
|
360 /*! |
|
361 * Now that we know the two points where the zooming started, we move those points only along |
|
362 * the same line y = mx + b, so lets calculate m and b. |
|
363 */ |
|
364 void CPinchGestureRecogniser::calculateZoomingLine() |
|
365 { |
|
366 int sX = m_pinchstart.iX ; |
|
367 int sY = m_pinchstart.iY ; |
|
368 int eX = m_pinchend.iX ; |
|
369 int eY = m_pinchend.iY ; |
|
370 |
|
371 if (eX == sX) |
|
372 { |
|
373 m_m = 0.f ; |
|
374 } |
|
375 else |
|
376 { |
|
377 m_m = float(eY-sY)/(eX-sX) ; |
|
378 } |
|
379 m_b = sY-(m_m*sX) ; |
|
380 } |
|
381 |
|
382 /*! |
|
383 * calculate the distance, return as float |
|
384 */ |
|
385 float CPinchGestureRecogniser::calculateDistance() |
|
386 { |
|
387 double x = ((m_pinchstart.iX-m_pinchend.iX)*(m_pinchstart.iX-m_pinchend.iX))+ |
|
388 ((m_pinchstart.iY-m_pinchend.iY)*(m_pinchstart.iY-m_pinchend.iY)) ; |
|
389 double ddist ; |
|
390 Math::Sqrt(ddist, x) ; |
|
391 return float(ddist) ; |
|
392 } |
|
393 |
|
394 /*! |
|
395 * Set the pinching speed as pixels / ms (meaning that in case of singletouch device |
|
396 * the other finger looks like the EMove UI event suddenly jumps to new location; |
|
397 * in resistive the new location is somewhere in the middle of the touches, in capacitive |
|
398 * the driver seems to report three or four points: |
|
399 * original (x,y), new (a,b) and also (a,y), sometimes (x,b) |
|
400 */ |
|
401 void CPinchGestureRecogniser::setPinchingSpeed(float aSpeed) __SOFTFP |
|
402 { |
|
403 m_pinchingspeed = aSpeed ; |
|
404 } |
|
405 |
|
406 /*! |
|
407 * Adjust the pinch move so that it will not be too jumpy |
|
408 */ |
|
409 int CPinchGestureRecogniser::adjustPinchMove(float& aPreviousDistance, float aNewDistance) |
|
410 { |
|
411 float diff = aNewDistance - aPreviousDistance ; |
|
412 float logdiff = diff ; |
|
413 if (diff < 0) diff = -diff ; // Note that the next calculations need the positive diff value, but keep the original in logdiff |
|
414 float changePercentage = (diff/aPreviousDistance)*100.f ; |
|
415 if (changePercentage > 10.f) |
|
416 { |
|
417 // change more than 10%, make at most 10% |
|
418 float newdiff = aPreviousDistance*0.1f ; |
|
419 if (aPreviousDistance > aNewDistance) newdiff = -newdiff ; |
|
420 if (m_loggingenabled) |
|
421 { |
|
422 LOGARG("CPinchGestureRecogniser: adjustPinchMove from %f to %f : was, now %f %f", |
|
423 double(logdiff), double(newdiff), double(aPreviousDistance), double(aNewDistance)); |
|
424 } |
|
425 |
|
426 aPreviousDistance = aPreviousDistance + newdiff ; |
|
427 diff = newdiff ; |
|
428 } |
|
429 else |
|
430 { |
|
431 if (m_loggingenabled) |
|
432 { |
|
433 LOGARG("CPinchGestureRecogniser: adjustPinchMove from %f to %f : was, now %f %f", |
|
434 double(logdiff), double(diff), double(aPreviousDistance), double(aNewDistance)); |
|
435 } |
|
436 aPreviousDistance = aNewDistance ; // accept the new value and update the new length |
|
437 diff = logdiff ; // put the original back (this is why the logdiff can not be Abs(diff)! |
|
438 } |
|
439 return (int)diff ; |
|
440 } |