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