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