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