804492397c2fa0cd4dfee291da086982a51e10f1
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / text / controller / text-controller-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/controller/text-controller-event-handler.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/devel-api/adaptor-framework/key-devel.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/integration-api/trace.h>
25
26 // INTERNAL INCLUDES
27 #include <dali-toolkit/internal/text/controller/text-controller-impl.h>
28 #include <dali-toolkit/internal/text/controller/text-controller-placeholder-handler.h>
29 #include <dali-toolkit/internal/text/controller/text-controller-text-updater.h>
30 #include <dali-toolkit/internal/text/cursor-helper-functions.h>
31 #include <dali-toolkit/internal/text/text-editable-control-interface.h>
32
33 namespace
34 {
35 #if defined(DEBUG_ENABLED)
36 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
37 #endif
38
39 DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_PERFORMANCE_MARKER, false);
40
41 const char* KEY_C_NAME      = "c";
42 const char* KEY_V_NAME      = "v";
43 const char* KEY_X_NAME      = "x";
44 const char* KEY_A_NAME      = "a";
45 const char* KEY_INSERT_NAME = "Insert";
46
47 } // namespace
48
49 namespace Dali
50 {
51 namespace Toolkit
52 {
53 namespace Text
54 {
55 void Controller::EventHandler::KeyboardFocusGainEvent(Controller& controller)
56 {
57   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected KeyboardFocusGainEvent");
58
59   if(NULL != controller.mImpl->mEventData)
60   {
61     if((EventData::INACTIVE == controller.mImpl->mEventData->mState) ||
62        (EventData::INTERRUPTED == controller.mImpl->mEventData->mState))
63     {
64       controller.mImpl->ChangeState(EventData::EDITING);
65       controller.mImpl->mEventData->mUpdateCursorPosition      = true; //If editing started without tap event, cursor update must be triggered.
66       controller.mImpl->mEventData->mUpdateInputStyle          = true;
67       controller.mImpl->mEventData->mScrollAfterUpdatePosition = true;
68     }
69     controller.mImpl->NotifyInputMethodContextMultiLineStatus();
70     if(controller.mImpl->IsShowingPlaceholderText())
71     {
72       // Show alternative placeholder-text when editing
73       PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
74     }
75
76     controller.mImpl->RequestRelayout();
77   }
78 }
79
80 void Controller::EventHandler::KeyboardFocusLostEvent(Controller& controller)
81 {
82   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected KeyboardFocusLostEvent");
83
84   if(NULL != controller.mImpl->mEventData)
85   {
86     if(EventData::INTERRUPTED != controller.mImpl->mEventData->mState)
87     {
88       // Init selection position
89       if(controller.mImpl->mEventData->mState == EventData::SELECTING)
90       {
91         uint32_t oldStart, oldEnd;
92         oldStart = controller.mImpl->mEventData->mLeftSelectionPosition;
93         oldEnd   = controller.mImpl->mEventData->mRightSelectionPosition;
94
95         controller.mImpl->mEventData->mLeftSelectionPosition  = controller.mImpl->mEventData->mPrimaryCursorPosition;
96         controller.mImpl->mEventData->mRightSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
97
98         if(controller.mImpl->mSelectableControlInterface != nullptr)
99         {
100           controller.mImpl->mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, controller.mImpl->mEventData->mPrimaryCursorPosition, controller.mImpl->mEventData->mPrimaryCursorPosition);
101         }
102       }
103
104       controller.mImpl->ChangeState(EventData::INACTIVE);
105
106       if(!controller.mImpl->IsShowingRealText())
107       {
108         // Revert to regular placeholder-text when not editing
109         PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
110       }
111     }
112   }
113   controller.mImpl->RequestRelayout();
114 }
115
116 bool Controller::EventHandler::KeyEvent(Controller& controller, const Dali::KeyEvent& keyEvent)
117 {
118   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected KeyEvent");
119
120   bool textChanged    = false;
121   bool relayoutNeeded = false;
122   bool isEditable     = controller.IsEditable() && controller.IsUserInteractionEnabled();
123
124   if((NULL != controller.mImpl->mEventData) &&
125      (keyEvent.GetState() == KeyEvent::DOWN))
126   {
127     int                keyCode   = keyEvent.GetKeyCode();
128     const std::string& keyString = keyEvent.GetKeyString();
129     const std::string  keyName   = keyEvent.GetKeyName();
130     // Key will produce same logical-key value when ctrl
131     // is down, regardless of language layout
132     const std::string logicalKey = keyEvent.GetLogicalKey();
133
134     const bool isNullKey = (0 == keyCode) && (keyString.empty());
135
136     // Pre-process to separate modifying events from non-modifying input events.
137     if(isNullKey)
138     {
139       // In some platforms arrive key events with no key code.
140       // Do nothing.
141       return false;
142     }
143     else if(Dali::DALI_KEY_ESCAPE == keyCode || Dali::DALI_KEY_BACK == keyCode || Dali::DALI_KEY_SEARCH == keyCode)
144     {
145       // Do nothing
146       return false;
147     }
148     else if((Dali::DALI_KEY_CURSOR_LEFT == keyCode) ||
149             (Dali::DALI_KEY_CURSOR_RIGHT == keyCode) ||
150             (Dali::DALI_KEY_CURSOR_UP == keyCode) ||
151             (Dali::DALI_KEY_CURSOR_DOWN == keyCode))
152     {
153       // If don't have any text, do nothing.
154       if(!controller.mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters || !isEditable)
155       {
156         return false;
157       }
158
159       uint32_t cursorPosition     = controller.mImpl->mEventData->mPrimaryCursorPosition;
160       uint32_t numberOfCharacters = controller.mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
161       uint32_t cursorLine         = controller.mImpl->mModel->mVisualModel->GetLineOfCharacter(cursorPosition);
162       uint32_t numberOfLines      = controller.mImpl->mModel->GetNumberOfLines();
163
164       // Logic to determine whether this text control will lose focus or not.
165       if((Dali::DALI_KEY_CURSOR_LEFT == keyCode && 0 == cursorPosition && !keyEvent.IsShiftModifier()) ||
166          (Dali::DALI_KEY_CURSOR_RIGHT == keyCode && numberOfCharacters == cursorPosition && !keyEvent.IsShiftModifier()) ||
167          (Dali::DALI_KEY_CURSOR_DOWN == keyCode && cursorLine == numberOfLines - 1) ||
168          (Dali::DALI_KEY_CURSOR_DOWN == keyCode && numberOfCharacters == cursorPosition && cursorLine - 1 == numberOfLines - 1) ||
169          (Dali::DALI_KEY_CURSOR_UP == keyCode && cursorLine == 0) ||
170          (Dali::DALI_KEY_CURSOR_UP == keyCode && numberOfCharacters == cursorPosition && cursorLine == 1))
171       {
172         // Release the active highlight.
173         if(controller.mImpl->mEventData->mState == EventData::SELECTING)
174         {
175           uint32_t oldStart, oldEnd;
176           oldStart = controller.mImpl->mEventData->mLeftSelectionPosition;
177           oldEnd   = controller.mImpl->mEventData->mRightSelectionPosition;
178
179           controller.mImpl->ChangeState(EventData::EDITING);
180
181           // Update selection position.
182           controller.mImpl->mEventData->mLeftSelectionPosition  = controller.mImpl->mEventData->mPrimaryCursorPosition;
183           controller.mImpl->mEventData->mRightSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
184           controller.mImpl->mEventData->mUpdateCursorPosition   = true;
185
186           if(controller.mImpl->mSelectableControlInterface != nullptr)
187           {
188             controller.mImpl->mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, controller.mImpl->mEventData->mLeftSelectionPosition, controller.mImpl->mEventData->mRightSelectionPosition);
189           }
190
191           controller.mImpl->RequestRelayout();
192         }
193         return false;
194       }
195
196       if(controller.mImpl->mEventData->mState == EventData::INACTIVE)
197       {
198         // Cursor position will be updated
199         controller.mImpl->ChangeState(EventData::EDITING);
200       }
201
202       controller.mImpl->mEventData->mCheckScrollAmount = true;
203       Event event(Event::CURSOR_KEY_EVENT);
204       event.p1.mInt  = keyCode;
205       event.p2.mBool = keyEvent.IsShiftModifier();
206       controller.mImpl->mEventData->mEventQueue.push_back(event);
207
208       // Will request for relayout.
209       relayoutNeeded = true;
210     }
211     else if(Dali::DevelKey::DALI_KEY_CONTROL_LEFT == keyCode || Dali::DevelKey::DALI_KEY_CONTROL_RIGHT == keyCode)
212     {
213       // Left or Right Control key event is received before Ctrl-C/V/X key event is received
214       // If not handle it here, any selected text will be deleted
215
216       // Do nothing
217       return false;
218     }
219     else if(keyEvent.IsCtrlModifier() && !keyEvent.IsShiftModifier() && isEditable)
220     {
221       bool consumed = false;
222       if(keyName == KEY_C_NAME || keyName == KEY_INSERT_NAME || logicalKey == KEY_C_NAME || logicalKey == KEY_INSERT_NAME)
223       {
224         if(!keyEvent.IsRepeat())
225         {
226           // Ctrl-C or Ctrl+Insert to copy the selected text
227           controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::COPY);
228         }
229         consumed = true;
230       }
231       else if(keyName == KEY_V_NAME || logicalKey == KEY_V_NAME)
232       {
233         // Ctrl-V to paste the copied text
234         controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::PASTE);
235         consumed = true;
236       }
237       else if(keyName == KEY_X_NAME || logicalKey == KEY_X_NAME)
238       {
239         // Ctrl-X to cut the selected text
240         controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::CUT);
241         consumed = true;
242       }
243       else if(keyName == KEY_A_NAME || logicalKey == KEY_A_NAME)
244       {
245         // Ctrl-A to select All the text
246         controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::SELECT_ALL);
247         consumed = true;
248       }
249       return consumed;
250     }
251     else if((Dali::DALI_KEY_BACKSPACE == keyCode) ||
252             (Dali::DevelKey::DALI_KEY_DELETE == keyCode))
253     {
254       textChanged = DeleteEvent(controller, keyCode);
255
256       // Will request for relayout.
257       relayoutNeeded = true;
258     }
259     else if(IsKey(keyEvent, Dali::DALI_KEY_POWER) ||
260             IsKey(keyEvent, Dali::DALI_KEY_MENU) ||
261             IsKey(keyEvent, Dali::DALI_KEY_HOME))
262     {
263       // Power key/Menu/Home key behaviour does not allow edit mode to resume.
264       controller.mImpl->ChangeState(EventData::INACTIVE);
265
266       // Will request for relayout.
267       relayoutNeeded = true;
268
269       // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
270     }
271     else if((Dali::DALI_KEY_SHIFT_LEFT == keyCode) || (Dali::DALI_KEY_SHIFT_RIGHT == keyCode))
272     {
273       // DALI_KEY_SHIFT_LEFT or DALI_KEY_SHIFT_RIGHT is the key code for Shift. It's sent (by the InputMethodContext?) when the predictive text is enabled
274       // and a character is typed after the type of a upper case latin character.
275
276       // Do nothing.
277       return false;
278     }
279     else if((Dali::DALI_KEY_VOLUME_UP == keyCode) || (Dali::DALI_KEY_VOLUME_DOWN == keyCode))
280     {
281       // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
282       // Do nothing.
283       return false;
284     }
285     else
286     {
287       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::KeyEvent %p keyString %s\n", &controller, keyString.c_str());
288       if(!isEditable) return false;
289
290       std::string refinedKey = keyString;
291       if(controller.mImpl->mInputFilter != NULL && !refinedKey.empty())
292       {
293         bool accepted = false;
294         bool rejected = false;
295         accepted      = controller.mImpl->mInputFilter->Contains(Toolkit::InputFilter::Property::ACCEPTED, keyString);
296         rejected      = controller.mImpl->mInputFilter->Contains(Toolkit::InputFilter::Property::REJECTED, keyString);
297
298         if(!accepted)
299         {
300           // The filtered key is set to empty.
301           refinedKey = "";
302           // Signal emits when the character to be inserted is filtered by the accepted filter.
303           controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::ACCEPTED);
304         }
305         if(rejected)
306         {
307           // The filtered key is set to empty.
308           refinedKey = "";
309           // Signal emits when the character to be inserted is filtered by the rejected filter.
310           controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::REJECTED);
311         }
312       }
313
314       if(!refinedKey.empty())
315       {
316         // InputMethodContext is no longer handling key-events
317         controller.mImpl->ClearPreEditFlag();
318
319         TextUpdater::InsertText(controller, refinedKey, COMMIT);
320
321         textChanged = true;
322
323         // Will request for relayout.
324         relayoutNeeded = true;
325       }
326     }
327
328     if((controller.mImpl->mEventData->mState != EventData::INTERRUPTED) &&
329        (controller.mImpl->mEventData->mState != EventData::INACTIVE) &&
330        (!isNullKey) &&
331        (Dali::DALI_KEY_SHIFT_LEFT != keyCode) &&
332        (Dali::DALI_KEY_SHIFT_RIGHT != keyCode) &&
333        (Dali::DALI_KEY_VOLUME_UP != keyCode) &&
334        (Dali::DALI_KEY_VOLUME_DOWN != keyCode))
335     {
336       // Should not change the state if the key is the shift send by the InputMethodContext.
337       // Otherwise, when the state is SELECTING the text controller can't send the right
338       // surrounding info to the InputMethodContext.
339       controller.mImpl->ChangeState(EventData::EDITING);
340
341       // Will request for relayout.
342       relayoutNeeded = true;
343     }
344
345     if(relayoutNeeded)
346     {
347       controller.mImpl->RequestRelayout();
348     }
349   }
350   else if((NULL != controller.mImpl->mEventData) && (keyEvent.GetState() == KeyEvent::UP))
351   {
352     // Handles specific keys that require event propagation.
353     if(Dali::DALI_KEY_BACK == keyEvent.GetKeyCode())
354     {
355       // Do nothing
356       return false;
357     }
358   }
359
360   if(textChanged &&
361      (NULL != controller.mImpl->mEditableControlInterface))
362   {
363     // Do this last since it provides callbacks into application code
364     controller.mImpl->mEditableControlInterface->TextChanged(false);
365   }
366
367   return true;
368 }
369
370 void Controller::EventHandler::AnchorEvent(Controller& controller, float x, float y)
371 {
372   if(!controller.mImpl->mMarkupProcessorEnabled ||
373      !controller.mImpl->mModel->mLogicalModel->mAnchors.Count() ||
374      !controller.mImpl->IsShowingRealText())
375   {
376     return;
377   }
378
379   CharacterIndex cursorPosition = 0u;
380
381   // Convert from control's coords to text's coords.
382
383   const float xPosition = x - controller.mImpl->mModel->mScrollPosition.x;
384   const float yPosition = y - controller.mImpl->mModel->mScrollPosition.y;
385
386   Vector2 visualTransformOffset = controller.mImpl->mModel->mVisualTransformOffset;
387
388   // Whether to touch point hits on a glyph.
389   bool matchedCharacter = false;
390   cursorPosition        = Text::GetClosestCursorIndex(controller.mImpl->mModel->mVisualModel,
391                                                controller.mImpl->mModel->mLogicalModel,
392                                                controller.mImpl->mMetrics,
393                                                xPosition,
394                                                yPosition - visualTransformOffset.y,
395                                                CharacterHitTest::TAP,
396                                                matchedCharacter);
397
398   for(auto& anchor : controller.mImpl->mModel->mLogicalModel->mAnchors)
399   {
400     // Anchor clicked if the calculated cursor position is within the range of anchor.
401     if(cursorPosition >= anchor.startIndex && cursorPosition < anchor.endIndex)
402     {
403       if(controller.mImpl->mAnchorControlInterface)
404       {
405         if(!anchor.isClicked)
406         {
407           anchor.isClicked = true;
408           // TODO: in mutable text, the anchor color and underline run index should be able to be updated.
409           if(!controller.IsEditable())
410           {
411             // If there is a markup clicked color attribute, use it. Otherwise, use the property color.
412             if(controller.mImpl->mModel->mLogicalModel->mColorRuns.Count() > anchor.colorRunIndex)
413             {
414               ColorRun& colorRun = *(controller.mImpl->mModel->mLogicalModel->mColorRuns.Begin() + anchor.colorRunIndex);
415               colorRun.color = anchor.isMarkupClickedColorSet ? anchor.markupClickedColor : controller.mImpl->mAnchorClickedColor;
416             }
417             if(controller.mImpl->mModel->mLogicalModel->mUnderlinedCharacterRuns.Count() > anchor.underlinedCharacterRunIndex)
418             {
419               UnderlinedCharacterRun& underlineRun = *(controller.mImpl->mModel->mLogicalModel->mUnderlinedCharacterRuns.Begin() + anchor.underlinedCharacterRunIndex);
420               underlineRun.properties.color = anchor.isMarkupClickedColorSet ? anchor.markupClickedColor : controller.mImpl->mAnchorClickedColor;
421             }
422
423             controller.mImpl->ClearFontData();
424             controller.mImpl->mOperationsPending = static_cast<OperationsMask>(controller.mImpl->mOperationsPending | COLOR);
425             controller.mImpl->RequestRelayout();
426           }
427         }
428
429         std::string href = anchor.href == nullptr ? "" : anchor.href;
430         controller.mImpl->mAnchorControlInterface->AnchorClicked(href);
431         break;
432       }
433     }
434   }
435 }
436
437 void Controller::EventHandler::TapEvent(Controller& controller, unsigned int tapCount, float x, float y)
438 {
439   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected TapEvent");
440
441   if(NULL != controller.mImpl->mEventData)
442   {
443     DALI_LOG_INFO(gLogFilter, Debug::Concise, "TapEvent state:%d \n", controller.mImpl->mEventData->mState);
444     EventData::State state(controller.mImpl->mEventData->mState);
445     bool             relayoutNeeded(false); // to avoid unnecessary relayouts when tapping an empty text-field
446
447     if(controller.mImpl->IsClipboardVisible())
448     {
449       if(EventData::INACTIVE == state || EventData::EDITING == state)
450       {
451         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE);
452       }
453       relayoutNeeded = true;
454     }
455     else if(1u == tapCount)
456     {
457       if(EventData::EDITING_WITH_POPUP == state || EventData::EDITING_WITH_PASTE_POPUP == state)
458       {
459         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE); // If Popup shown hide it here so can be shown again if required.
460       }
461
462       if(controller.mImpl->IsShowingRealText() && (EventData::INACTIVE != state))
463       {
464         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE);
465         relayoutNeeded = true;
466       }
467       else
468       {
469         if(controller.mImpl->IsShowingPlaceholderText() && !controller.mImpl->IsFocusedPlaceholderAvailable())
470         {
471           // Hide placeholder text
472           TextUpdater::ResetText(controller);
473         }
474
475         if(EventData::INACTIVE == state)
476         {
477           controller.mImpl->ChangeState(EventData::EDITING);
478         }
479         else if(!controller.mImpl->IsClipboardEmpty())
480         {
481           controller.mImpl->ChangeState(EventData::EDITING_WITH_POPUP);
482         }
483         relayoutNeeded = true;
484       }
485     }
486     else if(2u == tapCount)
487     {
488       if(controller.mImpl->mEventData->mSelectionEnabled &&
489          controller.mImpl->IsShowingRealText())
490       {
491         relayoutNeeded                                       = true;
492         controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
493         controller.mImpl->mEventData->mIsRightHandleSelected = true;
494       }
495     }
496
497     // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
498     if(relayoutNeeded)
499     {
500       Event event(Event::TAP_EVENT);
501       event.p1.mUint  = tapCount;
502       event.p2.mFloat = x;
503       event.p3.mFloat = y;
504       controller.mImpl->mEventData->mEventQueue.push_back(event);
505
506       controller.mImpl->RequestRelayout();
507     }
508   }
509
510   // Reset keyboard as tap event has occurred.
511   controller.mImpl->ResetInputMethodContext();
512 }
513
514 void Controller::EventHandler::PanEvent(Controller& controller, GestureState state, const Vector2& displacement)
515 {
516   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected PanEvent");
517
518   if(NULL != controller.mImpl->mEventData)
519   {
520     Event event(Event::PAN_EVENT);
521     event.p1.mInt   = static_cast<int>(state);
522     event.p2.mFloat = displacement.x;
523     event.p3.mFloat = displacement.y;
524     controller.mImpl->mEventData->mEventQueue.push_back(event);
525
526     controller.mImpl->RequestRelayout();
527   }
528 }
529
530 void Controller::EventHandler::LongPressEvent(Controller& controller, GestureState state, float x, float y)
531 {
532   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected LongPressEvent");
533
534   if((state == GestureState::STARTED) &&
535      (NULL != controller.mImpl->mEventData))
536   {
537     // The 1st long-press on inactive text-field is treated as tap
538     if(EventData::INACTIVE == controller.mImpl->mEventData->mState)
539     {
540       controller.mImpl->ChangeState(EventData::EDITING);
541
542       Event event(Event::TAP_EVENT);
543       event.p1.mUint  = 1;
544       event.p2.mFloat = x;
545       event.p3.mFloat = y;
546       controller.mImpl->mEventData->mEventQueue.push_back(event);
547
548       controller.mImpl->RequestRelayout();
549     }
550     else if(!controller.mImpl->IsShowingRealText())
551     {
552       Event event(Event::LONG_PRESS_EVENT);
553       event.p1.mInt   = static_cast<int>(state);
554       event.p2.mFloat = x;
555       event.p3.mFloat = y;
556       controller.mImpl->mEventData->mEventQueue.push_back(event);
557       controller.mImpl->RequestRelayout();
558     }
559     else if(!controller.mImpl->IsClipboardVisible())
560     {
561       // Reset the InputMethodContext to commit the pre-edit before selecting the text.
562       controller.mImpl->ResetInputMethodContext();
563
564       Event event(Event::LONG_PRESS_EVENT);
565       event.p1.mInt   = static_cast<int>(state);
566       event.p2.mFloat = x;
567       event.p3.mFloat = y;
568       controller.mImpl->mEventData->mEventQueue.push_back(event);
569       controller.mImpl->RequestRelayout();
570
571       controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
572       controller.mImpl->mEventData->mIsRightHandleSelected = true;
573     }
574   }
575 }
576
577 void Controller::EventHandler::SelectEvent(Controller& controller, float x, float y, SelectionType selectType)
578 {
579   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SelectEvent\n");
580
581   if(NULL != controller.mImpl->mEventData)
582   {
583     if(selectType == SelectionType::ALL)
584     {
585       Event event(Event::SELECT_ALL);
586       controller.mImpl->mEventData->mEventQueue.push_back(event);
587     }
588     else if(selectType == SelectionType::NONE)
589     {
590       Event event(Event::SELECT_NONE);
591       controller.mImpl->mEventData->mEventQueue.push_back(event);
592     }
593     else
594     {
595       Event event(Event::SELECT);
596       event.p2.mFloat = x;
597       event.p3.mFloat = y;
598       controller.mImpl->mEventData->mEventQueue.push_back(event);
599     }
600
601     controller.mImpl->mEventData->mCheckScrollAmount     = true;
602     controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
603     controller.mImpl->mEventData->mIsRightHandleSelected = true;
604     controller.mImpl->RequestRelayout();
605   }
606 }
607
608 void Controller::EventHandler::SelectEvent(Controller& controller, const uint32_t start, const uint32_t end, SelectionType selectType)
609 {
610   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SelectEvent\n");
611
612   if(NULL != controller.mImpl->mEventData)
613   {
614     if(selectType == SelectionType::RANGE)
615     {
616       Event event(Event::SELECT_RANGE);
617       event.p2.mUint = start;
618       event.p3.mUint = end;
619       controller.mImpl->mEventData->mEventQueue.push_back(event);
620     }
621
622     controller.mImpl->mEventData->mCheckScrollAmount     = true;
623     controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
624     controller.mImpl->mEventData->mIsRightHandleSelected = true;
625     controller.mImpl->RequestRelayout();
626   }
627 }
628
629 void Controller::EventHandler::ProcessModifyEvents(Controller& controller)
630 {
631   Vector<ModifyEvent>& events = controller.mImpl->mModifyEvents;
632
633   if(0u == events.Count())
634   {
635     // Nothing to do.
636     return;
637   }
638
639   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_MODIFY_EVENTS");
640
641   for(Vector<ModifyEvent>::ConstIterator it    = events.Begin(),
642                                          endIt = events.End();
643       it != endIt;
644       ++it)
645   {
646     const ModifyEvent& event = *it;
647
648     if(ModifyEvent::TEXT_REPLACED == event.type)
649     {
650       // A (single) replace event should come first, otherwise we wasted time processing NOOP events
651       DALI_ASSERT_DEBUG(it == events.Begin() && "Unexpected TEXT_REPLACED event");
652
653       TextReplacedEvent(controller);
654     }
655     else if(ModifyEvent::TEXT_INSERTED == event.type)
656     {
657       TextInsertedEvent(controller);
658     }
659     else if(ModifyEvent::TEXT_DELETED == event.type)
660     {
661       // Placeholder-text cannot be deleted
662       if(!controller.mImpl->IsShowingPlaceholderText())
663       {
664         TextDeletedEvent(controller);
665       }
666     }
667   }
668
669   if(NULL != controller.mImpl->mEventData)
670   {
671     uint32_t oldStart, oldEnd;
672     oldStart = controller.mImpl->mEventData->mLeftSelectionPosition;
673     oldEnd   = controller.mImpl->mEventData->mRightSelectionPosition;
674
675     // When the text is being modified, delay cursor blinking
676     controller.mImpl->mEventData->mDecorator->DelayCursorBlink();
677
678     // Update selection position after modifying the text
679     controller.mImpl->mEventData->mLeftSelectionPosition  = controller.mImpl->mEventData->mPrimaryCursorPosition;
680     controller.mImpl->mEventData->mRightSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
681
682     if(controller.mImpl->mSelectableControlInterface != nullptr && controller.mImpl->mEventData->mState == EventData::SELECTING)
683     {
684       controller.mImpl->mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, controller.mImpl->mEventData->mLeftSelectionPosition, controller.mImpl->mEventData->mRightSelectionPosition);
685     }
686   }
687
688   // DISCARD temporary text
689   events.Clear();
690 }
691
692 void Controller::EventHandler::TextReplacedEvent(Controller& controller)
693 {
694   // The natural size needs to be re-calculated.
695   controller.mImpl->mRecalculateNaturalSize = true;
696
697   // The text direction needs to be updated.
698   controller.mImpl->mUpdateTextDirection = true;
699
700   // Apply modifications to the model
701   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
702 }
703
704 void Controller::EventHandler::TextInsertedEvent(Controller& controller)
705 {
706   DALI_ASSERT_DEBUG(NULL != controller.mImpl->mEventData && "Unexpected TextInsertedEvent");
707
708   if(NULL == controller.mImpl->mEventData)
709   {
710     return;
711   }
712
713   controller.mImpl->mEventData->mCheckScrollAmount = true;
714
715   // The natural size needs to be re-calculated.
716   controller.mImpl->mRecalculateNaturalSize = true;
717
718   // The text direction needs to be updated.
719   controller.mImpl->mUpdateTextDirection = true;
720
721   // Apply modifications to the model; TODO - Optimize this
722   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
723 }
724
725 void Controller::EventHandler::TextDeletedEvent(Controller& controller)
726 {
727   DALI_ASSERT_DEBUG(NULL != controller.mImpl->mEventData && "Unexpected TextDeletedEvent");
728
729   if(NULL == controller.mImpl->mEventData)
730   {
731     return;
732   }
733
734   if(!controller.IsEditable()) return;
735
736   controller.mImpl->mEventData->mCheckScrollAmount = true;
737
738   // The natural size needs to be re-calculated.
739   controller.mImpl->mRecalculateNaturalSize = true;
740
741   // The text direction needs to be updated.
742   controller.mImpl->mUpdateTextDirection = true;
743
744   // Apply modifications to the model; TODO - Optimize this
745   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
746 }
747
748 bool Controller::EventHandler::DeleteEvent(Controller& controller, int keyCode)
749 {
750   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::KeyEvent %p KeyCode : %d \n", &controller, keyCode);
751
752   bool removed = false;
753
754   if(NULL == controller.mImpl->mEventData)
755   {
756     return removed;
757   }
758
759   if(!controller.IsEditable()) return false;
760
761   // InputMethodContext is no longer handling key-events
762   controller.mImpl->ClearPreEditFlag();
763
764   if(EventData::SELECTING == controller.mImpl->mEventData->mState)
765   {
766     removed = TextUpdater::RemoveSelectedText(controller);
767   }
768   else if((controller.mImpl->mEventData->mPrimaryCursorPosition > 0) && (keyCode == Dali::DALI_KEY_BACKSPACE))
769   {
770     // Remove the character before the current cursor position
771     removed = TextUpdater::RemoveText(controller, -1, 1, UPDATE_INPUT_STYLE);
772   }
773   else if((controller.mImpl->mEventData->mPrimaryCursorPosition < controller.mImpl->mModel->mLogicalModel->mText.Count()) &&
774           (keyCode == Dali::DevelKey::DALI_KEY_DELETE))
775   {
776     // Remove the character after the current cursor position
777     removed = TextUpdater::RemoveText(controller, 0, 1, UPDATE_INPUT_STYLE);
778   }
779
780   if(removed)
781   {
782     if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
783        !controller.mImpl->IsPlaceholderAvailable())
784     {
785       controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
786     }
787     else
788     {
789       PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
790     }
791     controller.mImpl->mEventData->mUpdateCursorPosition = true;
792     controller.mImpl->mEventData->mScrollAfterDelete    = true;
793   }
794
795   return removed;
796 }
797
798 InputMethodContext::CallbackData Controller::EventHandler::OnInputMethodContextEvent(Controller& controller, InputMethodContext& inputMethodContext, const InputMethodContext::EventData& inputMethodContextEvent)
799 {
800   // Whether the text needs to be relaid-out.
801   bool requestRelayout = false;
802
803   // Whether to retrieve the text and cursor position to be sent to the InputMethodContext.
804   bool retrieveText   = false;
805   bool retrieveCursor = false;
806
807   switch(inputMethodContextEvent.eventName)
808   {
809     case InputMethodContext::COMMIT:
810     {
811       TextUpdater::InsertText(controller, inputMethodContextEvent.predictiveString, Text::Controller::COMMIT);
812       requestRelayout = true;
813       retrieveCursor  = true;
814       break;
815     }
816     case InputMethodContext::PRE_EDIT:
817     {
818       TextUpdater::InsertText(controller, inputMethodContextEvent.predictiveString, Text::Controller::PRE_EDIT);
819       requestRelayout = true;
820       retrieveCursor  = true;
821       break;
822     }
823     case InputMethodContext::DELETE_SURROUNDING:
824     {
825       const bool textDeleted = TextUpdater::RemoveText(controller,
826                                                        inputMethodContextEvent.cursorOffset,
827                                                        inputMethodContextEvent.numberOfChars,
828                                                        DONT_UPDATE_INPUT_STYLE);
829
830       if(textDeleted)
831       {
832         if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
833            !controller.mImpl->IsPlaceholderAvailable())
834         {
835           controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
836         }
837         else
838         {
839           PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
840         }
841         controller.mImpl->mEventData->mUpdateCursorPosition = true;
842         controller.mImpl->mEventData->mScrollAfterDelete    = true;
843
844         requestRelayout = true;
845       }
846       break;
847     }
848     case InputMethodContext::GET_SURROUNDING:
849     {
850       retrieveText   = true;
851       retrieveCursor = true;
852       break;
853     }
854     case InputMethodContext::PRIVATE_COMMAND:
855     {
856       // PRIVATECOMMAND event is just for getting the private command message
857       retrieveText   = true;
858       retrieveCursor = true;
859       break;
860     }
861     case InputMethodContext::SELECTION_SET:
862     {
863       uint32_t start = static_cast<uint32_t>(inputMethodContextEvent.startIndex);
864       uint32_t end   = static_cast<uint32_t>(inputMethodContextEvent.endIndex);
865       if(start == end)
866       {
867         controller.SetPrimaryCursorPosition(start, true);
868       }
869       else
870       {
871         controller.SelectText(start, end);
872       }
873
874       break;
875     }
876     case InputMethodContext::VOID:
877     {
878       // do nothing
879       break;
880     }
881   } // end switch
882
883   if(requestRelayout)
884   {
885     controller.mImpl->mOperationsPending = ALL_OPERATIONS;
886     controller.mImpl->RequestRelayout();
887   }
888
889   std::string    text;
890   CharacterIndex cursorPosition      = 0u;
891
892   if(retrieveCursor)
893   {
894     cursorPosition = controller.mImpl->GetLogicalCursorPosition();
895   }
896
897   if(retrieveText)
898   {
899     if(!controller.mImpl->IsShowingPlaceholderText())
900     {
901       // Retrieves the normal text string.
902       controller.mImpl->GetText(0u, text);
903     }
904     else
905     {
906       // When the current text is Placeholder Text, the surrounding text should be empty string.
907       // It means DALi should send empty string ("") to IME.
908       text = "";
909     }
910   }
911
912   InputMethodContext::CallbackData callbackData((retrieveText || retrieveCursor), cursorPosition, text, false);
913
914   if(requestRelayout &&
915      (NULL != controller.mImpl->mEditableControlInterface))
916   {
917     // Do this last since it provides callbacks into application code
918     controller.mImpl->mEditableControlInterface->TextChanged(false);
919   }
920
921   return callbackData;
922 }
923
924 void Controller::EventHandler::PasteClipboardItemEvent(Controller& controller, const std::string& text)
925 {
926   // Commit the current pre-edit text; the contents of the clipboard should be appended
927   controller.mImpl->ResetInputMethodContext();
928
929   // Temporary disable hiding clipboard
930   controller.mImpl->SetClipboardHideEnable(false);
931
932   // Paste
933   TextUpdater::PasteText(controller, text);
934
935   controller.mImpl->SetClipboardHideEnable(true);
936 }
937
938 void Controller::EventHandler::DecorationEvent(Controller& controller, HandleType handleType, HandleState state, float x, float y)
939 {
940   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected DecorationEvent");
941
942   if(NULL != controller.mImpl->mEventData)
943   {
944     switch(handleType)
945     {
946       case GRAB_HANDLE:
947       {
948         Event event(Event::GRAB_HANDLE_EVENT);
949         event.p1.mUint  = state;
950         event.p2.mFloat = x;
951         event.p3.mFloat = y;
952
953         controller.mImpl->mEventData->mEventQueue.push_back(event);
954         break;
955       }
956       case LEFT_SELECTION_HANDLE:
957       {
958         Event event(Event::LEFT_SELECTION_HANDLE_EVENT);
959         event.p1.mUint  = state;
960         event.p2.mFloat = x;
961         event.p3.mFloat = y;
962
963         controller.mImpl->mEventData->mEventQueue.push_back(event);
964         break;
965       }
966       case RIGHT_SELECTION_HANDLE:
967       {
968         Event event(Event::RIGHT_SELECTION_HANDLE_EVENT);
969         event.p1.mUint  = state;
970         event.p2.mFloat = x;
971         event.p3.mFloat = y;
972
973         controller.mImpl->mEventData->mEventQueue.push_back(event);
974         break;
975       }
976       case LEFT_SELECTION_HANDLE_MARKER:
977       case RIGHT_SELECTION_HANDLE_MARKER:
978       {
979         // Markers do not move the handles.
980         break;
981       }
982       case HANDLE_TYPE_COUNT:
983       {
984         DALI_ASSERT_DEBUG(!"Controller::HandleEvent. Unexpected handle type");
985       }
986     }
987
988     controller.mImpl->RequestRelayout();
989   }
990 }
991
992 void Controller::EventHandler::TextPopupButtonTouched(Controller& controller, Dali::Toolkit::TextSelectionPopup::Buttons button)
993 {
994   if(NULL == controller.mImpl->mEventData)
995   {
996     return;
997   }
998
999   switch(button)
1000   {
1001     case Toolkit::TextSelectionPopup::CUT:
1002     {
1003       controller.CutText();
1004       break;
1005     }
1006     case Toolkit::TextSelectionPopup::COPY:
1007     {
1008       controller.CopyText();
1009       break;
1010     }
1011     case Toolkit::TextSelectionPopup::PASTE:
1012     {
1013       controller.PasteText();
1014       break;
1015     }
1016     case Toolkit::TextSelectionPopup::SELECT:
1017     {
1018       const Vector2& currentCursorPosition = controller.mImpl->mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1019
1020       if(controller.mImpl->mEventData->mSelectionEnabled)
1021       {
1022         // Creates a SELECT event.
1023         SelectEvent(controller, currentCursorPosition.x, currentCursorPosition.y, SelectionType::INTERACTIVE);
1024       }
1025       break;
1026     }
1027     case Toolkit::TextSelectionPopup::SELECT_ALL:
1028     {
1029       // Creates a SELECT_ALL event
1030       SelectEvent(controller, 0.f, 0.f, SelectionType::ALL);
1031       break;
1032     }
1033     case Toolkit::TextSelectionPopup::CLIPBOARD:
1034     {
1035       controller.mImpl->ShowClipboard();
1036       break;
1037     }
1038     case Toolkit::TextSelectionPopup::NONE:
1039     {
1040       // Nothing to do.
1041       break;
1042     }
1043   }
1044 }
1045
1046 } // namespace Text
1047
1048 } // namespace Toolkit
1049
1050 } // namespace Dali