Add SelectionChanged signal
[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         uint32_t oldStart = eventData.mLeftSelectionPosition;
414         uint32_t oldEnd   = eventData.mRightSelectionPosition;
415
416         eventData.mRightSelectionPosition += cursorPositionDelta;
417
418         if(impl.mSelectableControlInterface != nullptr)
419         {
420           impl.mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
421         }
422       }
423       selecting = true;
424     }
425     else if(eventData.mLeftSelectionPosition != eventData.mRightSelectionPosition)
426     {
427       // Show no grab handles and text highlight if Shift-Up/Down pressed but no selected text
428       selecting = true;
429     }
430
431     if(selecting)
432     {
433       // Notify the cursor position to the InputMethodContext.
434       if(eventData.mInputMethodContext)
435       {
436         eventData.mInputMethodContext.SetCursorPosition(primaryCursorPosition);
437         eventData.mInputMethodContext.NotifyCursorPosition();
438       }
439
440       impl.ChangeState(EventData::SELECTING);
441
442       eventData.mUpdateLeftSelectionPosition  = true;
443       eventData.mUpdateRightSelectionPosition = true;
444       eventData.mUpdateGrabHandlePosition     = true;
445       eventData.mUpdateHighlightBox           = true;
446
447       // Hide the text selection popup if select the text using keyboard instead of moving grab handles
448       if(eventData.mGrabHandlePopupEnabled)
449       {
450         eventData.mDecorator->SetPopupActive(false);
451       }
452     }
453   }
454   else
455   {
456     // Handle normal cursor move
457     impl.ChangeState(EventData::EDITING);
458     eventData.mUpdateCursorPosition = true;
459   }
460
461   eventData.mUpdateInputStyle          = true;
462   eventData.mScrollAfterUpdatePosition = true;
463 }
464
465 void ControllerImplEventHandler::OnTapEvent(Controller::Impl& impl, const Event& event)
466 {
467   if(impl.mEventData)
468   {
469     const unsigned int tapCount     = event.p1.mUint;
470     EventData&         eventData    = *impl.mEventData;
471     ModelPtr&          model        = impl.mModel;
472     LogicalModelPtr&   logicalModel = model->mLogicalModel;
473     VisualModelPtr&    visualModel  = model->mVisualModel;
474
475     if(1u == tapCount)
476     {
477       if(impl.IsShowingRealText())
478       {
479         // Convert from control's coords to text's coords.
480         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
481         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
482
483         // Keep the tap 'x' position. Used to move the cursor.
484         eventData.mCursorHookPositionX = xPosition;
485
486         // Whether to touch point hits on a glyph.
487         bool matchedCharacter            = false;
488         eventData.mPrimaryCursorPosition = Text::GetClosestCursorIndex(visualModel,
489                                                                        logicalModel,
490                                                                        impl.mMetrics,
491                                                                        xPosition,
492                                                                        yPosition,
493                                                                        CharacterHitTest::TAP,
494                                                                        matchedCharacter);
495
496         // When the cursor position is changing, delay cursor blinking
497         eventData.mDecorator->DelayCursorBlink();
498       }
499       else
500       {
501         eventData.mPrimaryCursorPosition = 0u;
502       }
503
504       // Update selection position after tapping
505       eventData.mLeftSelectionPosition  = eventData.mPrimaryCursorPosition;
506       eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
507
508       eventData.mUpdateCursorPosition      = true;
509       eventData.mUpdateGrabHandlePosition  = true;
510       eventData.mScrollAfterUpdatePosition = true;
511       eventData.mUpdateInputStyle          = true;
512
513       // Notify the cursor position to the InputMethodContext.
514       if(eventData.mInputMethodContext)
515       {
516         eventData.mInputMethodContext.SetCursorPosition(eventData.mPrimaryCursorPosition);
517         eventData.mInputMethodContext.NotifyCursorPosition();
518       }
519     }
520     else if(2u == tapCount)
521     {
522       if(eventData.mSelectionEnabled)
523       {
524         // Convert from control's coords to text's coords.
525         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
526         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
527
528         // Calculates the logical position from the x,y coords.
529         impl.RepositionSelectionHandles(xPosition, yPosition, eventData.mDoubleTapAction);
530       }
531     }
532   }
533 }
534
535 void ControllerImplEventHandler::OnPanEvent(Controller::Impl& impl, const Event& event)
536 {
537   if(impl.mEventData)
538   {
539     EventData&    eventData = *impl.mEventData;
540     DecoratorPtr& decorator = eventData.mDecorator;
541
542     const bool isHorizontalScrollEnabled = decorator->IsHorizontalScrollEnabled();
543     const bool isVerticalScrollEnabled   = decorator->IsVerticalScrollEnabled();
544
545     if(!isHorizontalScrollEnabled && !isVerticalScrollEnabled)
546     {
547       // Nothing to do if scrolling is not enabled.
548       return;
549     }
550
551     const GestureState state = static_cast<GestureState>(event.p1.mInt);
552     switch(state)
553     {
554       case GestureState::STARTED:
555       {
556         // Will remove the cursor, handles or text's popup, ...
557         impl.ChangeState(EventData::TEXT_PANNING);
558         break;
559       }
560       case GestureState::CONTINUING:
561       {
562         ModelPtr& model = impl.mModel;
563
564         const Vector2& layoutSize     = model->mVisualModel->GetLayoutSize();
565         Vector2&       scrollPosition = model->mScrollPosition;
566         const Vector2  currentScroll  = scrollPosition;
567
568         if(isHorizontalScrollEnabled)
569         {
570           const float displacementX = event.p2.mFloat;
571           scrollPosition.x += displacementX;
572
573           impl.ClampHorizontalScroll(layoutSize);
574         }
575
576         if(isVerticalScrollEnabled)
577         {
578           const float displacementY = event.p3.mFloat;
579           scrollPosition.y += displacementY;
580
581           impl.ClampVerticalScroll(layoutSize);
582         }
583
584         decorator->UpdatePositions(scrollPosition - currentScroll);
585         break;
586       }
587       case GestureState::FINISHED:
588       case GestureState::CANCELLED: // FALLTHROUGH
589       {
590         // Will go back to the previous state to show the cursor, handles, the text's popup, ...
591         impl.ChangeState(eventData.mPreviousState);
592         break;
593       }
594       default:
595         break;
596     }
597   }
598 }
599
600 void ControllerImplEventHandler::OnLongPressEvent(Controller::Impl& impl, const Event& event)
601 {
602   DALI_LOG_INFO(gLogFilter, Debug::General, "Controller::OnLongPressEvent\n");
603
604   if(impl.mEventData)
605   {
606     EventData& eventData = *impl.mEventData;
607
608     if(!impl.IsShowingRealText() && (EventData::EDITING == eventData.mState))
609     {
610       impl.ChangeState(EventData::EDITING_WITH_POPUP);
611       eventData.mDecoratorUpdated = true;
612       eventData.mUpdateInputStyle = true;
613     }
614     else
615     {
616       if(eventData.mSelectionEnabled)
617       {
618         ModelPtr& model = impl.mModel;
619
620         // Convert from control's coords to text's coords.
621         const float xPosition = event.p2.mFloat - model->mScrollPosition.x;
622         const float yPosition = event.p3.mFloat - model->mScrollPosition.y;
623
624         // Calculates the logical position from the x,y coords.
625         impl.RepositionSelectionHandles(xPosition, yPosition, eventData.mLongPressAction);
626       }
627     }
628   }
629 }
630
631 void ControllerImplEventHandler::OnHandleEvent(Controller::Impl& impl, const Event& event)
632 {
633   if(impl.mEventData)
634   {
635     const unsigned int state                    = event.p1.mUint;
636     const bool         handleStopScrolling      = (HANDLE_STOP_SCROLLING == state);
637     const bool         isSmoothHandlePanEnabled = impl.mEventData->mDecorator->IsSmoothHandlePanEnabled();
638
639     if(HANDLE_PRESSED == state)
640     {
641       OnHandlePressed(impl, event, isSmoothHandlePanEnabled);
642     } // end ( HANDLE_PRESSED == state )
643     else if((HANDLE_RELEASED == state) ||
644             handleStopScrolling)
645     {
646       OnHandleReleased(impl, event, isSmoothHandlePanEnabled, handleStopScrolling);
647     } // end ( ( HANDLE_RELEASED == state ) || ( HANDLE_STOP_SCROLLING == state ) )
648     else if(HANDLE_SCROLLING == state)
649     {
650       OnHandleScrolling(impl, event, isSmoothHandlePanEnabled);
651     } // end ( HANDLE_SCROLLING == state )
652   }
653 }
654
655 void ControllerImplEventHandler::OnSelectEvent(Controller::Impl& impl, const Event& event)
656 {
657   if(impl.mEventData && impl.mEventData->mSelectionEnabled)
658   {
659     ModelPtr&      model          = impl.mModel;
660     const Vector2& scrollPosition = model->mScrollPosition;
661
662     // Convert from control's coords to text's coords.
663     const float xPosition = event.p2.mFloat - scrollPosition.x;
664     const float yPosition = event.p3.mFloat - scrollPosition.y;
665
666     // Calculates the logical position from the x,y coords.
667     impl.RepositionSelectionHandles(xPosition, yPosition, Controller::NoTextTap::HIGHLIGHT);
668   }
669 }
670
671 void ControllerImplEventHandler::OnSelectAllEvent(Controller::Impl& impl)
672 {
673   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "OnSelectAllEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled ? "true" : "false");
674
675   if(impl.mEventData)
676   {
677     EventData& eventData = *impl.mEventData;
678     if(eventData.mSelectionEnabled && eventData.mState != EventData::INACTIVE)
679     {
680       ModelPtr&      model          = impl.mModel;
681       const Vector2& scrollPosition = model->mScrollPosition;
682
683       // Calculates the logical position from the start.
684       impl.RepositionSelectionHandles(0.f - scrollPosition.x,
685                                       0.f - scrollPosition.y,
686                                       Controller::NoTextTap::HIGHLIGHT);
687
688       uint32_t oldStart = eventData.mLeftSelectionPosition;
689       uint32_t oldEnd   = eventData.mRightSelectionPosition;
690
691       eventData.mLeftSelectionPosition  = 0u;
692       eventData.mRightSelectionPosition = model->mLogicalModel->mText.Count();
693
694       if(impl.mSelectableControlInterface != nullptr)
695       {
696         impl.mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
697       }
698     }
699   }
700 }
701
702 void ControllerImplEventHandler::OnSelectNoneEvent(Controller::Impl& impl)
703 {
704   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "OnSelectNoneEvent mEventData->mSelectionEnabled%s \n", impl.mEventData->mSelectionEnabled ? "true" : "false");
705
706   if(impl.mEventData)
707   {
708     EventData& eventData = *impl.mEventData;
709     if(eventData.mSelectionEnabled && eventData.mState == EventData::SELECTING)
710     {
711       eventData.mPrimaryCursorPosition = 0u;
712       uint32_t oldStart                = eventData.mLeftSelectionPosition;
713       uint32_t oldEnd                  = eventData.mRightSelectionPosition;
714
715       eventData.mLeftSelectionPosition = eventData.mRightSelectionPosition = eventData.mPrimaryCursorPosition;
716       impl.ChangeState(EventData::INACTIVE);
717       eventData.mUpdateCursorPosition      = true;
718       eventData.mUpdateInputStyle          = true;
719       eventData.mScrollAfterUpdatePosition = true;
720
721       if(impl.mSelectableControlInterface != nullptr)
722       {
723         impl.mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
724       }
725     }
726   }
727 }
728
729 void ControllerImplEventHandler::OnSelectRangeEvent(Controller::Impl& impl, const Event& event)
730 {
731   if(impl.mEventData && impl.mEventData->mSelectionEnabled && impl.mEventData->mState != EventData::INACTIVE)
732   {
733     ModelPtr&      model          = impl.mModel;
734     const Vector2& scrollPosition = model->mScrollPosition;
735
736     // Calculate the selection index.
737     const uint32_t length = static_cast<uint32_t>(model->mLogicalModel->mText.Count());
738     const uint32_t start  = std::min(event.p2.mUint, length);
739     const uint32_t end    = std::min(event.p3.mUint, length);
740
741     if(start != end)
742     {
743       uint32_t oldStart = impl.mEventData->mLeftSelectionPosition;
744       uint32_t oldEnd   = impl.mEventData->mRightSelectionPosition;
745
746       // Calculates the logical position from the x,y coords.
747       impl.RepositionSelectionHandles(0.f - scrollPosition.x, 0.f - scrollPosition.y, Controller::NoTextTap::HIGHLIGHT);
748
749       impl.mEventData->mLeftSelectionPosition  = start;
750       impl.mEventData->mRightSelectionPosition = end;
751
752       if(impl.mSelectableControlInterface != nullptr)
753       {
754         impl.mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, start, end);
755       }
756     }
757   }
758 }
759
760 void ControllerImplEventHandler::OnHandlePressed(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
761 {
762   ModelPtr&      model          = impl.mModel;
763   const Vector2& scrollPosition = model->mScrollPosition;
764
765   // Convert from decorator's coords to text's coords.
766   const float xPosition = event.p2.mFloat - scrollPosition.x;
767   const float yPosition = event.p3.mFloat - scrollPosition.y;
768
769   // Need to calculate the handle's new position.
770   bool                 matchedCharacter  = false;
771   const CharacterIndex handleNewPosition = Text::GetClosestCursorIndex(model->mVisualModel,
772                                                                        model->mLogicalModel,
773                                                                        impl.mMetrics,
774                                                                        xPosition,
775                                                                        yPosition,
776                                                                        CharacterHitTest::SCROLL,
777                                                                        matchedCharacter);
778
779   EventData& eventData = *impl.mEventData;
780   uint32_t   oldStart  = eventData.mLeftSelectionPosition;
781   uint32_t   oldEnd    = eventData.mRightSelectionPosition;
782
783   if(Event::GRAB_HANDLE_EVENT == event.type)
784   {
785     impl.ChangeState(EventData::GRAB_HANDLE_PANNING);
786
787     if(handleNewPosition != eventData.mPrimaryCursorPosition)
788     {
789       // Updates the cursor position if the handle's new position is different than the current one.
790       eventData.mUpdateCursorPosition = true;
791       // Does not update the grab handle position if the smooth panning is enabled. (The decorator does it smooth).
792       eventData.mUpdateGrabHandlePosition = !isSmoothHandlePanEnabled;
793       eventData.mPrimaryCursorPosition    = 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   else if(Event::LEFT_SELECTION_HANDLE_EVENT == event.type)
800   {
801     impl.ChangeState(EventData::SELECTION_HANDLE_PANNING);
802
803     if((handleNewPosition != eventData.mLeftSelectionPosition) &&
804        (handleNewPosition != eventData.mRightSelectionPosition))
805     {
806       // Updates the highlight box if the handle's new position is different than the current one.
807       eventData.mUpdateHighlightBox = true;
808       // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
809       eventData.mUpdateLeftSelectionPosition = !isSmoothHandlePanEnabled;
810       eventData.mLeftSelectionPosition       = handleNewPosition;
811     }
812
813     // 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.
814     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
815
816     // Will define the order to scroll the text to match the handle position.
817     eventData.mIsLeftHandleSelected  = true;
818     eventData.mIsRightHandleSelected = false;
819   }
820   else if(Event::RIGHT_SELECTION_HANDLE_EVENT == event.type)
821   {
822     impl.ChangeState(EventData::SELECTION_HANDLE_PANNING);
823
824     if((handleNewPosition != eventData.mRightSelectionPosition) &&
825        (handleNewPosition != eventData.mLeftSelectionPosition))
826     {
827       // Updates the highlight box if the handle's new position is different than the current one.
828       eventData.mUpdateHighlightBox = true;
829       // Does not update the selection handle position if the smooth panning is enabled. (The decorator does it smooth).
830       eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
831       eventData.mRightSelectionPosition       = handleNewPosition;
832     }
833
834     // 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.
835     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
836
837     // Will define the order to scroll the text to match the handle position.
838     eventData.mIsLeftHandleSelected  = false;
839     eventData.mIsRightHandleSelected = true;
840   }
841
842   if((impl.mSelectableControlInterface != nullptr) || eventData.mUpdateRightSelectionPosition || eventData.mUpdateLeftSelectionPosition)
843   {
844     impl.mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
845   }
846 }
847
848 void ControllerImplEventHandler::OnHandleReleased(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled, const bool handleStopScrolling)
849 {
850   CharacterIndex handlePosition = 0u;
851   if(handleStopScrolling || isSmoothHandlePanEnabled)
852   {
853     ModelPtr&      model          = impl.mModel;
854     const Vector2& scrollPosition = model->mScrollPosition;
855
856     // Convert from decorator's coords to text's coords.
857     const float xPosition = event.p2.mFloat - scrollPosition.x;
858     const float yPosition = event.p3.mFloat - scrollPosition.y;
859
860     bool matchedCharacter = false;
861     handlePosition        = Text::GetClosestCursorIndex(model->mVisualModel,
862                                                  model->mLogicalModel,
863                                                  impl.mMetrics,
864                                                  xPosition,
865                                                  yPosition,
866                                                  CharacterHitTest::SCROLL,
867                                                  matchedCharacter);
868   }
869
870   EventData& eventData = *impl.mEventData;
871   uint32_t   oldStart  = eventData.mLeftSelectionPosition;
872   uint32_t   oldEnd    = eventData.mRightSelectionPosition;
873
874   if(Event::GRAB_HANDLE_EVENT == event.type)
875   {
876     eventData.mUpdateCursorPosition     = true;
877     eventData.mUpdateGrabHandlePosition = true;
878     eventData.mUpdateInputStyle         = true;
879
880     if(!impl.IsClipboardEmpty())
881     {
882       impl.ChangeState(EventData::EDITING_WITH_PASTE_POPUP); // Moving grabhandle will show Paste Popup
883     }
884
885     if(handleStopScrolling || isSmoothHandlePanEnabled)
886     {
887       eventData.mScrollAfterUpdatePosition = true;
888       eventData.mPrimaryCursorPosition     = handlePosition;
889     }
890   }
891   else if(Event::LEFT_SELECTION_HANDLE_EVENT == event.type)
892   {
893     impl.ChangeState(EventData::SELECTING);
894
895     eventData.mUpdateHighlightBox           = true;
896     eventData.mUpdateLeftSelectionPosition  = true;
897     eventData.mUpdateRightSelectionPosition = true;
898
899     if(handleStopScrolling || isSmoothHandlePanEnabled)
900     {
901       eventData.mScrollAfterUpdatePosition = true;
902
903       if((handlePosition != eventData.mRightSelectionPosition) &&
904          (handlePosition != eventData.mLeftSelectionPosition))
905       {
906         eventData.mLeftSelectionPosition = handlePosition;
907       }
908     }
909   }
910   else if(Event::RIGHT_SELECTION_HANDLE_EVENT == event.type)
911   {
912     impl.ChangeState(EventData::SELECTING);
913
914     eventData.mUpdateHighlightBox           = true;
915     eventData.mUpdateRightSelectionPosition = true;
916     eventData.mUpdateLeftSelectionPosition  = true;
917
918     if(handleStopScrolling || isSmoothHandlePanEnabled)
919     {
920       eventData.mScrollAfterUpdatePosition = true;
921       if((handlePosition != eventData.mRightSelectionPosition) &&
922          (handlePosition != eventData.mLeftSelectionPosition))
923       {
924         eventData.mRightSelectionPosition = handlePosition;
925       }
926     }
927   }
928
929   if((impl.mSelectableControlInterface != nullptr) || eventData.mUpdateRightSelectionPosition || eventData.mUpdateLeftSelectionPosition)
930   {
931     impl.mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
932   }
933
934   eventData.mDecoratorUpdated = true;
935 }
936
937 void ControllerImplEventHandler::OnHandleScrolling(Controller::Impl& impl, const Event& event, const bool isSmoothHandlePanEnabled)
938 {
939   ModelPtr&       model          = impl.mModel;
940   Vector2&        scrollPosition = model->mScrollPosition;
941   VisualModelPtr& visualModel    = model->mVisualModel;
942
943   const float    xSpeed                = event.p2.mFloat;
944   const float    ySpeed                = event.p3.mFloat;
945   const Vector2& layoutSize            = visualModel->GetLayoutSize();
946   const Vector2  currentScrollPosition = scrollPosition;
947
948   scrollPosition.x += xSpeed;
949   scrollPosition.y += ySpeed;
950
951   impl.ClampHorizontalScroll(layoutSize);
952   impl.ClampVerticalScroll(layoutSize);
953
954   EventData&    eventData = *impl.mEventData;
955   DecoratorPtr& decorator = eventData.mDecorator;
956
957   bool endOfScroll = false;
958   if(Vector2::ZERO == (currentScrollPosition - scrollPosition))
959   {
960     // Notify the decorator there is no more text to scroll.
961     // The decorator won't send more scroll events.
962     decorator->NotifyEndOfScroll();
963     // Still need to set the position of the handle.
964     endOfScroll = true;
965   }
966
967   // Set the position of the handle.
968   const bool scrollRightDirection      = xSpeed > 0.f;
969   const bool scrollBottomDirection     = ySpeed > 0.f;
970   const bool leftSelectionHandleEvent  = Event::LEFT_SELECTION_HANDLE_EVENT == event.type;
971   const bool rightSelectionHandleEvent = Event::RIGHT_SELECTION_HANDLE_EVENT == event.type;
972
973   if(Event::GRAB_HANDLE_EVENT == event.type)
974   {
975     impl.ChangeState(EventData::GRAB_HANDLE_PANNING);
976
977     // Get the grab handle position in decorator coords.
978     Vector2 position = decorator->GetPosition(GRAB_HANDLE);
979
980     if(decorator->IsHorizontalScrollEnabled())
981     {
982       // Position the grag handle close to either the left or right edge.
983       position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
984     }
985
986     if(decorator->IsVerticalScrollEnabled())
987     {
988       position.x = eventData.mCursorHookPositionX;
989
990       // Position the grag handle close to either the top or bottom edge.
991       position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
992     }
993
994     // Get the new handle position.
995     // The grab handle's position is in decorator's coords. Need to transforms to text's coords.
996     bool                 matchedCharacter = false;
997     const CharacterIndex handlePosition   = Text::GetClosestCursorIndex(visualModel,
998                                                                       impl.mModel->mLogicalModel,
999                                                                       impl.mMetrics,
1000                                                                       position.x - scrollPosition.x,
1001                                                                       position.y - scrollPosition.y,
1002                                                                       CharacterHitTest::SCROLL,
1003                                                                       matchedCharacter);
1004
1005     if(eventData.mPrimaryCursorPosition != handlePosition)
1006     {
1007       eventData.mUpdateCursorPosition      = true;
1008       eventData.mUpdateGrabHandlePosition  = !isSmoothHandlePanEnabled;
1009       eventData.mScrollAfterUpdatePosition = true;
1010       eventData.mPrimaryCursorPosition     = handlePosition;
1011     }
1012     eventData.mUpdateInputStyle = eventData.mUpdateCursorPosition;
1013
1014     // Updates the decorator if the soft handle panning is enabled.
1015     eventData.mDecoratorUpdated = isSmoothHandlePanEnabled;
1016   }
1017   else if(leftSelectionHandleEvent || rightSelectionHandleEvent)
1018   {
1019     impl.ChangeState(EventData::SELECTION_HANDLE_PANNING);
1020
1021     // Get the selection handle position in decorator coords.
1022     Vector2 position = decorator->GetPosition(leftSelectionHandleEvent ? Text::LEFT_SELECTION_HANDLE : Text::RIGHT_SELECTION_HANDLE);
1023
1024     if(decorator->IsHorizontalScrollEnabled())
1025     {
1026       // Position the selection handle close to either the left or right edge.
1027       position.x = scrollRightDirection ? 0.f : visualModel->mControlSize.width;
1028     }
1029
1030     if(decorator->IsVerticalScrollEnabled())
1031     {
1032       position.x = eventData.mCursorHookPositionX;
1033
1034       // Position the grag handle close to either the top or bottom edge.
1035       position.y = scrollBottomDirection ? 0.f : visualModel->mControlSize.height;
1036     }
1037
1038     // Get the new handle position.
1039     // The selection handle's position is in decorator's coords. Need to transform to text's coords.
1040     bool                 matchedCharacter = false;
1041     const CharacterIndex handlePosition   = Text::GetClosestCursorIndex(visualModel,
1042                                                                       impl.mModel->mLogicalModel,
1043                                                                       impl.mMetrics,
1044                                                                       position.x - scrollPosition.x,
1045                                                                       position.y - scrollPosition.y,
1046                                                                       CharacterHitTest::SCROLL,
1047                                                                       matchedCharacter);
1048     uint32_t             oldStart         = eventData.mLeftSelectionPosition;
1049     uint32_t             oldEnd           = eventData.mRightSelectionPosition;
1050
1051     if(leftSelectionHandleEvent)
1052     {
1053       const bool differentHandles = (eventData.mLeftSelectionPosition != handlePosition) && (eventData.mRightSelectionPosition != handlePosition);
1054
1055       if(differentHandles || endOfScroll)
1056       {
1057         eventData.mUpdateHighlightBox           = true;
1058         eventData.mUpdateLeftSelectionPosition  = !isSmoothHandlePanEnabled;
1059         eventData.mUpdateRightSelectionPosition = isSmoothHandlePanEnabled;
1060         eventData.mLeftSelectionPosition        = handlePosition;
1061       }
1062     }
1063     else
1064     {
1065       const bool differentHandles = (eventData.mRightSelectionPosition != handlePosition) && (eventData.mLeftSelectionPosition != handlePosition);
1066       if(differentHandles || endOfScroll)
1067       {
1068         eventData.mUpdateHighlightBox           = true;
1069         eventData.mUpdateRightSelectionPosition = !isSmoothHandlePanEnabled;
1070         eventData.mUpdateLeftSelectionPosition  = isSmoothHandlePanEnabled;
1071         eventData.mRightSelectionPosition       = handlePosition;
1072       }
1073     }
1074
1075     if(eventData.mUpdateLeftSelectionPosition || eventData.mUpdateRightSelectionPosition)
1076     {
1077       impl.RepositionSelectionHandles();
1078
1079       eventData.mScrollAfterUpdatePosition = !isSmoothHandlePanEnabled;
1080
1081       if(impl.mSelectableControlInterface != nullptr)
1082       {
1083         impl.mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, eventData.mLeftSelectionPosition, eventData.mRightSelectionPosition);
1084       }
1085     }
1086   }
1087   eventData.mDecoratorUpdated = true;
1088 }
1089
1090 } // namespace Text
1091
1092 } // namespace Toolkit
1093
1094 } // namespace Dali