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