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