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