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