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