|
1 /* |
|
2 * Copyright (c) 2009 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 "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: Juha Kauppinen, Mika Hokkanen |
|
13 * |
|
14 * Description: Photo Browser |
|
15 * |
|
16 */ |
|
17 |
|
18 #include "debug.h" |
|
19 #include "Gesture.h" |
|
20 #include <aknnotewrappers.h> |
|
21 |
|
22 // TODO: Add flexibility to support more gesture. e.g. circle |
|
23 // TODO: Add tap range (size of allowed moving area for tap) |
|
24 // TODO: Add mode-type working. Change config by one call |
|
25 |
|
26 CGesture::CGesture(MGestureCallBack* aOwner) |
|
27 //: CTimer(CActive::EPriorityUserInput) |
|
28 : CTimer(CActive::EPriorityHigh) |
|
29 , iState(EWaiting) |
|
30 , iOwner(aOwner) |
|
31 , iFirstGesture(EGestureNone) |
|
32 , iThresholdOfTap(KDefaultThresholdOfTapPixels) |
|
33 , iThresholdOfCursor(KDefaultThresholdOfCursorPixels) |
|
34 , iStationaryTime(KDefaultStationaryTime) |
|
35 , iLongTapTime(KDefaultLongTapTime) |
|
36 , iMonitoringTime(KDefaultMonitoringTime) |
|
37 , iSafetyTime(KDefaultSafetyTime) |
|
38 { |
|
39 // No implementation required |
|
40 } |
|
41 |
|
42 CGesture::~CGesture() |
|
43 { |
|
44 Cancel(); // CTimer |
|
45 iDragPoints.Close(); |
|
46 iDragTicks.Close(); |
|
47 } |
|
48 |
|
49 CGesture* CGesture::NewLC(MGestureCallBack* aOwner) |
|
50 { |
|
51 CGesture* self = new (ELeave) CGesture(aOwner); |
|
52 CleanupStack::PushL(self); |
|
53 self->ConstructL(); |
|
54 return self; |
|
55 } |
|
56 |
|
57 CGesture* CGesture::NewL(MGestureCallBack* aOwner) |
|
58 { |
|
59 CGesture* self = CGesture::NewLC(aOwner); |
|
60 CleanupStack::Pop(); // self; |
|
61 return self; |
|
62 } |
|
63 |
|
64 void CGesture::ConstructL() |
|
65 { |
|
66 CTimer::ConstructL(); |
|
67 CActiveScheduler::Add(this); |
|
68 } |
|
69 |
|
70 EXPORT_C void CGesture::PointerEventL( const TPointerEvent& aEvent ) |
|
71 { |
|
72 #define RESETPOINTS() {iDragPoints.Reset(); iDragTicks.Reset();} |
|
73 #define RECORDPOINTS(p, t) {iDragPoints.Append(p); iDragTicks.Append(t);} |
|
74 #define SETMOVEMENT(d, c, p) {d.iX=c.iX-p.iX; d.iY=c.iY-p.iY;} |
|
75 |
|
76 TInt tick = User::NTickCount(); |
|
77 TGestureType type = EGestureNone; |
|
78 TPoint delta; |
|
79 TPoint vector; |
|
80 TInt threshold; |
|
81 |
|
82 switch( aEvent.iType ) |
|
83 { |
|
84 case TPointerEvent::EButton1Down: |
|
85 DP2_IMAGIC(_L("CGesture::PointerEventL: TPointerEvent::EButton1Down (%d, %d)"), aEvent.iPosition.iX, aEvent.iPosition.iY); |
|
86 |
|
87 if (iState == EEnded) break; // do nothing if it's in safety time |
|
88 |
|
89 // Start timer to monitor long tap |
|
90 Cancel(); // CTimer. Stop all timers. |
|
91 DP1_IMAGIC(_L("iStationaryTime=%d"), iStationaryTime); |
|
92 After( TTimeIntervalMicroSeconds32(iStationaryTime)); |
|
93 |
|
94 // Initialise pointer records and start new record |
|
95 iPointBegan = iPointLastCursor = iPointPreviousEvent = aEvent.iPosition; |
|
96 RESETPOINTS(); |
|
97 RECORDPOINTS(aEvent.iPosition, tick); |
|
98 |
|
99 if (iState == EMonitoring) |
|
100 { |
|
101 DP0_IMAGIC(_L("CGesture::PointerEventL (2nd gesture starting)")); |
|
102 // store 1st gesture type in high 16 bits if this is 2nd gesture |
|
103 iFirstGesture = iFirstGesture << 16; |
|
104 } |
|
105 else // iState should be Waiting. Resets anyway even if it's not. |
|
106 { |
|
107 DP0_IMAGIC(_L("CGesture::PointerEventL (1st gesture starting)")); |
|
108 // Set point only for 1st gesture and notify owner |
|
109 iFirstGesture = EGestureNone; |
|
110 iPointFirstBegan = aEvent.iPosition; |
|
111 iOwner->HandleGestureBeganL(aEvent.iPosition); |
|
112 } |
|
113 |
|
114 iState = EBegan; |
|
115 |
|
116 DP1_IMAGIC(_L("####### Down:iFirstGesture=%x"), iFirstGesture); |
|
117 break; |
|
118 |
|
119 case TPointerEvent::EDrag: |
|
120 DP2_IMAGIC(_L("CGesture::PointerEventL: TPointerEvent::EDrag (%d, %d)"), aEvent.iPosition.iX, aEvent.iPosition.iY); |
|
121 |
|
122 if ((iState != EBegan) && (iState != EStationary) && (iState != ETravelling)) |
|
123 break; // do nothing if it's not in the state above |
|
124 |
|
125 // record drag points and thier tick counts |
|
126 RECORDPOINTS(aEvent.iPosition, tick); |
|
127 |
|
128 SETMOVEMENT(delta, aEvent.iPosition, iPointPreviousEvent); |
|
129 |
|
130 threshold = (iState == EBegan)? iThresholdOfTap * 3: iThresholdOfTap; |
|
131 |
|
132 if (IsMovementWithinThreshold(delta, threshold)) |
|
133 { |
|
134 DP0_IMAGIC(_L("CGesture::PointerEventL (staying within threshold)")); |
|
135 } |
|
136 else |
|
137 { |
|
138 DP0_IMAGIC(_L("CGesture::PointerEventL (going beyond threshold)")); |
|
139 Cancel(); // CTimer. Stop all timers. |
|
140 iState = ETravelling; |
|
141 } |
|
142 |
|
143 if (iState == ETravelling) |
|
144 { |
|
145 DP0_IMAGIC(_L("CGesture::PointerEventL (notify drag event)")); |
|
146 iOwner->HandleGestureMovedL(delta, EGestureDrag); |
|
147 iPointPreviousEvent = aEvent.iPosition; |
|
148 } |
|
149 |
|
150 #ifdef CURSOR_SIMULATION |
|
151 type = CheckMovement(iPointLastCursor, aEvent.iPosition); |
|
152 |
|
153 if (type != EGestureNone) |
|
154 { |
|
155 iOwner->HandleGestureMovedL(aEvent.iPosition, EGestureCursor|type); |
|
156 iPointLastCursor = aEvent.iPosition; |
|
157 } |
|
158 DP5_IMAGIC(_L("iPointLastCursor(%d)"), type); |
|
159 #endif |
|
160 break; |
|
161 |
|
162 case TPointerEvent::EButton1Up: |
|
163 DP2_IMAGIC(_L("CGesture::PointerEventL: TPointerEvent::EButton1Up (%d, %d)"), aEvent.iPosition.iX, aEvent.iPosition.iY); |
|
164 |
|
165 if ((iState != EBegan) && (iState != EStationary) && (iState != ETravelling)) |
|
166 break; // do nothing if it's not in the state above |
|
167 |
|
168 Cancel(); // Stop timers |
|
169 |
|
170 iPointPreviousEvent = aEvent.iPosition; |
|
171 |
|
172 // record drag points and thier tick counts |
|
173 RECORDPOINTS(aEvent.iPosition, tick); |
|
174 |
|
175 if ((iState == EBegan) || |
|
176 ((iState == EStationary) && !IS_GESTURE_LONGTAPPING(iFirstGesture))) |
|
177 { |
|
178 DP0_IMAGIC(_L("CGesture::PointerEventL (Tap!)")); |
|
179 type = EGestureTap; |
|
180 |
|
181 // TODO: check distance of each tap if it's double tap |
|
182 iFirstGesture |= type; |
|
183 |
|
184 TInt t = (IS_GESTURE_TAPPED(iFirstGesture))? 0: iMonitoringTime; |
|
185 After(TTimeIntervalMicroSeconds32(t)); // call immediately if double tap |
|
186 iState = EMonitoring; |
|
187 } |
|
188 else if ((iState == EStationary) && IS_GESTURE_LONGTAPPING(iFirstGesture)) |
|
189 { |
|
190 DP0_IMAGIC(_L("CGesture::PointerEventL (Long tap!)")); |
|
191 type = EGestureLongTap; |
|
192 |
|
193 iFirstGesture |= type; |
|
194 After(TTimeIntervalMicroSeconds32(iMonitoringTime)); |
|
195 iState = EMonitoring; |
|
196 } |
|
197 else if (iState == ETravelling) |
|
198 { |
|
199 DP0_IMAGIC(_L("CGesture::PointerEventL (Was drag. Flick!)")); |
|
200 type = CheckFlick(KDefaultValidityTimeOfFlick, vector); |
|
201 iPointPreviousEvent = vector; |
|
202 |
|
203 iFirstGesture |= type; |
|
204 After(TTimeIntervalMicroSeconds32(0)); // call immediately |
|
205 iState = EMonitoring; |
|
206 } |
|
207 |
|
208 DP1_IMAGIC(_L("####### Up:iFirstGesture=%x"), iFirstGesture); |
|
209 break; |
|
210 |
|
211 default: |
|
212 break; |
|
213 } |
|
214 } |
|
215 |
|
216 EXPORT_C void CGesture::SetThresholdOfTap( const TInt aPixels) |
|
217 { |
|
218 iThresholdOfTap = aPixels; |
|
219 } |
|
220 |
|
221 EXPORT_C void CGesture::SetThresholdOfCursor( const TInt aPixels) |
|
222 { |
|
223 iThresholdOfCursor = aPixels; |
|
224 } |
|
225 |
|
226 EXPORT_C void CGesture::SetStationaryTime(const TInt aMicroseconds) |
|
227 { |
|
228 iStationaryTime = aMicroseconds; |
|
229 } |
|
230 |
|
231 EXPORT_C void CGesture::SetLongTapTime(const TInt aMicroSeconds) |
|
232 { |
|
233 iLongTapTime = aMicroSeconds; |
|
234 } |
|
235 |
|
236 EXPORT_C void CGesture::SetMonitoringTime(const TInt aMicroseconds) |
|
237 { |
|
238 iMonitoringTime = aMicroseconds; |
|
239 } |
|
240 |
|
241 EXPORT_C void CGesture::SetSafetyTime(const TInt aMicroseconds) |
|
242 { |
|
243 iSafetyTime = aMicroseconds; |
|
244 } |
|
245 |
|
246 TBool CGesture::IsMovementWithinThreshold(const TPoint aDelta, const TInt aThreshold) |
|
247 { |
|
248 TBool ret = ETrue; |
|
249 |
|
250 TInt diff_x = aDelta.iX; |
|
251 TInt diff_y = aDelta.iY; |
|
252 TInt abs_diff_2 = diff_x * diff_x + diff_y * diff_y; |
|
253 TInt threshold_2 = aThreshold * aThreshold; |
|
254 |
|
255 if (abs_diff_2 > threshold_2) ret = EFalse; |
|
256 |
|
257 return ret; |
|
258 } |
|
259 |
|
260 TGestureType CGesture::CheckMovement(const TPoint aPointPrevious, const TPoint aPointCurrent, const TBool aSkipThresholdCheck) |
|
261 { |
|
262 TGestureType ret = EGestureNone; |
|
263 |
|
264 TInt diff_x = aPointCurrent.iX - aPointPrevious.iX; |
|
265 TInt diff_y = aPointCurrent.iY - aPointPrevious.iY; |
|
266 |
|
267 TInt abs_diff_x = Abs(diff_x); |
|
268 TInt abs_diff_y = Abs(diff_y); |
|
269 TInt abs_diff_2 = abs_diff_x*abs_diff_x + abs_diff_y*abs_diff_y; |
|
270 |
|
271 if (abs_diff_2 > iThresholdOfCursor*iThresholdOfCursor) |
|
272 { |
|
273 // Movement is mapped to one of 8 directions |
|
274 TBool valid_x = (abs_diff_x && (abs_diff_x * 5 > abs_diff_y * 4))? ETrue: EFalse; |
|
275 TBool valid_y = (abs_diff_y && (abs_diff_y * 5 > abs_diff_x * 4))? ETrue: EFalse; |
|
276 |
|
277 if (valid_y) |
|
278 { |
|
279 if (diff_y < 0) ret |= EGestureUp; |
|
280 else ret |= EGestureDown; |
|
281 } |
|
282 if (valid_x) |
|
283 { |
|
284 if (diff_x < 0) ret |= EGestureLeft; |
|
285 else ret |= EGestureRight; |
|
286 } |
|
287 } |
|
288 |
|
289 return ret; |
|
290 } |
|
291 |
|
292 TGestureType CGesture::CheckFlick(const TInt aValidityTime, TPoint& aVector) |
|
293 { |
|
294 DP0_IMAGIC(_L("CGesture::CheckFlick++")); |
|
295 |
|
296 // TODO: need to check if counts are same in iDragPoints and iDragTicks |
|
297 TInt first, last; |
|
298 TInt validtick = aValidityTime / 1000; // convert micro sec to milli sec. |
|
299 TGestureType ret = EGestureNone; |
|
300 |
|
301 first = last = iDragPoints.Count() - 1; |
|
302 aVector.iX = aVector.iY = 0; |
|
303 |
|
304 for (TInt i=last-1;i>=0;--i) |
|
305 { |
|
306 TInt tickdiff = iDragTicks[last] - iDragTicks[i]; |
|
307 |
|
308 DP5_IMAGIC(_L("i=%d, tick=%d, tickdiff=%d (x,y)=(%d,%d)"), |
|
309 i, iDragTicks[i], tickdiff, iDragPoints[i].iX, iDragPoints[i].iY); |
|
310 |
|
311 if (tickdiff < validtick) |
|
312 { |
|
313 first = i; |
|
314 } |
|
315 else |
|
316 { |
|
317 break; |
|
318 } |
|
319 } |
|
320 |
|
321 if (first != last) |
|
322 { |
|
323 TInt tickdiff = iDragTicks[last] - iDragTicks[first]; |
|
324 ret = CheckMovement(iDragPoints[first], iDragPoints[last], ETrue); |
|
325 |
|
326 // Tick diff is 100 at minimum to avoid returning extreamly big vector. |
|
327 if (tickdiff < 100) tickdiff = 100; |
|
328 |
|
329 // Calculate the movement speed = pixels/sec |
|
330 aVector.iX = (iDragPoints[last].iX - iDragPoints[first].iX)*1000/tickdiff; |
|
331 aVector.iY = (iDragPoints[last].iY - iDragPoints[first].iY)*1000/tickdiff; |
|
332 } |
|
333 |
|
334 DP5_IMAGIC(_L("%d~%d, %d=>%d =%d"), |
|
335 first, last, iDragPoints[first].iX, iDragPoints[last].iX, |
|
336 CheckMovement(iDragPoints[first], iDragPoints[last])); |
|
337 DP2_IMAGIC(_L("vector (%d,%d)"), aVector.iX, aVector.iY); |
|
338 DP0_IMAGIC(_L("CGesture::CheckFlick--")); |
|
339 |
|
340 return ret; |
|
341 } |
|
342 |
|
343 void CGesture::RunL() |
|
344 { |
|
345 DP0_IMAGIC(_L("CGesture::RunL++")); |
|
346 |
|
347 switch (iState) |
|
348 { |
|
349 case EBegan: |
|
350 DP1_IMAGIC(_L("CGesture::RunL (EBegan -> EStationary) iLongTapTime=%d"), iLongTapTime); |
|
351 iOwner->HandleGestureMovedL(iPointPreviousEvent, EGestureStationary); |
|
352 After(TTimeIntervalMicroSeconds32(iLongTapTime)); |
|
353 iState = EStationary; |
|
354 |
|
355 // Update the position so that movement check occurs against |
|
356 // last drag event, not against initial touch position |
|
357 if (iDragPoints.Count() > 1) |
|
358 iPointPreviousEvent = iDragPoints[iDragPoints.Count()-1]; |
|
359 break; |
|
360 case EStationary: |
|
361 DP0_IMAGIC(_L("CGesture::RunL (EStationary -> EStationary)")); |
|
362 iOwner->HandleGestureMovedL(iPointPreviousEvent, EGestureLongTapping); |
|
363 // record it's possible long tap if movement stays within threshold |
|
364 iFirstGesture |= EGestureLongTapping; |
|
365 // no state change |
|
366 break; |
|
367 case EMonitoring: |
|
368 DP1_IMAGIC(_L("CGesture::RunL (EMonitoring -> EEnded) iSafetyTime=%d"), iSafetyTime); |
|
369 iOwner->HandleGestureEndedL(iPointPreviousEvent, iFirstGesture); |
|
370 After(TTimeIntervalMicroSeconds32(iSafetyTime)); |
|
371 iState = EEnded; |
|
372 break; |
|
373 case EEnded: |
|
374 DP0_IMAGIC(_L("CGesture::RunL (EEnded -> EWaiting)")); |
|
375 iState = EWaiting; |
|
376 break; |
|
377 default: |
|
378 DP0_IMAGIC(_L("CGesture::RunL (default)")); |
|
379 // do nothing |
|
380 break; |
|
381 } |
|
382 |
|
383 DP0_IMAGIC(_L("CGesture::RunL--")); |
|
384 } |