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