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 TXnGestureCode CXnGestureHelper::HandlePointerEventL( |
|
216 const TPointerEvent& aEvent ) |
|
217 { |
|
218 TXnGestureCode ret( EGestureUnknown ); |
|
219 |
|
220 switch ( aEvent.iType ) |
|
221 { |
|
222 case TPointerEvent::EButton1Down: |
|
223 // If no up event was received during previous gesture, cancel |
|
224 // previous event and reset state |
|
225 if ( !IsIdle() ) |
|
226 { |
|
227 iGesture->SetCancelled(); |
|
228 // ambiguous what is the right thing when "cancel" event leaves |
|
229 // and "start" does not. Leaving for cancel *after* "start" could |
|
230 // be unexpected to client, as client would have handled start |
|
231 // event successfully. Assume that leaving upon cancellation |
|
232 // can be ignored. |
|
233 Reset(); |
|
234 } |
|
235 // adding the first point implicitly makes the state "not idle" |
|
236 AddPointL( aEvent ); |
|
237 // If AddPointL leaves, IsIdle will return EFalse for other events |
|
238 // types, hence further pointer events will be ignored. |
|
239 // Therefore, holding will NOT be started if AddPointL leaves, |
|
240 // since the callback would trigger a gesture callback, and that |
|
241 // would access an empty points array. |
|
242 iHoldingTimer->Start(); |
|
243 iDirection = EGestureUnknown; |
|
244 break; |
|
245 |
|
246 case TPointerEvent::EDrag: |
|
247 // ignore the event in case not in "recording" state. this may |
|
248 // happen if holding was triggered, or client sends up event after |
|
249 // down event was received in a different *client* state, and |
|
250 // client did not forward the down event to here. |
|
251 // Also, while stylus down, the same event is received repeatedly |
|
252 // even if stylus does not move. Filter out by checking if point |
|
253 // is the same as the latest point |
|
254 if ( iDirection != EGestureCanceled ) |
|
255 { |
|
256 if ( iDirection == EGestureUnknown ) |
|
257 { |
|
258 // check current direction |
|
259 iDirection = iGesture->CodeFromPoints( CXnGesture::EAxisBoth ); |
|
260 } |
|
261 else |
|
262 { |
|
263 // check if direction has changed |
|
264 if ( iDirection != iGesture->LastDirection( CXnGesture::EAxisBoth ) ) |
|
265 { |
|
266 // cancel swipe |
|
267 iDirection = EGestureCanceled; |
|
268 } |
|
269 } |
|
270 |
|
271 if ( !IsIdle() && !iGesture->IsLatestPoint( Position( aEvent ) ) ) |
|
272 { |
|
273 AddPointL( aEvent ); |
|
274 if ( !( iGesture->IsHolding() || |
|
275 iGesture->IsNearHoldingPoint( Position( aEvent ) ) ) ) |
|
276 { |
|
277 // restart hold timer, since pointer has moved |
|
278 iHoldingTimer->Start(); |
|
279 // Remember the point in which holding was started |
|
280 iGesture->SetHoldingPoint(); |
|
281 } |
|
282 } |
|
283 } |
|
284 break; |
|
285 |
|
286 case TPointerEvent::EButton1Up: |
|
287 // ignore up event if no down event received |
|
288 if ( !IsIdle() ) |
|
289 { |
|
290 // reset in case the down event is not received for a reason |
|
291 // in client, and instead drag or up events are received. |
|
292 // reset via cleanup stack to ensure Reset is run even if |
|
293 // observer leaves |
|
294 CleanupStack::PushL( TCleanupItem( &ResetHelper, this ) ); |
|
295 iGesture->SetComplete(); |
|
296 if ( iDirection != EGestureCanceled ) |
|
297 { |
|
298 // if adding of the point fails, notify client with a |
|
299 // cancelled event. It would be wrong to send another |
|
300 // gesture code when the up point is not known |
|
301 if ( AddPoint( aEvent ) != KErrNone ) |
|
302 { |
|
303 iGesture->SetCancelled(); |
|
304 } |
|
305 else |
|
306 { |
|
307 // send gesture code if holding has not been started |
|
308 if ( !iGesture->IsHolding() ) |
|
309 { |
|
310 // if client leaves, the state is automatically reset. |
|
311 // In this case the client will not get the released event |
|
312 ret = iDirection; |
|
313 } |
|
314 // send an event that stylus was lifted |
|
315 iGesture->SetReleased(); |
|
316 } |
|
317 } |
|
318 // reset state |
|
319 CleanupStack::PopAndDestroy( this ); |
|
320 } |
|
321 break; |
|
322 |
|
323 default: |
|
324 break; |
|
325 } |
|
326 |
|
327 return ret; |
|
328 } |
|
329 |
|
330 // ---------------------------------------------------------------------------- |
|
331 // Is the helper idle? |
|
332 // inline ok in cpp file for a private member function |
|
333 // ---------------------------------------------------------------------------- |
|
334 // |
|
335 inline TBool CXnGestureHelper::IsIdle() const |
|
336 { |
|
337 return iGesture->IsEmpty(); |
|
338 } |
|
339 |
|
340 // ---------------------------------------------------------------------------- |
|
341 // Add a point to the sequence of points that together make up the gesture |
|
342 // inline ok in cpp file for a private member function |
|
343 // ---------------------------------------------------------------------------- |
|
344 // |
|
345 inline void CXnGestureHelper::AddPointL( const TPointerEvent& aEvent ) |
|
346 { |
|
347 User::LeaveIfError( AddPoint( aEvent ) ); |
|
348 } |
|
349 |
|
350 // ---------------------------------------------------------------------------- |
|
351 // Add a point to the sequence of points that together make up the gesture |
|
352 // inline ok in cpp file for a private member function |
|
353 // ---------------------------------------------------------------------------- |
|
354 // |
|
355 inline TInt CXnGestureHelper::AddPoint( const TPointerEvent& aEvent ) |
|
356 { |
|
357 return iGesture->AddPoint( Position ( aEvent ) ); |
|
358 } |
|
359 |
|
360 /** |
|
361 * Helper function that calls ContinueHolding on the pointer to TGesture |
|
362 */ |
|
363 static void ContinueHolding( TAny* aGesture ) |
|
364 { |
|
365 static_cast< CXnGesture* >( aGesture )->ContinueHolding(); |
|
366 } |
|
367 |
|
368 // ---------------------------------------------------------------------------- |
|
369 // Add a point to the sequence of points that together make up the gesture |
|
370 // ---------------------------------------------------------------------------- |
|
371 // |
|
372 void CXnGestureHelper::StartHoldingL() |
|
373 { |
|
374 // hold & tap event is specifically filtered out. Use case: in list fast |
|
375 // scrolling activation (e.g. enhanced coverflow), tap & hold should not |
|
376 // start fast scroll. In addition, after long tap on start position, |
|
377 // drag and drag & hold swiping should emit normal swipe and swipe&hold |
|
378 // events. Therefore, tap & hold is not supported. |
|
379 if ( !iGesture->IsTap() ) |
|
380 { |
|
381 // holding has just started, and gesture code should be provided to client. |
|
382 // set gesture state so that it produces a gesture code (other than drag) |
|
383 iGesture->StartHolding(); |
|
384 |
|
385 // create an item in the cleanup stack that will set the gesture state |
|
386 // to holding-was-started-earlier state. NotifyL may leave, but the |
|
387 // holding-was-started-earlier state must still be successfully set, |
|
388 // otherwise, the holding gesture code will be sent twice |
|
389 CleanupStack::PushL( TCleanupItem( &ContinueHolding, iGesture ) ); |
|
390 |
|
391 // set holding state to "post holding" |
|
392 CleanupStack::PopAndDestroy( iGesture ); |
|
393 } |
|
394 } |
|
395 |
|
396 // ---------------------------------------------------------------------------- |
|
397 // Check if swipe is valid |
|
398 // ---------------------------------------------------------------------------- |
|
399 // |
|
400 TXnGestureCode CXnGestureHelper::ValidSwipe() const |
|
401 { |
|
402 return iGesture->Code( CXnGesture::EAxisBoth ); |
|
403 } |
|