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