[dali_2.3.21] Merge branch 'devel/master'
[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, false);
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, false);
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                                                        false);
830
831       if(textDeleted)
832       {
833         if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
834            !controller.mImpl->IsPlaceholderAvailable())
835         {
836           controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
837         }
838         else
839         {
840           PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
841         }
842         controller.mImpl->mEventData->mUpdateCursorPosition = true;
843         controller.mImpl->mEventData->mScrollAfterDelete    = true;
844
845         requestRelayout = true;
846       }
847       break;
848     }
849     case InputMethodContext::GET_SURROUNDING:
850     {
851       retrieveText   = true;
852       retrieveCursor = true;
853       break;
854     }
855     case InputMethodContext::PRIVATE_COMMAND:
856     {
857       // PRIVATECOMMAND event is just for getting the private command message
858       retrieveText   = true;
859       retrieveCursor = true;
860       break;
861     }
862     case InputMethodContext::SELECTION_SET:
863     {
864       uint32_t start = static_cast<uint32_t>(inputMethodContextEvent.startIndex);
865       uint32_t end   = static_cast<uint32_t>(inputMethodContextEvent.endIndex);
866       if(start == end)
867       {
868         controller.SetPrimaryCursorPosition(start, true);
869       }
870       else
871       {
872         controller.SelectText(start, end);
873       }
874
875       break;
876     }
877     case InputMethodContext::VOID:
878     {
879       // do nothing
880       break;
881     }
882   } // end switch
883
884   if(requestRelayout)
885   {
886     controller.mImpl->mOperationsPending = ALL_OPERATIONS;
887     controller.mImpl->RequestRelayout();
888   }
889
890   std::string    text;
891   CharacterIndex cursorPosition      = 0u;
892
893   if(retrieveCursor)
894   {
895     cursorPosition = controller.mImpl->GetLogicalCursorPosition();
896   }
897
898   if(retrieveText)
899   {
900     if(!controller.mImpl->IsShowingPlaceholderText())
901     {
902       // Retrieves the normal text string.
903       controller.mImpl->GetText(0u, text);
904     }
905     else
906     {
907       // When the current text is Placeholder Text, the surrounding text should be empty string.
908       // It means DALi should send empty string ("") to IME.
909       text = "";
910     }
911   }
912
913   InputMethodContext::CallbackData callbackData((retrieveText || retrieveCursor), cursorPosition, text, false);
914
915   if(requestRelayout &&
916      (NULL != controller.mImpl->mEditableControlInterface))
917   {
918     // Do this last since it provides callbacks into application code
919     controller.mImpl->mEditableControlInterface->TextChanged(false);
920   }
921
922   return callbackData;
923 }
924
925 void Controller::EventHandler::PasteClipboardItemEvent(Controller& controller, const std::string& text)
926 {
927   // Commit the current pre-edit text; the contents of the clipboard should be appended
928   controller.mImpl->ResetInputMethodContext();
929
930   // Temporary disable hiding clipboard
931   controller.mImpl->SetClipboardHideEnable(false);
932
933   // Paste
934   TextUpdater::PasteText(controller, text);
935
936   controller.mImpl->SetClipboardHideEnable(true);
937 }
938
939 void Controller::EventHandler::DecorationEvent(Controller& controller, HandleType handleType, HandleState state, float x, float y)
940 {
941   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected DecorationEvent");
942
943   if(NULL != controller.mImpl->mEventData)
944   {
945     switch(handleType)
946     {
947       case GRAB_HANDLE:
948       {
949         Event event(Event::GRAB_HANDLE_EVENT);
950         event.p1.mUint  = state;
951         event.p2.mFloat = x;
952         event.p3.mFloat = y;
953
954         controller.mImpl->mEventData->mEventQueue.push_back(event);
955         break;
956       }
957       case LEFT_SELECTION_HANDLE:
958       {
959         Event event(Event::LEFT_SELECTION_HANDLE_EVENT);
960         event.p1.mUint  = state;
961         event.p2.mFloat = x;
962         event.p3.mFloat = y;
963
964         controller.mImpl->mEventData->mEventQueue.push_back(event);
965         break;
966       }
967       case RIGHT_SELECTION_HANDLE:
968       {
969         Event event(Event::RIGHT_SELECTION_HANDLE_EVENT);
970         event.p1.mUint  = state;
971         event.p2.mFloat = x;
972         event.p3.mFloat = y;
973
974         controller.mImpl->mEventData->mEventQueue.push_back(event);
975         break;
976       }
977       case LEFT_SELECTION_HANDLE_MARKER:
978       case RIGHT_SELECTION_HANDLE_MARKER:
979       {
980         // Markers do not move the handles.
981         break;
982       }
983       case HANDLE_TYPE_COUNT:
984       {
985         DALI_ASSERT_DEBUG(!"Controller::HandleEvent. Unexpected handle type");
986       }
987     }
988
989     controller.mImpl->RequestRelayout();
990   }
991 }
992
993 void Controller::EventHandler::TextPopupButtonTouched(Controller& controller, Dali::Toolkit::TextSelectionPopup::Buttons button)
994 {
995   if(NULL == controller.mImpl->mEventData)
996   {
997     return;
998   }
999
1000   switch(button)
1001   {
1002     case Toolkit::TextSelectionPopup::CUT:
1003     {
1004       controller.CutText();
1005       break;
1006     }
1007     case Toolkit::TextSelectionPopup::COPY:
1008     {
1009       controller.CopyText();
1010       break;
1011     }
1012     case Toolkit::TextSelectionPopup::PASTE:
1013     {
1014       controller.PasteText();
1015       break;
1016     }
1017     case Toolkit::TextSelectionPopup::SELECT:
1018     {
1019       const Vector2& currentCursorPosition = controller.mImpl->mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1020
1021       if(controller.mImpl->mEventData->mSelectionEnabled)
1022       {
1023         // Creates a SELECT event.
1024         SelectEvent(controller, currentCursorPosition.x, currentCursorPosition.y, SelectionType::INTERACTIVE);
1025       }
1026       break;
1027     }
1028     case Toolkit::TextSelectionPopup::SELECT_ALL:
1029     {
1030       // Creates a SELECT_ALL event
1031       SelectEvent(controller, 0.f, 0.f, SelectionType::ALL);
1032       break;
1033     }
1034     case Toolkit::TextSelectionPopup::CLIPBOARD:
1035     {
1036       controller.mImpl->ShowClipboard();
1037       break;
1038     }
1039     case Toolkit::TextSelectionPopup::NONE:
1040     {
1041       // Nothing to do.
1042       break;
1043     }
1044   }
1045 }
1046
1047 } // namespace Text
1048
1049 } // namespace Toolkit
1050
1051 } // namespace Dali