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