12fcd6902dfd0c077fd1f11cbbf8219bb5267691
[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         // Ctrl-C or Ctrl+Insert to copy the selected text
225         controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::COPY);
226         consumed = true;
227       }
228       else if(keyName == KEY_V_NAME || logicalKey == KEY_V_NAME)
229       {
230         // Ctrl-V to paste the copied text
231         controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::PASTE);
232         consumed = true;
233       }
234       else if(keyName == KEY_X_NAME || logicalKey == KEY_X_NAME)
235       {
236         // Ctrl-X to cut the selected text
237         controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::CUT);
238         consumed = true;
239       }
240       else if(keyName == KEY_A_NAME || logicalKey == KEY_A_NAME)
241       {
242         // Ctrl-A to select All the text
243         controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::SELECT_ALL);
244         consumed = true;
245       }
246       return consumed;
247     }
248     else if((Dali::DALI_KEY_BACKSPACE == keyCode) ||
249             (Dali::DevelKey::DALI_KEY_DELETE == keyCode))
250     {
251       textChanged = DeleteEvent(controller, keyCode);
252
253       // Will request for relayout.
254       relayoutNeeded = true;
255     }
256     else if(IsKey(keyEvent, Dali::DALI_KEY_POWER) ||
257             IsKey(keyEvent, Dali::DALI_KEY_MENU) ||
258             IsKey(keyEvent, Dali::DALI_KEY_HOME))
259     {
260       // Power key/Menu/Home key behaviour does not allow edit mode to resume.
261       controller.mImpl->ChangeState(EventData::INACTIVE);
262
263       // Will request for relayout.
264       relayoutNeeded = true;
265
266       // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
267     }
268     else if((Dali::DALI_KEY_SHIFT_LEFT == keyCode) || (Dali::DALI_KEY_SHIFT_RIGHT == keyCode))
269     {
270       // 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
271       // and a character is typed after the type of a upper case latin character.
272
273       // Do nothing.
274       return false;
275     }
276     else if((Dali::DALI_KEY_VOLUME_UP == keyCode) || (Dali::DALI_KEY_VOLUME_DOWN == keyCode))
277     {
278       // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
279       // Do nothing.
280       return false;
281     }
282     else
283     {
284       DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::KeyEvent %p keyString %s\n", &controller, keyString.c_str());
285       if(!isEditable) return false;
286
287       std::string refinedKey = keyString;
288       if(controller.mImpl->mInputFilter != NULL && !refinedKey.empty())
289       {
290         bool accepted = false;
291         bool rejected = false;
292         accepted      = controller.mImpl->mInputFilter->Contains(Toolkit::InputFilter::Property::ACCEPTED, keyString);
293         rejected      = controller.mImpl->mInputFilter->Contains(Toolkit::InputFilter::Property::REJECTED, keyString);
294
295         if(!accepted)
296         {
297           // The filtered key is set to empty.
298           refinedKey = "";
299           // Signal emits when the character to be inserted is filtered by the accepted filter.
300           controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::ACCEPTED);
301         }
302         if(rejected)
303         {
304           // The filtered key is set to empty.
305           refinedKey = "";
306           // Signal emits when the character to be inserted is filtered by the rejected filter.
307           controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::REJECTED);
308         }
309       }
310
311       if(!refinedKey.empty())
312       {
313         // InputMethodContext is no longer handling key-events
314         controller.mImpl->ClearPreEditFlag();
315
316         TextUpdater::InsertText(controller, refinedKey, COMMIT);
317
318         textChanged = true;
319
320         // Will request for relayout.
321         relayoutNeeded = true;
322       }
323     }
324
325     if((controller.mImpl->mEventData->mState != EventData::INTERRUPTED) &&
326        (controller.mImpl->mEventData->mState != EventData::INACTIVE) &&
327        (!isNullKey) &&
328        (Dali::DALI_KEY_SHIFT_LEFT != keyCode) &&
329        (Dali::DALI_KEY_SHIFT_RIGHT != keyCode) &&
330        (Dali::DALI_KEY_VOLUME_UP != keyCode) &&
331        (Dali::DALI_KEY_VOLUME_DOWN != keyCode))
332     {
333       // Should not change the state if the key is the shift send by the InputMethodContext.
334       // Otherwise, when the state is SELECTING the text controller can't send the right
335       // surrounding info to the InputMethodContext.
336       controller.mImpl->ChangeState(EventData::EDITING);
337
338       // Will request for relayout.
339       relayoutNeeded = true;
340     }
341
342     if(relayoutNeeded)
343     {
344       controller.mImpl->RequestRelayout();
345     }
346   }
347   else if((NULL != controller.mImpl->mEventData) && (keyEvent.GetState() == KeyEvent::UP))
348   {
349     // Handles specific keys that require event propagation.
350     if(Dali::DALI_KEY_BACK == keyEvent.GetKeyCode())
351     {
352       // Do nothing
353       return false;
354     }
355   }
356
357   if(textChanged &&
358      (NULL != controller.mImpl->mEditableControlInterface))
359   {
360     // Do this last since it provides callbacks into application code
361     controller.mImpl->mEditableControlInterface->TextChanged(false);
362   }
363
364   return true;
365 }
366
367 void Controller::EventHandler::AnchorEvent(Controller& controller, float x, float y)
368 {
369   if(!controller.mImpl->mMarkupProcessorEnabled ||
370      !controller.mImpl->mModel->mLogicalModel->mAnchors.Count() ||
371      !controller.mImpl->IsShowingRealText())
372   {
373     return;
374   }
375
376   CharacterIndex cursorPosition = 0u;
377
378   // Convert from control's coords to text's coords.
379   const float xPosition = x - controller.mImpl->mModel->mScrollPosition.x;
380   const float yPosition = y - controller.mImpl->mModel->mScrollPosition.y;
381
382   // Whether to touch point hits on a glyph.
383   bool matchedCharacter = false;
384   cursorPosition        = Text::GetClosestCursorIndex(controller.mImpl->mModel->mVisualModel,
385                                                controller.mImpl->mModel->mLogicalModel,
386                                                controller.mImpl->mMetrics,
387                                                xPosition,
388                                                yPosition,
389                                                CharacterHitTest::TAP,
390                                                matchedCharacter);
391
392   for(const auto& anchor : controller.mImpl->mModel->mLogicalModel->mAnchors)
393   {
394     // Anchor clicked if the calculated cursor position is within the range of anchor.
395     if(cursorPosition >= anchor.startIndex && cursorPosition < anchor.endIndex)
396     {
397       if(controller.mImpl->mAnchorControlInterface && anchor.href)
398       {
399         std::string href(anchor.href);
400         controller.mImpl->mAnchorControlInterface->AnchorClicked(href);
401         break;
402       }
403     }
404   }
405 }
406
407 void Controller::EventHandler::TapEvent(Controller& controller, unsigned int tapCount, float x, float y)
408 {
409   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected TapEvent");
410
411   if(NULL != controller.mImpl->mEventData)
412   {
413     DALI_LOG_INFO(gLogFilter, Debug::Concise, "TapEvent state:%d \n", controller.mImpl->mEventData->mState);
414     EventData::State state(controller.mImpl->mEventData->mState);
415     bool             relayoutNeeded(false); // to avoid unnecessary relayouts when tapping an empty text-field
416
417     if(controller.mImpl->IsClipboardVisible())
418     {
419       if(EventData::INACTIVE == state || EventData::EDITING == state)
420       {
421         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE);
422       }
423       relayoutNeeded = true;
424     }
425     else if(1u == tapCount)
426     {
427       if(EventData::EDITING_WITH_POPUP == state || EventData::EDITING_WITH_PASTE_POPUP == state)
428       {
429         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE); // If Popup shown hide it here so can be shown again if required.
430       }
431
432       if(controller.mImpl->IsShowingRealText() && (EventData::INACTIVE != state))
433       {
434         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE);
435         relayoutNeeded = true;
436       }
437       else
438       {
439         if(controller.mImpl->IsShowingPlaceholderText() && !controller.mImpl->IsFocusedPlaceholderAvailable())
440         {
441           // Hide placeholder text
442           TextUpdater::ResetText(controller);
443         }
444
445         if(EventData::INACTIVE == state)
446         {
447           controller.mImpl->ChangeState(EventData::EDITING);
448         }
449         else if(!controller.mImpl->IsClipboardEmpty())
450         {
451           controller.mImpl->ChangeState(EventData::EDITING_WITH_POPUP);
452         }
453         relayoutNeeded = true;
454       }
455     }
456     else if(2u == tapCount)
457     {
458       if(controller.mImpl->mEventData->mSelectionEnabled &&
459          controller.mImpl->IsShowingRealText())
460       {
461         relayoutNeeded                                       = true;
462         controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
463         controller.mImpl->mEventData->mIsRightHandleSelected = true;
464       }
465     }
466
467     // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
468     if(relayoutNeeded)
469     {
470       Event event(Event::TAP_EVENT);
471       event.p1.mUint  = tapCount;
472       event.p2.mFloat = x;
473       event.p3.mFloat = y;
474       controller.mImpl->mEventData->mEventQueue.push_back(event);
475
476       controller.mImpl->RequestRelayout();
477     }
478   }
479
480   // Reset keyboard as tap event has occurred.
481   controller.mImpl->ResetInputMethodContext();
482 }
483
484 void Controller::EventHandler::PanEvent(Controller& controller, GestureState state, const Vector2& displacement)
485 {
486   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected PanEvent");
487
488   if(NULL != controller.mImpl->mEventData)
489   {
490     Event event(Event::PAN_EVENT);
491     event.p1.mInt   = static_cast<int>(state);
492     event.p2.mFloat = displacement.x;
493     event.p3.mFloat = displacement.y;
494     controller.mImpl->mEventData->mEventQueue.push_back(event);
495
496     controller.mImpl->RequestRelayout();
497   }
498 }
499
500 void Controller::EventHandler::LongPressEvent(Controller& controller, GestureState state, float x, float y)
501 {
502   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected LongPressEvent");
503
504   if((state == GestureState::STARTED) &&
505      (NULL != controller.mImpl->mEventData))
506   {
507     // The 1st long-press on inactive text-field is treated as tap
508     if(EventData::INACTIVE == controller.mImpl->mEventData->mState)
509     {
510       controller.mImpl->ChangeState(EventData::EDITING);
511
512       Event event(Event::TAP_EVENT);
513       event.p1.mUint  = 1;
514       event.p2.mFloat = x;
515       event.p3.mFloat = y;
516       controller.mImpl->mEventData->mEventQueue.push_back(event);
517
518       controller.mImpl->RequestRelayout();
519     }
520     else if(!controller.mImpl->IsShowingRealText())
521     {
522       Event event(Event::LONG_PRESS_EVENT);
523       event.p1.mInt   = static_cast<int>(state);
524       event.p2.mFloat = x;
525       event.p3.mFloat = y;
526       controller.mImpl->mEventData->mEventQueue.push_back(event);
527       controller.mImpl->RequestRelayout();
528     }
529     else if(!controller.mImpl->IsClipboardVisible())
530     {
531       // Reset the InputMethodContext to commit the pre-edit before selecting the text.
532       controller.mImpl->ResetInputMethodContext();
533
534       Event event(Event::LONG_PRESS_EVENT);
535       event.p1.mInt   = static_cast<int>(state);
536       event.p2.mFloat = x;
537       event.p3.mFloat = y;
538       controller.mImpl->mEventData->mEventQueue.push_back(event);
539       controller.mImpl->RequestRelayout();
540
541       controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
542       controller.mImpl->mEventData->mIsRightHandleSelected = true;
543     }
544   }
545 }
546
547 void Controller::EventHandler::SelectEvent(Controller& controller, float x, float y, SelectionType selectType)
548 {
549   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SelectEvent\n");
550
551   if(NULL != controller.mImpl->mEventData)
552   {
553     if(selectType == SelectionType::ALL)
554     {
555       Event event(Event::SELECT_ALL);
556       controller.mImpl->mEventData->mEventQueue.push_back(event);
557     }
558     else if(selectType == SelectionType::NONE)
559     {
560       Event event(Event::SELECT_NONE);
561       controller.mImpl->mEventData->mEventQueue.push_back(event);
562     }
563     else
564     {
565       Event event(Event::SELECT);
566       event.p2.mFloat = x;
567       event.p3.mFloat = y;
568       controller.mImpl->mEventData->mEventQueue.push_back(event);
569     }
570
571     controller.mImpl->mEventData->mCheckScrollAmount     = true;
572     controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
573     controller.mImpl->mEventData->mIsRightHandleSelected = true;
574     controller.mImpl->RequestRelayout();
575   }
576 }
577
578 void Controller::EventHandler::SelectEvent(Controller& controller, const uint32_t start, const uint32_t end, SelectionType selectType)
579 {
580   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SelectEvent\n");
581
582   if(NULL != controller.mImpl->mEventData)
583   {
584     if(selectType == SelectionType::RANGE)
585     {
586       Event event(Event::SELECT_RANGE);
587       event.p2.mUint = start;
588       event.p3.mUint = end;
589       controller.mImpl->mEventData->mEventQueue.push_back(event);
590     }
591
592     controller.mImpl->mEventData->mCheckScrollAmount     = true;
593     controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
594     controller.mImpl->mEventData->mIsRightHandleSelected = true;
595     controller.mImpl->RequestRelayout();
596   }
597 }
598
599 void Controller::EventHandler::ProcessModifyEvents(Controller& controller)
600 {
601   Vector<ModifyEvent>& events = controller.mImpl->mModifyEvents;
602
603   if(0u == events.Count())
604   {
605     // Nothing to do.
606     return;
607   }
608
609   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_MODIFY_EVENTS");
610
611   for(Vector<ModifyEvent>::ConstIterator it    = events.Begin(),
612                                          endIt = events.End();
613       it != endIt;
614       ++it)
615   {
616     const ModifyEvent& event = *it;
617
618     if(ModifyEvent::TEXT_REPLACED == event.type)
619     {
620       // A (single) replace event should come first, otherwise we wasted time processing NOOP events
621       DALI_ASSERT_DEBUG(it == events.Begin() && "Unexpected TEXT_REPLACED event");
622
623       TextReplacedEvent(controller);
624     }
625     else if(ModifyEvent::TEXT_INSERTED == event.type)
626     {
627       TextInsertedEvent(controller);
628     }
629     else if(ModifyEvent::TEXT_DELETED == event.type)
630     {
631       // Placeholder-text cannot be deleted
632       if(!controller.mImpl->IsShowingPlaceholderText())
633       {
634         TextDeletedEvent(controller);
635       }
636     }
637   }
638
639   if(NULL != controller.mImpl->mEventData)
640   {
641     uint32_t oldStart, oldEnd;
642     oldStart = controller.mImpl->mEventData->mLeftSelectionPosition;
643     oldEnd   = controller.mImpl->mEventData->mRightSelectionPosition;
644
645     // When the text is being modified, delay cursor blinking
646     controller.mImpl->mEventData->mDecorator->DelayCursorBlink();
647
648     // Update selection position after modifying the text
649     controller.mImpl->mEventData->mLeftSelectionPosition  = controller.mImpl->mEventData->mPrimaryCursorPosition;
650     controller.mImpl->mEventData->mRightSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
651
652     if(controller.mImpl->mSelectableControlInterface != nullptr && controller.mImpl->mEventData->mState == EventData::SELECTING)
653     {
654       controller.mImpl->mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, controller.mImpl->mEventData->mLeftSelectionPosition, controller.mImpl->mEventData->mRightSelectionPosition);
655     }
656   }
657
658   // DISCARD temporary text
659   events.Clear();
660 }
661
662 void Controller::EventHandler::TextReplacedEvent(Controller& controller)
663 {
664   // The natural size needs to be re-calculated.
665   controller.mImpl->mRecalculateNaturalSize = true;
666
667   // The text direction needs to be updated.
668   controller.mImpl->mUpdateTextDirection = true;
669
670   // Apply modifications to the model
671   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
672 }
673
674 void Controller::EventHandler::TextInsertedEvent(Controller& controller)
675 {
676   DALI_ASSERT_DEBUG(NULL != controller.mImpl->mEventData && "Unexpected TextInsertedEvent");
677
678   if(NULL == controller.mImpl->mEventData)
679   {
680     return;
681   }
682
683   controller.mImpl->mEventData->mCheckScrollAmount = true;
684
685   // The natural size needs to be re-calculated.
686   controller.mImpl->mRecalculateNaturalSize = true;
687
688   // The text direction needs to be updated.
689   controller.mImpl->mUpdateTextDirection = true;
690
691   // Apply modifications to the model; TODO - Optimize this
692   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
693 }
694
695 void Controller::EventHandler::TextDeletedEvent(Controller& controller)
696 {
697   DALI_ASSERT_DEBUG(NULL != controller.mImpl->mEventData && "Unexpected TextDeletedEvent");
698
699   if(NULL == controller.mImpl->mEventData)
700   {
701     return;
702   }
703
704   if(!controller.IsEditable()) return;
705
706   controller.mImpl->mEventData->mCheckScrollAmount = true;
707
708   // The natural size needs to be re-calculated.
709   controller.mImpl->mRecalculateNaturalSize = true;
710
711   // The text direction needs to be updated.
712   controller.mImpl->mUpdateTextDirection = true;
713
714   // Apply modifications to the model; TODO - Optimize this
715   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
716 }
717
718 bool Controller::EventHandler::DeleteEvent(Controller& controller, int keyCode)
719 {
720   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::KeyEvent %p KeyCode : %d \n", &controller, keyCode);
721
722   bool removed = false;
723
724   if(NULL == controller.mImpl->mEventData)
725   {
726     return removed;
727   }
728
729   if(!controller.IsEditable()) return false;
730
731   // InputMethodContext is no longer handling key-events
732   controller.mImpl->ClearPreEditFlag();
733
734   if(EventData::SELECTING == controller.mImpl->mEventData->mState)
735   {
736     removed = TextUpdater::RemoveSelectedText(controller);
737   }
738   else if((controller.mImpl->mEventData->mPrimaryCursorPosition > 0) && (keyCode == Dali::DALI_KEY_BACKSPACE))
739   {
740     // Remove the character before the current cursor position
741     removed = TextUpdater::RemoveText(controller, -1, 1, UPDATE_INPUT_STYLE);
742   }
743   else if((controller.mImpl->mEventData->mPrimaryCursorPosition < controller.mImpl->mModel->mLogicalModel->mText.Count()) &&
744           (keyCode == Dali::DevelKey::DALI_KEY_DELETE))
745   {
746     // Remove the character after the current cursor position
747     removed = TextUpdater::RemoveText(controller, 0, 1, UPDATE_INPUT_STYLE);
748   }
749
750   if(removed)
751   {
752     if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
753        !controller.mImpl->IsPlaceholderAvailable())
754     {
755       controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
756     }
757     else
758     {
759       PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
760     }
761     controller.mImpl->mEventData->mUpdateCursorPosition = true;
762     controller.mImpl->mEventData->mScrollAfterDelete    = true;
763   }
764
765   return removed;
766 }
767
768 InputMethodContext::CallbackData Controller::EventHandler::OnInputMethodContextEvent(Controller& controller, InputMethodContext& inputMethodContext, const InputMethodContext::EventData& inputMethodContextEvent)
769 {
770   // Whether the text needs to be relaid-out.
771   bool requestRelayout = false;
772
773   // Whether to retrieve the text and cursor position to be sent to the InputMethodContext.
774   bool retrieveText   = false;
775   bool retrieveCursor = false;
776
777   switch(inputMethodContextEvent.eventName)
778   {
779     case InputMethodContext::COMMIT:
780     {
781       TextUpdater::InsertText(controller, inputMethodContextEvent.predictiveString, Text::Controller::COMMIT);
782       requestRelayout = true;
783       retrieveCursor  = true;
784       break;
785     }
786     case InputMethodContext::PRE_EDIT:
787     {
788       TextUpdater::InsertText(controller, inputMethodContextEvent.predictiveString, Text::Controller::PRE_EDIT);
789       requestRelayout = true;
790       retrieveCursor  = true;
791       break;
792     }
793     case InputMethodContext::DELETE_SURROUNDING:
794     {
795       const bool textDeleted = TextUpdater::RemoveText(controller,
796                                                        inputMethodContextEvent.cursorOffset,
797                                                        inputMethodContextEvent.numberOfChars,
798                                                        DONT_UPDATE_INPUT_STYLE);
799
800       if(textDeleted)
801       {
802         if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
803            !controller.mImpl->IsPlaceholderAvailable())
804         {
805           controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
806         }
807         else
808         {
809           PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
810         }
811         controller.mImpl->mEventData->mUpdateCursorPosition = true;
812         controller.mImpl->mEventData->mScrollAfterDelete    = true;
813
814         requestRelayout = true;
815       }
816       break;
817     }
818     case InputMethodContext::GET_SURROUNDING:
819     {
820       retrieveText   = true;
821       retrieveCursor = true;
822       break;
823     }
824     case InputMethodContext::PRIVATE_COMMAND:
825     {
826       // PRIVATECOMMAND event is just for getting the private command message
827       retrieveText   = true;
828       retrieveCursor = true;
829       break;
830     }
831     case InputMethodContext::SELECTION_SET:
832     {
833       uint32_t start = static_cast<uint32_t>(inputMethodContextEvent.startIndex);
834       uint32_t end   = static_cast<uint32_t>(inputMethodContextEvent.endIndex);
835       if(start == end)
836       {
837         controller.SetPrimaryCursorPosition(start, true);
838       }
839       else
840       {
841         controller.SelectText(start, end);
842       }
843
844       break;
845     }
846     case InputMethodContext::VOID:
847     {
848       // do nothing
849       break;
850     }
851   } // end switch
852
853   if(requestRelayout)
854   {
855     controller.mImpl->mOperationsPending = ALL_OPERATIONS;
856     controller.mImpl->RequestRelayout();
857   }
858
859   std::string    text;
860   CharacterIndex cursorPosition      = 0u;
861
862   if(retrieveCursor)
863   {
864     cursorPosition = controller.mImpl->GetLogicalCursorPosition();
865   }
866
867   if(retrieveText)
868   {
869     if(!controller.mImpl->IsShowingPlaceholderText())
870     {
871       // Retrieves the normal text string.
872       controller.mImpl->GetText(0u, text);
873     }
874     else
875     {
876       // When the current text is Placeholder Text, the surrounding text should be empty string.
877       // It means DALi should send empty string ("") to IME.
878       text = "";
879     }
880   }
881
882   InputMethodContext::CallbackData callbackData((retrieveText || retrieveCursor), cursorPosition, text, false);
883
884   if(requestRelayout &&
885      (NULL != controller.mImpl->mEditableControlInterface))
886   {
887     // Do this last since it provides callbacks into application code
888     controller.mImpl->mEditableControlInterface->TextChanged(false);
889   }
890
891   return callbackData;
892 }
893
894 void Controller::EventHandler::PasteClipboardItemEvent(Controller& controller, const std::string& text)
895 {
896   // Commit the current pre-edit text; the contents of the clipboard should be appended
897   controller.mImpl->ResetInputMethodContext();
898
899   // Temporary disable hiding clipboard
900   controller.mImpl->SetClipboardHideEnable(false);
901
902   // Paste
903   TextUpdater::PasteText(controller, text);
904
905   controller.mImpl->SetClipboardHideEnable(true);
906 }
907
908 void Controller::EventHandler::DecorationEvent(Controller& controller, HandleType handleType, HandleState state, float x, float y)
909 {
910   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected DecorationEvent");
911
912   if(NULL != controller.mImpl->mEventData)
913   {
914     switch(handleType)
915     {
916       case GRAB_HANDLE:
917       {
918         Event event(Event::GRAB_HANDLE_EVENT);
919         event.p1.mUint  = state;
920         event.p2.mFloat = x;
921         event.p3.mFloat = y;
922
923         controller.mImpl->mEventData->mEventQueue.push_back(event);
924         break;
925       }
926       case LEFT_SELECTION_HANDLE:
927       {
928         Event event(Event::LEFT_SELECTION_HANDLE_EVENT);
929         event.p1.mUint  = state;
930         event.p2.mFloat = x;
931         event.p3.mFloat = y;
932
933         controller.mImpl->mEventData->mEventQueue.push_back(event);
934         break;
935       }
936       case RIGHT_SELECTION_HANDLE:
937       {
938         Event event(Event::RIGHT_SELECTION_HANDLE_EVENT);
939         event.p1.mUint  = state;
940         event.p2.mFloat = x;
941         event.p3.mFloat = y;
942
943         controller.mImpl->mEventData->mEventQueue.push_back(event);
944         break;
945       }
946       case LEFT_SELECTION_HANDLE_MARKER:
947       case RIGHT_SELECTION_HANDLE_MARKER:
948       {
949         // Markers do not move the handles.
950         break;
951       }
952       case HANDLE_TYPE_COUNT:
953       {
954         DALI_ASSERT_DEBUG(!"Controller::HandleEvent. Unexpected handle type");
955       }
956     }
957
958     controller.mImpl->RequestRelayout();
959   }
960 }
961
962 void Controller::EventHandler::TextPopupButtonTouched(Controller& controller, Dali::Toolkit::TextSelectionPopup::Buttons button)
963 {
964   if(NULL == controller.mImpl->mEventData)
965   {
966     return;
967   }
968
969   switch(button)
970   {
971     case Toolkit::TextSelectionPopup::CUT:
972     {
973       controller.CutText();
974       break;
975     }
976     case Toolkit::TextSelectionPopup::COPY:
977     {
978       controller.CopyText();
979       break;
980     }
981     case Toolkit::TextSelectionPopup::PASTE:
982     {
983       controller.PasteText();
984       break;
985     }
986     case Toolkit::TextSelectionPopup::SELECT:
987     {
988       const Vector2& currentCursorPosition = controller.mImpl->mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
989
990       if(controller.mImpl->mEventData->mSelectionEnabled)
991       {
992         // Creates a SELECT event.
993         SelectEvent(controller, currentCursorPosition.x, currentCursorPosition.y, SelectionType::INTERACTIVE);
994       }
995       break;
996     }
997     case Toolkit::TextSelectionPopup::SELECT_ALL:
998     {
999       // Creates a SELECT_ALL event
1000       SelectEvent(controller, 0.f, 0.f, SelectionType::ALL);
1001       break;
1002     }
1003     case Toolkit::TextSelectionPopup::CLIPBOARD:
1004     {
1005       controller.mImpl->ShowClipboard();
1006       break;
1007     }
1008     case Toolkit::TextSelectionPopup::NONE:
1009     {
1010       // Nothing to do.
1011       break;
1012     }
1013   }
1014 }
1015
1016 } // namespace Text
1017
1018 } // namespace Toolkit
1019
1020 } // namespace Dali