(Text Controller Impl) Moved event handling related methods into a separate struct
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-impl-event-handler.cpp
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 // CLASS HEADER
19 #include <dali-toolkit/internal/text/text-controller-impl-event-handler.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/public-api/adaptor-framework/key.h>
23 #include <dali/integration-api/debug.h>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
27
28 using namespace Dali;
29
30 namespace
31 {
32
33 #if defined(DEBUG_ENABLED)
34 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
35 #endif
36
37 } // namespace
38
39 namespace Dali
40 {
41
42 namespace Toolkit
43 {
44
45 namespace Text
46 {
47
48 void ControllerImplEventHandler::OnCursorKeyEvent(Controller::Impl& impl, const Event& event)
49 {
50   if( NULL == impl.mEventData || !impl.IsShowingRealText() )
51   {
52     // Nothing to do if there is no text input.
53     return;
54   }
55
56   int keyCode = event.p1.mInt;
57   bool isShiftModifier = event.p2.mBool;
58   EventData& eventData = *impl.mEventData;
59   ModelPtr& model = impl.mModel;
60   LogicalModelPtr& logicalModel = model->mLogicalModel;
61   VisualModelPtr& visualModel = model->mVisualModel;
62
63   CharacterIndex& primaryCursorPosition = eventData.mPrimaryCursorPosition;
64   CharacterIndex previousPrimaryCursorPosition = primaryCursorPosition;
65
66   if( Dali::DALI_KEY_CURSOR_LEFT == keyCode )
67   {
68     if( primaryCursorPosition > 0u )
69     {
70       if ( !isShiftModifier && eventData.mDecorator->IsHighlightVisible() )
71       {
72         primaryCursorPosition = std::min(eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
73       }
74       else
75       {
76         primaryCursorPosition = impl.CalculateNewCursorIndex( primaryCursorPosition - 1u );
77       }
78     }
79   }
80   else if( Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
81   {
82     if( logicalModel->mText.Count() > primaryCursorPosition )
83     {
84       if ( !isShiftModifier && eventData.mDecorator->IsHighlightVisible() )
85       {
86         primaryCursorPosition = std::max(eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
87       }
88       else
89       {
90         primaryCursorPosition = impl.CalculateNewCursorIndex( primaryCursorPosition );
91       }
92     }
93   }
94   else if( Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier )
95   {
96     // Ignore Shift-Up for text selection for now.
97
98     // Get first the line index of the current cursor position index.
99     CharacterIndex characterIndex = 0u;
100
101     if( primaryCursorPosition > 0u )
102     {
103       characterIndex = primaryCursorPosition - 1u;
104     }
105
106     const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterIndex );
107     const LineIndex previousLineIndex = ( lineIndex > 0 ? lineIndex - 1u : lineIndex );
108
109     // Retrieve the cursor position info.
110     CursorInfo cursorInfo;
111     impl.GetCursorPosition( primaryCursorPosition,
112                             cursorInfo );
113
114     // Get the line above.
115     const LineRun& line = *( visualModel->mLines.Begin() + previousLineIndex );
116
117     // Get the next hit 'y' point.
118     const float hitPointY = cursorInfo.lineOffset - 0.5f * ( line.ascender - line.descender );
119
120     // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
121     bool matchedCharacter = false;
122     primaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
123                                                          logicalModel,
124                                                          impl.mMetrics,
125                                                          eventData.mCursorHookPositionX,
126                                                          hitPointY,
127                                                          CharacterHitTest::TAP,
128                                                          matchedCharacter );
129   }
130   else if( Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier )
131   {
132     // Ignore Shift-Down for text selection for now.
133
134     // Get first the line index of the current cursor position index.
135     CharacterIndex characterIndex = 0u;
136
137     if( primaryCursorPosition > 0u )
138     {
139       characterIndex = primaryCursorPosition - 1u;
140     }
141
142     const LineIndex lineIndex = visualModel->GetLineOfCharacter( characterIndex );
143
144     if( lineIndex + 1u < visualModel->mLines.Count() )
145     {
146       // Retrieve the cursor position info.
147       CursorInfo cursorInfo;
148       impl.GetCursorPosition( primaryCursorPosition, cursorInfo );
149
150       // Get the line below.
151       const LineRun& line = *( visualModel->mLines.Begin() + lineIndex + 1u );
152
153       // Get the next hit 'y' point.
154       const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * ( line.ascender - line.descender );
155
156       // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
157       bool matchedCharacter = false;
158       primaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
159                                                            logicalModel,
160                                                            impl.mMetrics,
161                                                            eventData.mCursorHookPositionX,
162                                                            hitPointY,
163                                                            CharacterHitTest::TAP,
164                                                            matchedCharacter );
165     }
166   }
167
168   if ( !isShiftModifier && eventData.mState != EventData::SELECTING )
169   {
170     // Update selection position after moving the cursor
171     eventData.mLeftSelectionPosition = primaryCursorPosition;
172     eventData.mRightSelectionPosition = primaryCursorPosition;
173   }
174
175   if ( isShiftModifier && impl.IsShowingRealText() && eventData.mShiftSelectionFlag )
176   {
177     // Handle text selection
178     bool selecting = false;
179
180     if ( Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode )
181     {
182       // Shift-Left/Right to select the text
183       int cursorPositionDelta = primaryCursorPosition - previousPrimaryCursorPosition;
184       if ( cursorPositionDelta > 0 || eventData.mRightSelectionPosition > 0u ) // Check the boundary
185       {
186         eventData.mRightSelectionPosition += cursorPositionDelta;
187       }
188       selecting = true;
189     }
190     else if ( eventData.mLeftSelectionPosition != eventData.mRightSelectionPosition )
191     {
192       // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
193       selecting = true;
194     }
195
196     if ( selecting )
197     {
198       // Notify the cursor position to the InputMethodContext.
199       if( eventData.mInputMethodContext )
200       {
201         eventData.mInputMethodContext.SetCursorPosition( primaryCursorPosition );
202         eventData.mInputMethodContext.NotifyCursorPosition();
203       }
204
205       impl.ChangeState( EventData::SELECTING );
206
207       eventData.mUpdateLeftSelectionPosition = true;
208       eventData.mUpdateRightSelectionPosition = true;
209       eventData.mUpdateGrabHandlePosition = true;
210       eventData.mUpdateHighlightBox = true;
211
212       // Hide the text selection popup if select the text using keyboard instead of moving grab handles
213       if( eventData.mGrabHandlePopupEnabled )
214       {
215         eventData.mDecorator->SetPopupActive( false );
216       }
217     }
218   }
219   else
220   {
221     // Handle normal cursor move
222     impl.ChangeState( EventData::EDITING );
223     eventData.mUpdateCursorPosition = true;
224   }
225
226   eventData.mUpdateInputStyle = true;
227   eventData.mScrollAfterUpdatePosition = true;
228 }
229
230 void ControllerImplEventHandler::OnTapEvent(Controller::Impl& impl, const Event& event)
231 {
232   if( impl.mEventData )
233   {
234     const unsigned int tapCount = event.p1.mUint;
235     EventData& eventData = *impl.mEventData;
236     ModelPtr& model = impl.mModel;
237     LogicalModelPtr& logicalModel = model->mLogicalModel;
238     VisualModelPtr& visualModel = model->mVisualModel;
239
240     if( 1u == tapCount )
241     {
242       if( impl.IsShowingRealText() )
243       {
244         // Convert from control's coords to text's coords.
245         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
246         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
247
248         // Keep the tap 'x' position. Used to move the cursor.
249         eventData.mCursorHookPositionX = xPosition;
250
251         // Whether to touch point hits on a glyph.
252         bool matchedCharacter = false;
253         eventData.mPrimaryCursorPosition = Text::GetClosestCursorIndex( visualModel,
254                                                                         logicalModel,
255                                                                         impl.mMetrics,
256                                                                         xPosition,
257                                                                         yPosition,
258                                                                         CharacterHitTest::TAP,
259                                                                         matchedCharacter );
260
261         // When the cursor position is changing, delay cursor blinking
262         eventData.mDecorator->DelayCursorBlink();
263       }
264       else
265       {
266         eventData.mPrimaryCursorPosition = 0u;
267       }
268
269       // Update selection position after tapping
270       eventData.mLeftSelectionPosition = eventData.mPrimaryCursorPosition;
271       eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
272
273       eventData.mUpdateCursorPosition = true;
274       eventData.mUpdateGrabHandlePosition = true;
275       eventData.mScrollAfterUpdatePosition = true;
276       eventData.mUpdateInputStyle = true;
277
278       // Notify the cursor position to the InputMethodContext.
279       if( eventData.mInputMethodContext )
280       {
281         eventData.mInputMethodContext.SetCursorPosition( eventData.mPrimaryCursorPosition );
282         eventData.mInputMethodContext.NotifyCursorPosition();
283       }
284     }
285     else if( 2u == tapCount )
286     {
287       if( eventData.mSelectionEnabled )
288       {
289         // Convert from control's coords to text's coords.
290         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
291         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
292
293         // Calculates the logical position from the x,y coords.
294         impl.RepositionSelectionHandles( xPosition, yPosition, eventData.mDoubleTapAction );
295       }
296     }
297   }
298 }
299
300 void ControllerImplEventHandler::OnPanEvent(Controller::Impl& impl, const Event& event)
301 {
302   if( impl.mEventData )
303   {
304     EventData& eventData = *impl.mEventData;
305     DecoratorPtr& decorator = eventData.mDecorator;
306
307     const bool isHorizontalScrollEnabled = decorator->IsHorizontalScrollEnabled();
308     const bool isVerticalScrollEnabled = decorator->IsVerticalScrollEnabled();
309
310     if( !isHorizontalScrollEnabled && !isVerticalScrollEnabled )
311     {
312       // Nothing to do if scrolling is not enabled.
313       return;
314     }
315
316     const GestureState state = static_cast<GestureState>( event.p1.mInt );
317     switch( state )
318     {
319       case GestureState::STARTED:
320       {
321         // Will remove the cursor, handles or text's popup, ...
322         impl.ChangeState( EventData::TEXT_PANNING );
323         break;
324       }
325       case GestureState::CONTINUING:
326       {
327         ModelPtr& model = impl.mModel;
328
329         const Vector2& layoutSize = model->mVisualModel->GetLayoutSize();
330         Vector2& scrollPosition = model->mScrollPosition;
331         const Vector2 currentScroll = scrollPosition;
332
333         if( isHorizontalScrollEnabled )
334         {
335           const float displacementX = event.p2.mFloat;
336           scrollPosition.x += displacementX;
337
338           impl.ClampHorizontalScroll( layoutSize );
339         }
340
341         if( isVerticalScrollEnabled )
342         {
343           const float displacementY = event.p3.mFloat;
344           scrollPosition.y += displacementY;
345
346           impl.ClampVerticalScroll( layoutSize );
347         }
348
349         decorator->UpdatePositions( scrollPosition - currentScroll );
350         break;
351       }
352       case GestureState::FINISHED:
353       case GestureState::CANCELLED: // FALLTHROUGH
354       {
355         // Will go back to the previous state to show the cursor, handles, the text's popup, ...
356         impl.ChangeState( eventData.mPreviousState );
357         break;
358       }
359       default:
360         break;
361     }
362   }
363 }
364
365 void ControllerImplEventHandler::OnLongPressEvent(Controller::Impl& impl, const Event& event)
366 {
367   DALI_LOG_INFO( gLogFilter, Debug::General, "Controller::OnLongPressEvent\n" );
368
369   if( impl.mEventData )
370   {
371     EventData& eventData = *impl.mEventData;
372
373     if( !impl.IsShowingRealText() && ( EventData::EDITING == eventData.mState ) )
374     {
375       impl.ChangeState( EventData::EDITING_WITH_POPUP );
376       eventData.mDecoratorUpdated = true;
377       eventData.mUpdateInputStyle = true;
378     }
379     else
380     {
381       if( eventData.mSelectionEnabled )
382       {
383         ModelPtr& model = impl.mModel;
384
385         // Convert from control's coords to text's coords.
386         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
387         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
388
389         // Calculates the logical position from the x,y coords.
390         impl.RepositionSelectionHandles( xPosition, yPosition, eventData.mLongPressAction );
391       }
392     }
393   }
394 }
395
396 void ControllerImplEventHandler::OnHandleEvent(Controller::Impl& impl, const Event& event)
397 {
398   if( impl.mEventData )
399   {
400     const unsigned int state = event.p1.mUint;
401     const bool handleStopScrolling = ( HANDLE_STOP_SCROLLING == state );
402     const bool isSmoothHandlePanEnabled = impl.mEventData->mDecorator->IsSmoothHandlePanEnabled();
403
404     if( HANDLE_PRESSED == state )
405     {
406       OnHandlePressed(impl, event, isSmoothHandlePanEnabled);
407     } // end ( HANDLE_PRESSED == state )
408     else if( ( HANDLE_RELEASED == state ) ||
409              handleStopScrolling )
410     {
411       OnHandleReleased(impl, event, isSmoothHandlePanEnabled, handleStopScrolling);
412     } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
413     else if( HANDLE_SCROLLING == state )
414     {
415       OnHandleScrolling(impl, event, isSmoothHandlePanEnabled);
416     } // end ( HANDLE_SCROLLING == state )
417   }
418 }
419
420 void ControllerImplEventHandler::OnSelectEvent(Controller::Impl& impl, const Event& event )
421 {
422   if( impl.mEventData && impl.mEventData->mSelectionEnabled )
423   {
424     ModelPtr& model = impl.mModel;
425     const Vector2& scrollPosition = model->mScrollPosition;
426
427     // Convert from control's coords to text's coords.
428     const float xPosition = event.p2.mFloat - scrollPosition.x;
429     const float yPosition = event.p3.mFloat - scrollPosition.y;
430
431     // Calculates the logical position from the x,y coords.
432     impl.RepositionSelectionHandles( xPosition, yPosition, Controller::NoTextTap::HIGHLIGHT );
433   }
434 }
435
436 void ControllerImplEventHandler::OnSelectAllEvent(Controller::Impl& impl)
437 {
438   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled?"true":"false");
439
440   if( impl.mEventData )
441   {
442     EventData& eventData = *impl.mEventData;
443     if( eventData.mSelectionEnabled )
444     {
445       ModelPtr& model = impl.mModel;
446       const Vector2& scrollPosition = model->mScrollPosition;
447
448       // Calculates the logical position from the start.
449       impl.RepositionSelectionHandles( 0.f - scrollPosition.x,
450                                        0.f - scrollPosition.y,
451                                        Controller::NoTextTap::HIGHLIGHT );
452
453       eventData.mLeftSelectionPosition = 0u;
454       eventData.mRightSelectionPosition = model->mLogicalModel->mText.Count();
455     }
456   }
457 }
458
459 void ControllerImplEventHandler::OnSelectNoneEvent(Controller::Impl& impl)
460 {
461   DALI_LOG_INFO( gLogFilter, Debug::Verbose, "OnSelectNoneEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled?"true":"false");
462
463   if( impl.mEventData )
464   {
465     EventData& eventData = *impl.mEventData;
466     if( eventData.mSelectionEnabled && eventData.mState == EventData::SELECTING)
467     {
468       eventData.mPrimaryCursorPosition = 0u;
469       eventData.mLeftSelectionPosition = eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
470       impl.ChangeState( EventData::INACTIVE );
471       eventData.mUpdateCursorPosition = true;
472       eventData.mUpdateInputStyle = true;
473       eventData.mScrollAfterUpdatePosition = true;
474     }
475   }
476 }
477
478 void ControllerImplEventHandler::OnHandlePressed(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
479 {
480   ModelPtr& model = impl.mModel;
481   const Vector2& scrollPosition = model->mScrollPosition;
482
483   // Convert from decorator's coords to text's coords.
484   const float xPosition = event.p2.mFloat - scrollPosition.x;
485   const float yPosition = event.p3.mFloat - scrollPosition.y;
486
487   // Need to calculate the handle's new position.
488   bool matchedCharacter = false;
489   const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex( model->mVisualModel,
490                                                                         model->mLogicalModel,
491                                                                         impl.mMetrics,
492                                                                         xPosition,
493                                                                         yPosition,
494                                                                         CharacterHitTest::SCROLL,
495                                                                         matchedCharacter );
496
497   EventData& eventData = *impl.mEventData;
498
499   if( Event::GRAB_HANDLE_EVENT == event.type )
500   {
501     impl.ChangeState ( EventData::GRAB_HANDLE_PANNING );
502
503     if( handleNewPosition != eventData.mPrimaryCursorPosition )
504     {
505       // Updates the cursor position if the handle's new position is different than the current one.
506       eventData.mUpdateCursorPosition = true;
507       // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
508       eventData.mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
509       eventData.mPrimaryCursorPosition = handleNewPosition;
510     }
511
512     // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
513     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
514   }
515   else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
516   {
517     impl.ChangeState ( EventData::SELECTION_HANDLE_PANNING );
518
519     if( ( handleNewPosition != eventData.mLeftSelectionPosition ) &&
520         ( handleNewPosition != eventData.mRightSelectionPosition ) )
521     {
522       // Updates the highlight box if the handle's new position is different than the current one.
523       eventData.mUpdateHighlightBox = true;
524       // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
525       eventData.mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
526       eventData.mLeftSelectionPosition = handleNewPosition;
527     }
528
529     // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
530     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
531
532     // Will define the order to scroll the text to match the handle position.
533     eventData.mIsLeftHandleSelected = true;
534     eventData.mIsRightHandleSelected = false;
535   }
536   else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
537   {
538     impl.ChangeState ( EventData::SELECTION_HANDLE_PANNING );
539
540     if( ( handleNewPosition != eventData.mRightSelectionPosition ) &&
541         ( handleNewPosition != eventData.mLeftSelectionPosition ) )
542     {
543       // Updates the highlight box if the handle's new position is different than the current one.
544       eventData.mUpdateHighlightBox = true;
545       // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
546       eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
547       eventData.mRightSelectionPosition = handleNewPosition;
548     }
549
550     // Updates the decorator if the soft handle panning is enabled. It triggers a relayout in the decorator and the new position of the handle is set.
551     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
552
553     // Will define the order to scroll the text to match the handle position.
554     eventData.mIsLeftHandleSelected = false;
555     eventData.mIsRightHandleSelected = true;
556   }
557 }
558
559 void ControllerImplEventHandler::OnHandleReleased(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled, const bool handleStopScrolling)
560 {
561   CharacterIndex handlePosition = 0u;
562   if( handleStopScrolling || isSmoothHandlePanEnabled )
563   {
564     ModelPtr& model = impl.mModel;
565     const Vector2& scrollPosition = model->mScrollPosition;
566
567     // Convert from decorator's coords to text's coords.
568     const float xPosition = event.p2.mFloat - scrollPosition.x;
569     const float yPosition = event.p3.mFloat - scrollPosition.y;
570
571     bool matchedCharacter = false;
572     handlePosition = Text::GetClosestCursorIndex( model->mVisualModel,
573                                                   model->mLogicalModel,
574                                                   impl.mMetrics,
575                                                   xPosition,
576                                                   yPosition,
577                                                   CharacterHitTest::SCROLL,
578                                                   matchedCharacter );
579   }
580
581   EventData& eventData = *impl.mEventData;
582
583   if( Event::GRAB_HANDLE_EVENT == event.type )
584   {
585     eventData.mUpdateCursorPosition = true;
586     eventData.mUpdateGrabHandlePosition = true;
587     eventData.mUpdateInputStyle = true;
588
589     if( !impl.IsClipboardEmpty() )
590     {
591       impl.ChangeState( EventData::EDITING_WITH_PASTE_POPUP ); // Moving grabhandle will show Paste Popup
592     }
593
594     if( handleStopScrolling || isSmoothHandlePanEnabled )
595     {
596       eventData.mScrollAfterUpdatePosition = true;
597       eventData.mPrimaryCursorPosition = handlePosition;
598     }
599   }
600   else if( Event::LEFT_SELECTION_HANDLE_EVENT == event.type )
601   {
602     impl.ChangeState( EventData::SELECTING );
603
604     eventData.mUpdateHighlightBox = true;
605     eventData.mUpdateLeftSelectionPosition = true;
606     eventData.mUpdateRightSelectionPosition = true;
607
608     if( handleStopScrolling || isSmoothHandlePanEnabled )
609     {
610       eventData.mScrollAfterUpdatePosition = true;
611
612       if( ( handlePosition != eventData.mRightSelectionPosition ) &&
613           ( handlePosition != eventData.mLeftSelectionPosition ) )
614       {
615         eventData.mLeftSelectionPosition = handlePosition;
616       }
617     }
618   }
619   else if( Event::RIGHT_SELECTION_HANDLE_EVENT == event.type )
620   {
621     impl.ChangeState( EventData::SELECTING );
622
623     eventData.mUpdateHighlightBox = true;
624     eventData.mUpdateRightSelectionPosition = true;
625     eventData.mUpdateLeftSelectionPosition = true;
626
627     if( handleStopScrolling || isSmoothHandlePanEnabled )
628     {
629       eventData.mScrollAfterUpdatePosition = true;
630       if( ( handlePosition != eventData.mRightSelectionPosition ) &&
631           ( handlePosition != eventData.mLeftSelectionPosition ) )
632       {
633         eventData.mRightSelectionPosition = handlePosition;
634       }
635     }
636   }
637
638   eventData.mDecoratorUpdated = true;
639 }
640
641 void ControllerImplEventHandler::OnHandleScrolling(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
642 {
643   ModelPtr& model = impl.mModel;
644   Vector2& scrollPosition = model->mScrollPosition;
645   VisualModelPtr& visualModel = model->mVisualModel;
646
647   const float xSpeed = event.p2.mFloat;
648   const float ySpeed = event.p3.mFloat;
649   const Vector2& layoutSize = visualModel->GetLayoutSize();
650   const Vector2 currentScrollPosition = scrollPosition;
651
652   scrollPosition.x += xSpeed;
653   scrollPosition.y += ySpeed;
654
655   impl.ClampHorizontalScroll( layoutSize );
656   impl.ClampVerticalScroll( layoutSize );
657
658   EventData& eventData = *impl.mEventData;
659   DecoratorPtr& decorator = eventData.mDecorator;
660
661   bool endOfScroll = false;
662   if( Vector2::ZERO == ( currentScrollPosition - scrollPosition ) )
663   {
664     // Notify the decorator there is no more text to scroll.
665     // The decorator won't send more scroll events.
666     decorator->NotifyEndOfScroll();
667     // Still need to set the position of the handle.
668     endOfScroll = true;
669   }
670
671   // Set the position of the handle.
672   const bool scrollRightDirection = xSpeed > 0.f;
673   const bool scrollBottomDirection = ySpeed > 0.f;
674   const bool leftSelectionHandleEvent = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
675   const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
676
677   if( Event::GRAB_HANDLE_EVENT == event.type )
678   {
679     impl.ChangeState( EventData::GRAB_HANDLE_PANNING );
680
681     // Get the grab handle position in decorator coords.
682     Vector2 position = decorator->GetPosition( GRAB_HANDLE );
683
684     if( decorator->IsHorizontalScrollEnabled() )
685     {
686       // Position the grag handle close to either the left or right edge.
687       position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
688     }
689
690     if( decorator->IsVerticalScrollEnabled() )
691     {
692       position.x = eventData.mCursorHookPositionX;
693
694       // Position the grag handle close to either the top or bottom edge.
695       position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
696     }
697
698     // Get the new handle position.
699     // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
700     bool matchedCharacter = false;
701     const CharacterIndex handlePosition = Text::GetClosestCursorIndex( visualModel,
702                                                                        impl.mModel->mLogicalModel,
703                                                                        impl.mMetrics,
704                                                                        position.x - scrollPosition.x,
705                                                                        position.y - scrollPosition.y,
706                                                                        CharacterHitTest::SCROLL,
707                                                                        matchedCharacter );
708
709     if( eventData.mPrimaryCursorPosition != handlePosition )
710     {
711       eventData.mUpdateCursorPosition = true;
712       eventData.mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
713       eventData.mScrollAfterUpdatePosition = true;
714       eventData.mPrimaryCursorPosition = handlePosition;
715     }
716     eventData.mUpdateInputStyle = eventData.mUpdateCursorPosition;
717
718     // Updates the decorator if the soft handle panning is enabled.
719     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
720   }
721   else if( leftSelectionHandleEvent || rightSelectionHandleEvent )
722   {
723     impl.ChangeState( EventData::SELECTION_HANDLE_PANNING );
724
725     // Get the selection handle position in decorator coords.
726     Vector2 position = decorator->GetPosition( leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE );
727
728     if( decorator->IsHorizontalScrollEnabled() )
729     {
730       // Position the selection handle close to either the left or right edge.
731       position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
732     }
733
734     if( decorator->IsVerticalScrollEnabled() )
735     {
736       position.x = eventData.mCursorHookPositionX;
737
738       // Position the grag handle close to either the top or bottom edge.
739       position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
740     }
741
742     // Get the new handle position.
743     // The selection handle's position is in decorator's coords. Need to transform to text's coords.
744     bool matchedCharacter = false;
745     const CharacterIndex handlePosition = Text::GetClosestCursorIndex( visualModel,
746                                                                        impl.mModel->mLogicalModel,
747                                                                        impl.mMetrics,
748                                                                        position.x - scrollPosition.x,
749                                                                        position.y - scrollPosition.y,
750                                                                        CharacterHitTest::SCROLL,
751                                                                        matchedCharacter );
752
753     if( leftSelectionHandleEvent )
754     {
755       const bool differentHandles = ( eventData.mLeftSelectionPosition != handlePosition ) && ( eventData.mRightSelectionPosition != handlePosition );
756
757       if( differentHandles || endOfScroll )
758       {
759         eventData.mUpdateHighlightBox = true;
760         eventData.mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
761         eventData.mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
762         eventData.mLeftSelectionPosition = handlePosition;
763       }
764     }
765     else
766     {
767       const bool differentHandles = ( eventData.mRightSelectionPosition != handlePosition ) && ( eventData.mLeftSelectionPosition != handlePosition );
768       if( differentHandles || endOfScroll )
769       {
770         eventData.mUpdateHighlightBox = true;
771         eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
772         eventData.mUpdateLeftSelectionPosition = isSmoothHandlePanEnabled;
773         eventData.mRightSelectionPosition = handlePosition;
774       }
775     }
776
777     if( eventData.mUpdateLeftSelectionPosition || eventData.mUpdateRightSelectionPosition )
778     {
779       impl.RepositionSelectionHandles();
780
781       eventData.mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
782     }
783   }
784   eventData.mDecoratorUpdated = true;
785 }
786
787 } // namespace Text
788
789 } // namespace Toolkit
790
791 } // namespace Dali