Merge "CanvasView: Change Process to be called as PostProcessor" into devel/master
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / text-controller-impl-event-handler.cpp
1 /*
2  * Copyright (c) 2021 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/integration-api/debug.h>
23 #include <dali/public-api/adaptor-framework/key.h>
24
25 // INTERNAL INCLUDES
26 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
27 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
28
29 using namespace Dali;
30
31 namespace
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 namespace Toolkit
42 {
43 namespace Text
44 {
45 bool ControllerImplEventHandler::ProcessInputEvents(Controller::Impl& impl)
46 {
47   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "-->Controller::ProcessInputEvents\n");
48
49   EventData*& eventData = impl.mEventData;
50   if(NULL == eventData)
51   {
52     // Nothing to do if there is no text input.
53     DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents no event data\n");
54     return false;
55   }
56
57   unsigned int oldPos = eventData->mPrimaryCursorPosition;
58
59   if(eventData->mDecorator)
60   {
61     for(std::vector<Event>::iterator iter = eventData->mEventQueue.begin();
62         iter != eventData->mEventQueue.end();
63         ++iter)
64     {
65       switch(iter->type)
66       {
67         case Event::CURSOR_KEY_EVENT:
68         {
69           OnCursorKeyEvent(impl, *iter);
70           break;
71         }
72         case Event::TAP_EVENT:
73         {
74           OnTapEvent(impl, *iter);
75           break;
76         }
77         case Event::LONG_PRESS_EVENT:
78         {
79           OnLongPressEvent(impl, *iter);
80           break;
81         }
82         case Event::PAN_EVENT:
83         {
84           OnPanEvent(impl, *iter);
85           break;
86         }
87         case Event::GRAB_HANDLE_EVENT:
88         case Event::LEFT_SELECTION_HANDLE_EVENT:
89         case Event::RIGHT_SELECTION_HANDLE_EVENT: // Fall through
90         {
91           OnHandleEvent(impl, *iter);
92           break;
93         }
94         case Event::SELECT:
95         {
96           OnSelectEvent(impl, *iter);
97           break;
98         }
99         case Event::SELECT_ALL:
100         {
101           OnSelectAllEvent(impl);
102           break;
103         }
104         case Event::SELECT_NONE:
105         {
106           OnSelectNoneEvent(impl);
107           break;
108         }
109         case Event::SELECT_RANGE:
110         {
111           OnSelectRangeEvent(impl, *iter);
112           break;
113         }
114       }
115     }
116   }
117
118   if(eventData->mUpdateCursorPosition ||
119      eventData->mUpdateHighlightBox)
120   {
121     impl.NotifyInputMethodContext();
122   }
123
124   // The cursor must also be repositioned after inserts into the model
125   if(eventData->mUpdateCursorPosition)
126   {
127     // Updates the cursor position and scrolls the text to make it visible.
128     CursorInfo cursorInfo;
129
130     // Calculate the cursor position from the new cursor index.
131     impl.GetCursorPosition(eventData->mPrimaryCursorPosition, cursorInfo);
132
133     //only emit the event if the cursor is moved in current function.
134     if(nullptr != impl.mEditableControlInterface && eventData->mEventQueue.size() > 0)
135     {
136       impl.mEditableControlInterface->CursorPositionChanged(oldPos, eventData->mPrimaryCursorPosition);
137     }
138
139     if(eventData->mUpdateCursorHookPosition)
140     {
141       // Update the cursor hook position. Used to move the cursor with the keys 'up' and 'down'.
142       eventData->mCursorHookPositionX      = cursorInfo.primaryPosition.x;
143       eventData->mUpdateCursorHookPosition = false;
144     }
145
146     // Scroll first the text after delete ...
147     if(eventData->mScrollAfterDelete)
148     {
149       impl.ScrollTextToMatchCursor(cursorInfo);
150     }
151
152     // ... then, text can be scrolled to make the cursor visible.
153     if(eventData->mScrollAfterUpdatePosition)
154     {
155       const Vector2 currentCursorPosition(cursorInfo.primaryPosition.x, cursorInfo.lineOffset);
156       impl.ScrollToMakePositionVisible(currentCursorPosition, cursorInfo.lineHeight);
157     }
158     eventData->mScrollAfterUpdatePosition = false;
159     eventData->mScrollAfterDelete         = false;
160
161     impl.UpdateCursorPosition(cursorInfo);
162
163     eventData->mDecoratorUpdated         = true;
164     eventData->mUpdateCursorPosition     = false;
165     eventData->mUpdateGrabHandlePosition = false;
166   }
167
168   if(eventData->mUpdateHighlightBox ||
169      eventData->mUpdateLeftSelectionPosition ||
170      eventData->mUpdateRightSelectionPosition)
171   {
172     CursorInfo leftHandleInfo;
173     CursorInfo rightHandleInfo;
174
175     if(eventData->mUpdateHighlightBox)
176     {
177       impl.GetCursorPosition(eventData->mLeftSelectionPosition, leftHandleInfo);
178
179       impl.GetCursorPosition(eventData->mRightSelectionPosition, rightHandleInfo);
180
181       if(eventData->mScrollAfterUpdatePosition && (eventData->mIsLeftHandleSelected ? eventData->mUpdateLeftSelectionPosition : eventData->mUpdateRightSelectionPosition))
182       {
183         if(eventData->mIsLeftHandleSelected && eventData->mIsRightHandleSelected)
184         {
185           CursorInfo& infoLeft = leftHandleInfo;
186
187           const Vector2 currentCursorPositionLeft(infoLeft.primaryPosition.x, infoLeft.lineOffset);
188           impl.ScrollToMakePositionVisible(currentCursorPositionLeft, infoLeft.lineHeight);
189
190           CursorInfo& infoRight = rightHandleInfo;
191
192           const Vector2 currentCursorPositionRight(infoRight.primaryPosition.x, infoRight.lineOffset);
193           impl.ScrollToMakePositionVisible(currentCursorPositionRight, infoRight.lineHeight);
194         }
195         else
196         {
197           CursorInfo& info = eventData->mIsLeftHandleSelected ? leftHandleInfo : rightHandleInfo;
198
199           const Vector2 currentCursorPosition(info.primaryPosition.x, info.lineOffset);
200           impl.ScrollToMakePositionVisible(currentCursorPosition, info.lineHeight);
201         }
202       }
203     }
204
205     if(eventData->mUpdateLeftSelectionPosition)
206     {
207       impl.UpdateSelectionHandle(LEFT_SELECTION_HANDLE, leftHandleInfo);
208
209       impl.SetPopupButtons();
210       eventData->mDecoratorUpdated            = true;
211       eventData->mUpdateLeftSelectionPosition = false;
212     }
213
214     if(eventData->mUpdateRightSelectionPosition)
215     {
216       impl.UpdateSelectionHandle(RIGHT_SELECTION_HANDLE, rightHandleInfo);
217
218       impl.SetPopupButtons();
219       eventData->mDecoratorUpdated             = true;
220       eventData->mUpdateRightSelectionPosition = false;
221     }
222
223     if(eventData->mUpdateHighlightBox)
224     {
225       impl.RepositionSelectionHandles();
226
227       eventData->mUpdateLeftSelectionPosition  = false;
228       eventData->mUpdateRightSelectionPosition = false;
229       eventData->mUpdateHighlightBox           = false;
230       eventData->mIsLeftHandleSelected         = false;
231       eventData->mIsRightHandleSelected        = false;
232     }
233
234     eventData->mScrollAfterUpdatePosition = false;
235   }
236
237   if(eventData->mUpdateInputStyle)
238   {
239     // Keep a copy of the current input style.
240     InputStyle currentInputStyle;
241     currentInputStyle.Copy(eventData->mInputStyle);
242
243     // Set the default style first.
244     impl.RetrieveDefaultInputStyle(eventData->mInputStyle);
245
246     // Get the character index from the cursor index.
247     const CharacterIndex styleIndex = (eventData->mPrimaryCursorPosition > 0u) ? eventData->mPrimaryCursorPosition - 1u : 0u;
248
249     // Retrieve the style from the style runs stored in the logical model.
250     impl.mModel->mLogicalModel->RetrieveStyle(styleIndex, eventData->mInputStyle);
251
252     // Compare if the input style has changed.
253     const bool hasInputStyleChanged = !currentInputStyle.Equal(eventData->mInputStyle);
254
255     if(hasInputStyleChanged)
256     {
257       const InputStyle::Mask styleChangedMask = currentInputStyle.GetInputStyleChangeMask(eventData->mInputStyle);
258       // Queue the input style changed signal.
259       eventData->mInputStyleChangedQueue.PushBack(styleChangedMask);
260     }
261
262     eventData->mUpdateInputStyle = false;
263   }
264
265   eventData->mEventQueue.clear();
266
267   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "<--Controller::ProcessInputEvents\n");
268
269   const bool decoratorUpdated  = eventData->mDecoratorUpdated;
270   eventData->mDecoratorUpdated = false;
271
272   return decoratorUpdated;
273 }
274
275 void ControllerImplEventHandler::OnCursorKeyEvent(Controller::Impl& impl, const Event& event)
276 {
277   if(NULL == impl.mEventData || !impl.IsShowingRealText())
278   {
279     // Nothing to do if there is no text input.
280     return;
281   }
282
283   int              keyCode         = event.p1.mInt;
284   bool             isShiftModifier = event.p2.mBool;
285   EventData&       eventData       = *impl.mEventData;
286   ModelPtr&        model           = impl.mModel;
287   LogicalModelPtr& logicalModel    = model->mLogicalModel;
288   VisualModelPtr&  visualModel     = model->mVisualModel;
289
290   CharacterIndex& primaryCursorPosition         = eventData.mPrimaryCursorPosition;
291   CharacterIndex  previousPrimaryCursorPosition = primaryCursorPosition;
292
293   if(Dali::DALI_KEY_CURSOR_LEFT == keyCode)
294   {
295     if(primaryCursorPosition > 0u)
296     {
297       if(!isShiftModifier && eventData.mDecorator->IsHighlightVisible())
298       {
299         primaryCursorPosition = std::min(eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
300       }
301       else
302       {
303         primaryCursorPosition = impl.CalculateNewCursorIndex(primaryCursorPosition - 1u);
304       }
305     }
306   }
307   else if(Dali::DALI_KEY_CURSOR_RIGHT == keyCode)
308   {
309     if(logicalModel->mText.Count() > primaryCursorPosition)
310     {
311       if(!isShiftModifier && eventData.mDecorator->IsHighlightVisible())
312       {
313         primaryCursorPosition = std::max(eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
314       }
315       else
316       {
317         primaryCursorPosition = impl.CalculateNewCursorIndex(primaryCursorPosition);
318       }
319     }
320   }
321   else if(Dali::DALI_KEY_CURSOR_UP == keyCode && !isShiftModifier)
322   {
323     // Ignore Shift-Up for text selection for now.
324
325     // Get first the line index of the current cursor position index.
326     CharacterIndex characterIndex = 0u;
327
328     if(primaryCursorPosition > 0u)
329     {
330       characterIndex = primaryCursorPosition - 1u;
331     }
332
333     const LineIndex lineIndex         = visualModel->GetLineOfCharacter(characterIndex);
334     const LineIndex previousLineIndex = (lineIndex > 0 ? lineIndex - 1u : lineIndex);
335
336     // Retrieve the cursor position info.
337     CursorInfo cursorInfo;
338     impl.GetCursorPosition(primaryCursorPosition,
339                            cursorInfo);
340
341     // Get the line above.
342     const LineRun& line = *(visualModel->mLines.Begin() + previousLineIndex);
343
344     // Get the next hit 'y' point.
345     const float hitPointY = cursorInfo.lineOffset - 0.5f * GetLineHeight(line);
346
347     // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
348     bool matchedCharacter = false;
349     primaryCursorPosition = Text::GetClosestCursorIndex(visualModel,
350                                                         logicalModel,
351                                                         impl.mMetrics,
352                                                         eventData.mCursorHookPositionX,
353                                                         hitPointY,
354                                                         CharacterHitTest::TAP,
355                                                         matchedCharacter);
356   }
357   else if(Dali::DALI_KEY_CURSOR_DOWN == keyCode && !isShiftModifier)
358   {
359     // Ignore Shift-Down for text selection for now.
360
361     // Get first the line index of the current cursor position index.
362     CharacterIndex characterIndex = 0u;
363
364     if(primaryCursorPosition > 0u)
365     {
366       characterIndex = primaryCursorPosition - 1u;
367     }
368
369     const LineIndex lineIndex = visualModel->GetLineOfCharacter(characterIndex);
370
371     if(lineIndex + 1u < visualModel->mLines.Count())
372     {
373       // Retrieve the cursor position info.
374       CursorInfo cursorInfo;
375       impl.GetCursorPosition(primaryCursorPosition, cursorInfo);
376
377       // Get the line below.
378       const LineRun& line = *(visualModel->mLines.Begin() + lineIndex + 1u);
379
380       // Get the next hit 'y' point.
381       const float hitPointY = cursorInfo.lineOffset + cursorInfo.lineHeight + 0.5f * GetLineHeight(line);
382
383       // Use the cursor hook position 'x' and the next hit 'y' position to calculate the new cursor index.
384       bool matchedCharacter = false;
385       primaryCursorPosition = Text::GetClosestCursorIndex(visualModel,
386                                                           logicalModel,
387                                                           impl.mMetrics,
388                                                           eventData.mCursorHookPositionX,
389                                                           hitPointY,
390                                                           CharacterHitTest::TAP,
391                                                           matchedCharacter);
392     }
393   }
394
395   if(!isShiftModifier && eventData.mState != EventData::SELECTING)
396   {
397     // Update selection position after moving the cursor
398     eventData.mLeftSelectionPosition  = primaryCursorPosition;
399     eventData.mRightSelectionPosition = primaryCursorPosition;
400   }
401
402   if(isShiftModifier && impl.IsShowingRealText() && eventData.mShiftSelectionFlag)
403   {
404     // Handle text selection
405     bool selecting = false;
406
407     if(Dali::DALI_KEY_CURSOR_LEFT == keyCode || Dali::DALI_KEY_CURSOR_RIGHT == keyCode)
408     {
409       // Shift-Left/Right to select the text
410       int cursorPositionDelta = primaryCursorPosition - previousPrimaryCursorPosition;
411       if(cursorPositionDelta > 0 || eventData.mRightSelectionPosition > 0u) // Check the boundary
412       {
413         eventData.mRightSelectionPosition += cursorPositionDelta;
414       }
415       selecting = true;
416     }
417     else if(eventData.mLeftSelectionPosition != eventData.mRightSelectionPosition)
418     {
419       // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
420       selecting = true;
421     }
422
423     if(selecting)
424     {
425       // Notify the cursor position to the InputMethodContext.
426       if(eventData.mInputMethodContext)
427       {
428         eventData.mInputMethodContext.SetCursorPosition(primaryCursorPosition);
429         eventData.mInputMethodContext.NotifyCursorPosition();
430       }
431
432       impl.ChangeState(EventData::SELECTING);
433
434       eventData.mUpdateLeftSelectionPosition  = true;
435       eventData.mUpdateRightSelectionPosition = true;
436       eventData.mUpdateGrabHandlePosition     = true;
437       eventData.mUpdateHighlightBox           = true;
438
439       // Hide the text selection popup if select the text using keyboard instead of moving grab handles
440       if(eventData.mGrabHandlePopupEnabled)
441       {
442         eventData.mDecorator->SetPopupActive(false);
443       }
444     }
445   }
446   else
447   {
448     // Handle normal cursor move
449     impl.ChangeState(EventData::EDITING);
450     eventData.mUpdateCursorPosition = true;
451   }
452
453   eventData.mUpdateInputStyle          = true;
454   eventData.mScrollAfterUpdatePosition = true;
455 }
456
457 void ControllerImplEventHandler::OnTapEvent(Controller::Impl& impl, const Event& event)
458 {
459   if(impl.mEventData)
460   {
461     const unsigned int tapCount     = event.p1.mUint;
462     EventData&         eventData    = *impl.mEventData;
463     ModelPtr&          model        = impl.mModel;
464     LogicalModelPtr&   logicalModel = model->mLogicalModel;
465     VisualModelPtr&    visualModel  = model->mVisualModel;
466
467     if(1u == tapCount)
468     {
469       if(impl.IsShowingRealText())
470       {
471         // Convert from control's coords to text's coords.
472         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
473         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
474
475         // Keep the tap 'x' position. Used to move the cursor.
476         eventData.mCursorHookPositionX = xPosition;
477
478         // Whether to touch point hits on a glyph.
479         bool matchedCharacter            = false;
480         eventData.mPrimaryCursorPosition = Text::GetClosestCursorIndex(visualModel,
481                                                                        logicalModel,
482                                                                        impl.mMetrics,
483                                                                        xPosition,
484                                                                        yPosition,
485                                                                        CharacterHitTest::TAP,
486                                                                        matchedCharacter);
487
488         // When the cursor position is changing, delay cursor blinking
489         eventData.mDecorator->DelayCursorBlink();
490       }
491       else
492       {
493         eventData.mPrimaryCursorPosition = 0u;
494       }
495
496       // Update selection position after tapping
497       eventData.mLeftSelectionPosition  = eventData.mPrimaryCursorPosition;
498       eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
499
500       eventData.mUpdateCursorPosition      = true;
501       eventData.mUpdateGrabHandlePosition  = true;
502       eventData.mScrollAfterUpdatePosition = true;
503       eventData.mUpdateInputStyle          = true;
504
505       // Notify the cursor position to the InputMethodContext.
506       if(eventData.mInputMethodContext)
507       {
508         eventData.mInputMethodContext.SetCursorPosition(eventData.mPrimaryCursorPosition);
509         eventData.mInputMethodContext.NotifyCursorPosition();
510       }
511     }
512     else if(2u == tapCount)
513     {
514       if(eventData.mSelectionEnabled)
515       {
516         // Convert from control's coords to text's coords.
517         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
518         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
519
520         // Calculates the logical position from the x,y coords.
521         impl.RepositionSelectionHandles(xPosition, yPosition, eventData.mDoubleTapAction);
522       }
523     }
524   }
525 }
526
527 void ControllerImplEventHandler::OnPanEvent(Controller::Impl& impl, const Event& event)
528 {
529   if(impl.mEventData)
530   {
531     EventData&    eventData = *impl.mEventData;
532     DecoratorPtr& decorator = eventData.mDecorator;
533
534     const bool isHorizontalScrollEnabled = decorator->IsHorizontalScrollEnabled();
535     const bool isVerticalScrollEnabled   = decorator->IsVerticalScrollEnabled();
536
537     if(!isHorizontalScrollEnabled && !isVerticalScrollEnabled)
538     {
539       // Nothing to do if scrolling is not enabled.
540       return;
541     }
542
543     const GestureState state = static_cast<GestureState>(event.p1.mInt);
544     switch(state)
545     {
546       case GestureState::STARTED:
547       {
548         // Will remove the cursor, handles or text's popup, ...
549         impl.ChangeState(EventData::TEXT_PANNING);
550         break;
551       }
552       case GestureState::CONTINUING:
553       {
554         ModelPtr& model = impl.mModel;
555
556         const Vector2& layoutSize     = model->mVisualModel->GetLayoutSize();
557         Vector2&       scrollPosition = model->mScrollPosition;
558         const Vector2  currentScroll  = scrollPosition;
559
560         if(isHorizontalScrollEnabled)
561         {
562           const float displacementX = event.p2.mFloat;
563           scrollPosition.x += displacementX;
564
565           impl.ClampHorizontalScroll(layoutSize);
566         }
567
568         if(isVerticalScrollEnabled)
569         {
570           const float displacementY = event.p3.mFloat;
571           scrollPosition.y += displacementY;
572
573           impl.ClampVerticalScroll(layoutSize);
574         }
575
576         decorator->UpdatePositions(scrollPosition - currentScroll);
577         break;
578       }
579       case GestureState::FINISHED:
580       case GestureState::CANCELLED: // FALLTHROUGH
581       {
582         // Will go back to the previous state to show the cursor, handles, the text's popup, ...
583         impl.ChangeState(eventData.mPreviousState);
584         break;
585       }
586       default:
587         break;
588     }
589   }
590 }
591
592 void ControllerImplEventHandler::OnLongPressEvent(Controller::Impl& impl, const Event& event)
593 {
594   DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::OnLongPressEvent\n");
595
596   if(impl.mEventData)
597   {
598     EventData& eventData = *impl.mEventData;
599
600     if(!impl.IsShowingRealText() && (EventData::EDITING == eventData.mState))
601     {
602       impl.ChangeState(EventData::EDITING_WITH_POPUP);
603       eventData.mDecoratorUpdated = true;
604       eventData.mUpdateInputStyle = true;
605     }
606     else
607     {
608       if(eventData.mSelectionEnabled)
609       {
610         ModelPtr& model = impl.mModel;
611
612         // Convert from control's coords to text's coords.
613         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
614         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
615
616         // Calculates the logical position from the x,y coords.
617         impl.RepositionSelectionHandles(xPosition, yPosition, eventData.mLongPressAction);
618       }
619     }
620   }
621 }
622
623 void ControllerImplEventHandler::OnHandleEvent(Controller::Impl& impl, const Event& event)
624 {
625   if(impl.mEventData)
626   {
627     const unsigned int state                    = event.p1.mUint;
628     const bool         handleStopScrolling      = (HANDLE_STOP_SCROLLING == state);
629     const bool         isSmoothHandlePanEnabled = impl.mEventData->mDecorator->IsSmoothHandlePanEnabled();
630
631     if(HANDLE_PRESSED == state)
632     {
633       OnHandlePressed(impl, event, isSmoothHandlePanEnabled);
634     } // end ( HANDLE_PRESSED == state )
635     else if((HANDLE_RELEASED == state) ||
636             handleStopScrolling)
637     {
638       OnHandleReleased(impl, event, isSmoothHandlePanEnabled, handleStopScrolling);
639     } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
640     else if(HANDLE_SCROLLING == state)
641     {
642       OnHandleScrolling(impl, event, isSmoothHandlePanEnabled);
643     } // end ( HANDLE_SCROLLING == state )
644   }
645 }
646
647 void ControllerImplEventHandler::OnSelectEvent(Controller::Impl& impl, const Event& event)
648 {
649   if(impl.mEventData && impl.mEventData->mSelectionEnabled)
650   {
651     ModelPtr&      model          = impl.mModel;
652     const Vector2& scrollPosition = model->mScrollPosition;
653
654     // Convert from control's coords to text's coords.
655     const float xPosition = event.p2.mFloat - scrollPosition.x;
656     const float yPosition = event.p3.mFloat - scrollPosition.y;
657
658     // Calculates the logical position from the x,y coords.
659     impl.RepositionSelectionHandles(xPosition, yPosition, Controller::NoTextTap::HIGHLIGHT);
660   }
661 }
662
663 void ControllerImplEventHandler::OnSelectAllEvent(Controller::Impl& impl)
664 {
665   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled ? "true" : "false");
666
667   if(impl.mEventData)
668   {
669     EventData& eventData = *impl.mEventData;
670     if(eventData.mSelectionEnabled && eventData.mState != EventData::INACTIVE)
671     {
672       ModelPtr&      model          = impl.mModel;
673       const Vector2& scrollPosition = model->mScrollPosition;
674
675       // Calculates the logical position from the start.
676       impl.RepositionSelectionHandles(0.f - scrollPosition.x,
677                                       0.f - scrollPosition.y,
678                                       Controller::NoTextTap::HIGHLIGHT);
679
680       eventData.mLeftSelectionPosition  = 0u;
681       eventData.mRightSelectionPosition = model->mLogicalModel->mText.Count();
682     }
683   }
684 }
685
686 void ControllerImplEventHandler::OnSelectNoneEvent(Controller::Impl& impl)
687 {
688   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "OnSelectNoneEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled ? "true" : "false");
689
690   if(impl.mEventData)
691   {
692     EventData& eventData = *impl.mEventData;
693     if(eventData.mSelectionEnabled && eventData.mState == EventData::SELECTING)
694     {
695       eventData.mPrimaryCursorPosition = 0u;
696       eventData.mLeftSelectionPosition = eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
697       impl.ChangeState(EventData::INACTIVE);
698       eventData.mUpdateCursorPosition      = true;
699       eventData.mUpdateInputStyle          = true;
700       eventData.mScrollAfterUpdatePosition = true;
701     }
702   }
703 }
704
705 void ControllerImplEventHandler::OnSelectRangeEvent(Controller::Impl& impl, const Event& event)
706 {
707   if(impl.mEventData && impl.mEventData->mSelectionEnabled && impl.mEventData->mState != EventData::INACTIVE)
708   {
709     ModelPtr&      model          = impl.mModel;
710     const Vector2& scrollPosition = model->mScrollPosition;
711
712     // Calculate the selection index.
713     const uint32_t length = static_cast<uint32_t>(model->mLogicalModel->mText.Count());
714     const uint32_t start  = std::min(event.p2.mUint, length);
715     const uint32_t end    = std::min(event.p3.mUint, length);
716
717     if(start != end)
718     {
719       // Calculates the logical position from the x,y coords.
720       impl.RepositionSelectionHandles(0.f - scrollPosition.x, 0.f - scrollPosition.y, Controller::NoTextTap::HIGHLIGHT);
721
722       impl.mEventData->mLeftSelectionPosition  = start;
723       impl.mEventData->mRightSelectionPosition = end;
724     }
725   }
726 }
727
728 void ControllerImplEventHandler::OnHandlePressed(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
729 {
730   ModelPtr&      model          = impl.mModel;
731   const Vector2& scrollPosition = model->mScrollPosition;
732
733   // Convert from decorator's coords to text's coords.
734   const float xPosition = event.p2.mFloat - scrollPosition.x;
735   const float yPosition = event.p3.mFloat - scrollPosition.y;
736
737   // Need to calculate the handle's new position.
738   bool                 matchedCharacter  = false;
739   const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex(model->mVisualModel,
740                                                                        model->mLogicalModel,
741                                                                        impl.mMetrics,
742                                                                        xPosition,
743                                                                        yPosition,
744                                                                        CharacterHitTest::SCROLL,
745                                                                        matchedCharacter);
746
747   EventData& eventData = *impl.mEventData;
748
749   if(Event::GRAB_HANDLE_EVENT == event.type)
750   {
751     impl.ChangeState(EventData::GRAB_HANDLE_PANNING);
752
753     if(handleNewPosition != eventData.mPrimaryCursorPosition)
754     {
755       // Updates the cursor position if the handle's new position is different than the current one.
756       eventData.mUpdateCursorPosition = true;
757       // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
758       eventData.mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
759       eventData.mPrimaryCursorPosition    = handleNewPosition;
760     }
761
762     // 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.
763     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
764   }
765   else if(Event::LEFT_SELECTION_HANDLE_EVENT == event.type)
766   {
767     impl.ChangeState(EventData::SELECTION_HANDLE_PANNING);
768
769     if((handleNewPosition != eventData.mLeftSelectionPosition) &&
770        (handleNewPosition != eventData.mRightSelectionPosition))
771     {
772       // Updates the highlight box if the handle's new position is different than the current one.
773       eventData.mUpdateHighlightBox = true;
774       // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
775       eventData.mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
776       eventData.mLeftSelectionPosition       = handleNewPosition;
777     }
778
779     // 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.
780     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
781
782     // Will define the order to scroll the text to match the handle position.
783     eventData.mIsLeftHandleSelected  = true;
784     eventData.mIsRightHandleSelected = false;
785   }
786   else if(Event::RIGHT_SELECTION_HANDLE_EVENT == event.type)
787   {
788     impl.ChangeState(EventData::SELECTION_HANDLE_PANNING);
789
790     if((handleNewPosition != eventData.mRightSelectionPosition) &&
791        (handleNewPosition != eventData.mLeftSelectionPosition))
792     {
793       // Updates the highlight box if the handle's new position is different than the current one.
794       eventData.mUpdateHighlightBox = true;
795       // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
796       eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
797       eventData.mRightSelectionPosition       = handleNewPosition;
798     }
799
800     // 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.
801     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
802
803     // Will define the order to scroll the text to match the handle position.
804     eventData.mIsLeftHandleSelected  = false;
805     eventData.mIsRightHandleSelected = true;
806   }
807 }
808
809 void ControllerImplEventHandler::OnHandleReleased(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled, const bool handleStopScrolling)
810 {
811   CharacterIndex handlePosition = 0u;
812   if(handleStopScrolling || isSmoothHandlePanEnabled)
813   {
814     ModelPtr&      model          = impl.mModel;
815     const Vector2& scrollPosition = model->mScrollPosition;
816
817     // Convert from decorator's coords to text's coords.
818     const float xPosition = event.p2.mFloat - scrollPosition.x;
819     const float yPosition = event.p3.mFloat - scrollPosition.y;
820
821     bool matchedCharacter = false;
822     handlePosition        = Text::GetClosestCursorIndex(model->mVisualModel,
823                                                  model->mLogicalModel,
824                                                  impl.mMetrics,
825                                                  xPosition,
826                                                  yPosition,
827                                                  CharacterHitTest::SCROLL,
828                                                  matchedCharacter);
829   }
830
831   EventData& eventData = *impl.mEventData;
832
833   if(Event::GRAB_HANDLE_EVENT == event.type)
834   {
835     eventData.mUpdateCursorPosition     = true;
836     eventData.mUpdateGrabHandlePosition = true;
837     eventData.mUpdateInputStyle         = true;
838
839     if(!impl.IsClipboardEmpty())
840     {
841       impl.ChangeState(EventData::EDITING_WITH_PASTE_POPUP); // Moving grabhandle will show Paste Popup
842     }
843
844     if(handleStopScrolling || isSmoothHandlePanEnabled)
845     {
846       eventData.mScrollAfterUpdatePosition = true;
847       eventData.mPrimaryCursorPosition     = handlePosition;
848     }
849   }
850   else if(Event::LEFT_SELECTION_HANDLE_EVENT == event.type)
851   {
852     impl.ChangeState(EventData::SELECTING);
853
854     eventData.mUpdateHighlightBox           = true;
855     eventData.mUpdateLeftSelectionPosition  = true;
856     eventData.mUpdateRightSelectionPosition = true;
857
858     if(handleStopScrolling || isSmoothHandlePanEnabled)
859     {
860       eventData.mScrollAfterUpdatePosition = true;
861
862       if((handlePosition != eventData.mRightSelectionPosition) &&
863          (handlePosition != eventData.mLeftSelectionPosition))
864       {
865         eventData.mLeftSelectionPosition = handlePosition;
866       }
867     }
868   }
869   else if(Event::RIGHT_SELECTION_HANDLE_EVENT == event.type)
870   {
871     impl.ChangeState(EventData::SELECTING);
872
873     eventData.mUpdateHighlightBox           = true;
874     eventData.mUpdateRightSelectionPosition = true;
875     eventData.mUpdateLeftSelectionPosition  = true;
876
877     if(handleStopScrolling || isSmoothHandlePanEnabled)
878     {
879       eventData.mScrollAfterUpdatePosition = true;
880       if((handlePosition != eventData.mRightSelectionPosition) &&
881          (handlePosition != eventData.mLeftSelectionPosition))
882       {
883         eventData.mRightSelectionPosition = handlePosition;
884       }
885     }
886   }
887
888   eventData.mDecoratorUpdated = true;
889 }
890
891 void ControllerImplEventHandler::OnHandleScrolling(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
892 {
893   ModelPtr&       model          = impl.mModel;
894   Vector2&        scrollPosition = model->mScrollPosition;
895   VisualModelPtr& visualModel    = model->mVisualModel;
896
897   const float    xSpeed                = event.p2.mFloat;
898   const float    ySpeed                = event.p3.mFloat;
899   const Vector2& layoutSize            = visualModel->GetLayoutSize();
900   const Vector2  currentScrollPosition = scrollPosition;
901
902   scrollPosition.x += xSpeed;
903   scrollPosition.y += ySpeed;
904
905   impl.ClampHorizontalScroll(layoutSize);
906   impl.ClampVerticalScroll(layoutSize);
907
908   EventData&    eventData = *impl.mEventData;
909   DecoratorPtr& decorator = eventData.mDecorator;
910
911   bool endOfScroll = false;
912   if(Vector2::ZERO == (currentScrollPosition - scrollPosition))
913   {
914     // Notify the decorator there is no more text to scroll.
915     // The decorator won't send more scroll events.
916     decorator->NotifyEndOfScroll();
917     // Still need to set the position of the handle.
918     endOfScroll = true;
919   }
920
921   // Set the position of the handle.
922   const bool scrollRightDirection      = xSpeed > 0.f;
923   const bool scrollBottomDirection     = ySpeed > 0.f;
924   const bool leftSelectionHandleEvent  = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
925   const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
926
927   if(Event::GRAB_HANDLE_EVENT == event.type)
928   {
929     impl.ChangeState(EventData::GRAB_HANDLE_PANNING);
930
931     // Get the grab handle position in decorator coords.
932     Vector2 position = decorator->GetPosition(GRAB_HANDLE);
933
934     if(decorator->IsHorizontalScrollEnabled())
935     {
936       // Position the grag handle close to either the left or right edge.
937       position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
938     }
939
940     if(decorator->IsVerticalScrollEnabled())
941     {
942       position.x = eventData.mCursorHookPositionX;
943
944       // Position the grag handle close to either the top or bottom edge.
945       position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
946     }
947
948     // Get the new handle position.
949     // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
950     bool                 matchedCharacter = false;
951     const CharacterIndex handlePosition   = Text::GetClosestCursorIndex(visualModel,
952                                                                       impl.mModel->mLogicalModel,
953                                                                       impl.mMetrics,
954                                                                       position.x - scrollPosition.x,
955                                                                       position.y - scrollPosition.y,
956                                                                       CharacterHitTest::SCROLL,
957                                                                       matchedCharacter);
958
959     if(eventData.mPrimaryCursorPosition != handlePosition)
960     {
961       eventData.mUpdateCursorPosition      = true;
962       eventData.mUpdateGrabHandlePosition  = !isSmoothHandlePanEnabled;
963       eventData.mScrollAfterUpdatePosition = true;
964       eventData.mPrimaryCursorPosition     = handlePosition;
965     }
966     eventData.mUpdateInputStyle = eventData.mUpdateCursorPosition;
967
968     // Updates the decorator if the soft handle panning is enabled.
969     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
970   }
971   else if(leftSelectionHandleEvent || rightSelectionHandleEvent)
972   {
973     impl.ChangeState(EventData::SELECTION_HANDLE_PANNING);
974
975     // Get the selection handle position in decorator coords.
976     Vector2 position = decorator->GetPosition(leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE);
977
978     if(decorator->IsHorizontalScrollEnabled())
979     {
980       // Position the selection handle close to either the left or right edge.
981       position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
982     }
983
984     if(decorator->IsVerticalScrollEnabled())
985     {
986       position.x = eventData.mCursorHookPositionX;
987
988       // Position the grag handle close to either the top or bottom edge.
989       position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
990     }
991
992     // Get the new handle position.
993     // The selection handle's position is in decorator's coords. Need to transform to text's coords.
994     bool                 matchedCharacter = false;
995     const CharacterIndex handlePosition   = Text::GetClosestCursorIndex(visualModel,
996                                                                       impl.mModel->mLogicalModel,
997                                                                       impl.mMetrics,
998                                                                       position.x - scrollPosition.x,
999                                                                       position.y - scrollPosition.y,
1000                                                                       CharacterHitTest::SCROLL,
1001                                                                       matchedCharacter);
1002
1003     if(leftSelectionHandleEvent)
1004     {
1005       const bool differentHandles = (eventData.mLeftSelectionPosition != handlePosition) && (eventData.mRightSelectionPosition != handlePosition);
1006
1007       if(differentHandles || endOfScroll)
1008       {
1009         eventData.mUpdateHighlightBox           = true;
1010         eventData.mUpdateLeftSelectionPosition  = !isSmoothHandlePanEnabled;
1011         eventData.mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1012         eventData.mLeftSelectionPosition        = handlePosition;
1013       }
1014     }
1015     else
1016     {
1017       const bool differentHandles = (eventData.mRightSelectionPosition != handlePosition) && (eventData.mLeftSelectionPosition != handlePosition);
1018       if(differentHandles || endOfScroll)
1019       {
1020         eventData.mUpdateHighlightBox           = true;
1021         eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1022         eventData.mUpdateLeftSelectionPosition  = isSmoothHandlePanEnabled;
1023         eventData.mRightSelectionPosition       = handlePosition;
1024       }
1025     }
1026
1027     if(eventData.mUpdateLeftSelectionPosition || eventData.mUpdateRightSelectionPosition)
1028     {
1029       impl.RepositionSelectionHandles();
1030
1031       eventData.mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1032     }
1033   }
1034   eventData.mDecoratorUpdated = true;
1035 }
1036
1037 } // namespace Text
1038
1039 } // namespace Toolkit
1040
1041 } // namespace Dali