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