|
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 "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 |
|
18 // System includes |
|
19 #include <e32base.h> |
|
20 #include <w32std.h> |
|
21 |
|
22 // User includes |
|
23 #include "xngesturehelper.h" |
|
24 #include "xngesture.h" |
|
25 #include "xngesturedefs.h" |
|
26 #include "xnnode.h" |
|
27 |
|
28 using namespace XnGestureHelper; |
|
29 |
|
30 namespace XnGestureHelper |
|
31 { |
|
32 NONSHARABLE_CLASS( CHoldingTimer ) : public CTimer |
|
33 { |
|
34 public: |
|
35 /** Two-phase constructor */ |
|
36 static CHoldingTimer* NewL( CXnGestureHelper& aHelper ) |
|
37 { |
|
38 CHoldingTimer* self = new ( ELeave ) CHoldingTimer( aHelper ); |
|
39 CleanupStack::PushL( self ); |
|
40 self->ConstructL(); |
|
41 // "hold event" sending is enabled by default |
|
42 self->iIsEnabled = ETrue; |
|
43 CActiveScheduler::Add( self ); |
|
44 CleanupStack::Pop( self ); |
|
45 return self; |
|
46 } |
|
47 |
|
48 /** Destructor */ |
|
49 ~CHoldingTimer() |
|
50 { |
|
51 Cancel(); |
|
52 } |
|
53 |
|
54 /** Set whether sending holding events is currently enabled */ |
|
55 void SetEnabled( TBool aEnabled ) |
|
56 { |
|
57 iIsEnabled = aEnabled; |
|
58 // cancel in case hold timer is already running |
|
59 Cancel(); |
|
60 } |
|
61 |
|
62 /** @return whether sending holding events is currently enabled */ |
|
63 TBool IsEnabled() const |
|
64 { |
|
65 return iIsEnabled; |
|
66 } |
|
67 |
|
68 /** Start the timer. Calls CXnGestureHelper::StartHoldingL upon |
|
69 * completion */ |
|
70 void Start() |
|
71 { |
|
72 // if sending hold events is disabled, do not ever start the hold |
|
73 // timer, and hence hold events will never be triggered |
|
74 if ( iIsEnabled ) |
|
75 { |
|
76 Cancel(); |
|
77 After( KHoldDuration ); |
|
78 } |
|
79 } |
|
80 |
|
81 private: |
|
82 /** Constructor */ |
|
83 CHoldingTimer( CXnGestureHelper& aHelper ) |
|
84 : // give higher priority to new pointer events with - 1 |
|
85 CTimer( EPriorityUserInput - 1 ), |
|
86 iHelper( aHelper ) |
|
87 { |
|
88 } |
|
89 |
|
90 void RunL() // From CActive |
|
91 { |
|
92 iHelper.StartHoldingL(); |
|
93 } |
|
94 |
|
95 private: |
|
96 /// helper object that will be called back when timer is triggered |
|
97 CXnGestureHelper& iHelper; |
|
98 /// whether sending holding events is currently enabled |
|
99 TBool iIsEnabled; |
|
100 }; |
|
101 } // namespace GestureHelper |
|
102 |
|
103 /** |
|
104 * @return position from event. Use this instead of using aEvent direction to |
|
105 * avoid accidentally using TPointerEvent::iPosition |
|
106 */ |
|
107 inline TPoint Position( const TPointerEvent& aEvent ) |
|
108 { |
|
109 // use parent position, since the capturer is using full screen area, |
|
110 // and because the (Alfred) drag events are not local to visual even when |
|
111 // coming from the client |
|
112 return aEvent.iParentPosition; |
|
113 } |
|
114 |
|
115 // ---------------------------------------------------------------------------- |
|
116 // Two-phase constructor |
|
117 // ---------------------------------------------------------------------------- |
|
118 // |
|
119 CXnGestureHelper* CXnGestureHelper::NewL( CXnNode& aNode ) |
|
120 { |
|
121 CXnGestureHelper* self = new ( ELeave ) CXnGestureHelper( aNode ); |
|
122 CleanupStack::PushL( self ); |
|
123 self->iHoldingTimer = CHoldingTimer::NewL( *self ); |
|
124 self->iGesture = new ( ELeave ) CXnGesture(); |
|
125 CleanupStack::Pop( self ); |
|
126 return self; |
|
127 } |
|
128 |
|
129 // ---------------------------------------------------------------------------- |
|
130 // Constructor |
|
131 // ---------------------------------------------------------------------------- |
|
132 // |
|
133 CXnGestureHelper::CXnGestureHelper( CXnNode& aNode ) |
|
134 : iOwner( aNode ) |
|
135 { |
|
136 } |
|
137 |
|
138 // ---------------------------------------------------------------------------- |
|
139 // Destructor |
|
140 // ---------------------------------------------------------------------------- |
|
141 // |
|
142 CXnGestureHelper::~CXnGestureHelper() |
|
143 { |
|
144 delete iHoldingTimer; |
|
145 delete iGesture; |
|
146 } |
|
147 |
|
148 // ---------------------------------------------------------------------------- |
|
149 // SetHoldingEnabled |
|
150 // ---------------------------------------------------------------------------- |
|
151 // |
|
152 void CXnGestureHelper::SetHoldingEnabled( TBool aEnabled ) |
|
153 { |
|
154 iHoldingTimer->SetEnabled( aEnabled ); |
|
155 } |
|
156 |
|
157 // ---------------------------------------------------------------------------- |
|
158 // IsHoldingEnabled |
|
159 // ---------------------------------------------------------------------------- |
|
160 // |
|
161 TBool CXnGestureHelper::IsHoldingEnabled() const |
|
162 { |
|
163 return iHoldingTimer->IsEnabled(); |
|
164 } |
|
165 |
|
166 // ---------------------------------------------------------------------------- |
|
167 // Reset state |
|
168 // ---------------------------------------------------------------------------- |
|
169 // |
|
170 void CXnGestureHelper::Reset() |
|
171 { |
|
172 iHoldingTimer->Cancel(); |
|
173 iGesture->Reset(); |
|
174 } |
|
175 |
|
176 /** |
|
177 * Helper function that calls Reset on the pointer to CXnGestureHelper |
|
178 */ |
|
179 static void ResetHelper( TAny* aHelper ) |
|
180 { |
|
181 static_cast< CXnGestureHelper* >( aHelper )->Reset(); |
|
182 } |
|
183 |
|
184 // ---------------------------------------------------------------------------- |
|
185 // Sets gesture destination |
|
186 // ---------------------------------------------------------------------------- |
|
187 // |
|
188 void CXnGestureHelper::SetDestination( CXnNode* aDestination ) |
|
189 { |
|
190 iDestination = aDestination; |
|
191 } |
|
192 |
|
193 // ---------------------------------------------------------------------------- |
|
194 // Gets gesture destination |
|
195 // ---------------------------------------------------------------------------- |
|
196 // |
|
197 CXnNode* CXnGestureHelper::Destination() const |
|
198 { |
|
199 return iDestination; |
|
200 } |
|
201 |
|
202 // ---------------------------------------------------------------------------- |
|
203 // Gets gesture owner |
|
204 // ---------------------------------------------------------------------------- |
|
205 // |
|
206 CXnNode* CXnGestureHelper::Owner() const |
|
207 { |
|
208 return &iOwner; |
|
209 } |
|
210 |
|
211 // ---------------------------------------------------------------------------- |
|
212 // Handle a pointer event |
|
213 // ---------------------------------------------------------------------------- |
|
214 // |
|
215 TSwipeResult CXnGestureHelper::HandlePointerEventL( const TPointerEvent& aEvent ) |
|
216 { |
|
217 TSwipeResult ret = ESwipeNone; |
|
218 switch ( aEvent.iType ) |
|
219 { |
|
220 case TPointerEvent::EButton1Down: |
|
221 // If no up event was received during previous gesture, cancel |
|
222 // previous event and reset state |
|
223 if ( !IsIdle() ) |
|
224 { |
|
225 iGesture->SetCancelled(); |
|
226 // ambiguous what is the right thing when "cancel" event leaves |
|
227 // and "start" does not. Leaving for cancel *after* "start" could |
|
228 // be unexpected to client, as client would have handled start |
|
229 // event successfully. Assume that leaving upon cancellation |
|
230 // can be ignored. |
|
231 Reset(); |
|
232 } |
|
233 // adding the first point implicitly makes the state "not idle" |
|
234 AddPointL( aEvent ); |
|
235 // If AddPointL leaves, IsIdle will return EFalse for other events |
|
236 // types, hence further pointer events will be ignored. |
|
237 // Therefore, holding will NOT be started if AddPointL leaves, |
|
238 // since the callback would trigger a gesture callback, and that |
|
239 // would access an empty points array. |
|
240 iHoldingTimer->Start(); |
|
241 break; |
|
242 |
|
243 case TPointerEvent::EDrag: |
|
244 // ignore the event in case not in "recording" state. this may |
|
245 // happen if holding was triggered, or client sends up event after |
|
246 // down event was received in a different *client* state, and |
|
247 // client did not forward the down event to here. |
|
248 // Also, while stylus down, the same event is received repeatedly |
|
249 // even if stylus does not move. Filter out by checking if point |
|
250 // is the same as the latest point |
|
251 if ( !IsIdle() && !iGesture->IsLatestPoint( Position( aEvent ) ) ) |
|
252 { |
|
253 AddPointL( aEvent ); |
|
254 if ( !( iGesture->IsHolding() || |
|
255 iGesture->IsNearHoldingPoint( Position( aEvent ) ) ) ) |
|
256 { |
|
257 // restart hold timer, since pointer has moved |
|
258 iHoldingTimer->Start(); |
|
259 // Remember the point in which holding was started |
|
260 iGesture->SetHoldingPoint(); |
|
261 } |
|
262 } |
|
263 break; |
|
264 |
|
265 case TPointerEvent::EButton1Up: |
|
266 // ignore up event if no down event received |
|
267 if ( !IsIdle() ) |
|
268 { |
|
269 // reset in case the down event is not received for a reason |
|
270 // in client, and instead drag or up events are received. |
|
271 // reset via cleanup stack to ensure Reset is run even if |
|
272 // observer leaves |
|
273 CleanupStack::PushL( TCleanupItem( &ResetHelper, this ) ); |
|
274 iGesture->SetComplete(); |
|
275 // if adding of the point fails, notify client with a |
|
276 // cancelled event. It would be wrong to send another |
|
277 // gesture code when the up point is not known |
|
278 if ( AddPoint( aEvent ) != KErrNone ) |
|
279 { |
|
280 iGesture->SetCancelled(); |
|
281 } |
|
282 else |
|
283 { |
|
284 // send gesture code if holding has not been started |
|
285 if ( !iGesture->IsHolding() ) |
|
286 { |
|
287 // if client leaves, the state is automatically reset. |
|
288 // In this case the client will not get the released event |
|
289 ret = ValidSwipe(); |
|
290 } |
|
291 // send an event that stylus was lifted |
|
292 iGesture->SetReleased(); |
|
293 } |
|
294 // reset state |
|
295 CleanupStack::PopAndDestroy( this ); |
|
296 } |
|
297 break; |
|
298 |
|
299 default: |
|
300 break; |
|
301 } |
|
302 return ret; |
|
303 } |
|
304 |
|
305 // ---------------------------------------------------------------------------- |
|
306 // Is the helper idle? |
|
307 // inline ok in cpp file for a private member function |
|
308 // ---------------------------------------------------------------------------- |
|
309 // |
|
310 inline TBool CXnGestureHelper::IsIdle() const |
|
311 { |
|
312 return iGesture->IsEmpty(); |
|
313 } |
|
314 |
|
315 // ---------------------------------------------------------------------------- |
|
316 // Add a point to the sequence of points that together make up the gesture |
|
317 // inline ok in cpp file for a private member function |
|
318 // ---------------------------------------------------------------------------- |
|
319 // |
|
320 inline void CXnGestureHelper::AddPointL( const TPointerEvent& aEvent ) |
|
321 { |
|
322 User::LeaveIfError( AddPoint( aEvent ) ); |
|
323 } |
|
324 |
|
325 // ---------------------------------------------------------------------------- |
|
326 // Add a point to the sequence of points that together make up the gesture |
|
327 // inline ok in cpp file for a private member function |
|
328 // ---------------------------------------------------------------------------- |
|
329 // |
|
330 inline TInt CXnGestureHelper::AddPoint( const TPointerEvent& aEvent ) |
|
331 { |
|
332 return iGesture->AddPoint( Position ( aEvent ) ); |
|
333 } |
|
334 |
|
335 /** |
|
336 * Helper function that calls ContinueHolding on the pointer to TGesture |
|
337 */ |
|
338 static void ContinueHolding( TAny* aGesture ) |
|
339 { |
|
340 static_cast< CXnGesture* >( aGesture )->ContinueHolding(); |
|
341 } |
|
342 |
|
343 // ---------------------------------------------------------------------------- |
|
344 // Add a point to the sequence of points that together make up the gesture |
|
345 // ---------------------------------------------------------------------------- |
|
346 // |
|
347 void CXnGestureHelper::StartHoldingL() |
|
348 { |
|
349 // hold & tap event is specifically filtered out. Use case: in list fast |
|
350 // scrolling activation (e.g. enhanced coverflow), tap & hold should not |
|
351 // start fast scroll. In addition, after long tap on start position, |
|
352 // drag and drag & hold swiping should emit normal swipe and swipe&hold |
|
353 // events. Therefore, tap & hold is not supported. |
|
354 if ( !iGesture->IsTap() ) |
|
355 { |
|
356 // holding has just started, and gesture code should be provided to client. |
|
357 // set gesture state so that it produces a gesture code (other than drag) |
|
358 iGesture->StartHolding(); |
|
359 |
|
360 // create an item in the cleanup stack that will set the gesture state |
|
361 // to holding-was-started-earlier state. NotifyL may leave, but the |
|
362 // holding-was-started-earlier state must still be successfully set, |
|
363 // otherwise, the holding gesture code will be sent twice |
|
364 CleanupStack::PushL( TCleanupItem( &ContinueHolding, iGesture ) ); |
|
365 |
|
366 // set holding state to "post holding" |
|
367 CleanupStack::PopAndDestroy( iGesture ); |
|
368 } |
|
369 } |
|
370 |
|
371 // ---------------------------------------------------------------------------- |
|
372 // Check if swipe is valid |
|
373 // ---------------------------------------------------------------------------- |
|
374 // |
|
375 TSwipeResult CXnGestureHelper::ValidSwipe() |
|
376 { |
|
377 TSwipeResult ret = ESwipeNone; |
|
378 TBool validSwipe(ETrue); |
|
379 |
|
380 // check if swipe is between defined values |
|
381 TInt distanceX = Abs( iGesture->Distance().iX ); |
|
382 TInt speedX = Abs( static_cast< TInt >( iGesture->Speed().iX ) ); |
|
383 |
|
384 TInt minLength( iOwner.MarginRect().Width() / 2 ); |
|
385 |
|
386 TInt dy( Abs( iGesture->StartPos().iY - iGesture->CurrentPos().iY ) ); |
|
387 |
|
388 if ( distanceX < minLength ) |
|
389 { |
|
390 validSwipe = EFalse; |
|
391 } |
|
392 |
|
393 if ( speedX < KGestureMinSpeedX ) |
|
394 { |
|
395 validSwipe = EFalse; |
|
396 } |
|
397 |
|
398 if ( dy > KGestureMaxDeltaY ) |
|
399 { |
|
400 validSwipe = EFalse; |
|
401 } |
|
402 |
|
403 // check the direction of swipe |
|
404 if ( validSwipe ) |
|
405 { |
|
406 switch ( iGesture->Code( CXnGesture::EAxisHorizontal ) ) |
|
407 { |
|
408 case EGestureSwipeLeft: |
|
409 ret = ESwipeLeft; |
|
410 break; |
|
411 case EGestureSwipeRight: |
|
412 ret = ESwipeRight; |
|
413 break; |
|
414 default: // fall through |
|
415 break; |
|
416 } |
|
417 } |
|
418 |
|
419 return ret; |
|
420 } |