Merge "Add AnchorColor Property" 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 there is a markup clicked color attribute, use it. Otherwise, use the property color.
409             if(controller.mImpl->mModel->mLogicalModel->mColorRuns.Count() > anchor.colorRunIndex)
410             {
411               ColorRun& colorRun = *(controller.mImpl->mModel->mLogicalModel->mColorRuns.Begin() + anchor.colorRunIndex);
412               colorRun.color = anchor.isMarkupClickedColorSet ? anchor.markupClickedColor : controller.mImpl->mAnchorClickedColor;
413             }
414             if(controller.mImpl->mModel->mLogicalModel->mUnderlinedCharacterRuns.Count() > anchor.underlinedCharacterRunIndex)
415             {
416               UnderlinedCharacterRun& underlineRun = *(controller.mImpl->mModel->mLogicalModel->mUnderlinedCharacterRuns.Begin() + anchor.underlinedCharacterRunIndex);
417               underlineRun.properties.color = anchor.isMarkupClickedColorSet ? anchor.markupClickedColor : controller.mImpl->mAnchorClickedColor;
418             }
419
420             controller.mImpl->ClearFontData();
421             controller.mImpl->mOperationsPending = static_cast<OperationsMask>(controller.mImpl->mOperationsPending | COLOR);
422             controller.mImpl->RequestRelayout();
423           }
424         }
425
426         std::string href = anchor.href == nullptr ? "" : anchor.href;
427         controller.mImpl->mAnchorControlInterface->AnchorClicked(href);
428         break;
429       }
430     }
431   }
432 }
433
434 void Controller::EventHandler::TapEvent(Controller& controller, unsigned int tapCount, float x, float y)
435 {
436   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected TapEvent");
437
438   if(NULL != controller.mImpl->mEventData)
439   {
440     DALI_LOG_INFO(gLogFilter, Debug::Concise, "TapEvent state:%d \n", controller.mImpl->mEventData->mState);
441     EventData::State state(controller.mImpl->mEventData->mState);
442     bool             relayoutNeeded(false); // to avoid unnecessary relayouts when tapping an empty text-field
443
444     if(controller.mImpl->IsClipboardVisible())
445     {
446       if(EventData::INACTIVE == state || EventData::EDITING == state)
447       {
448         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE);
449       }
450       relayoutNeeded = true;
451     }
452     else if(1u == tapCount)
453     {
454       if(EventData::EDITING_WITH_POPUP == state || EventData::EDITING_WITH_PASTE_POPUP == state)
455       {
456         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE); // If Popup shown hide it here so can be shown again if required.
457       }
458
459       if(controller.mImpl->IsShowingRealText() && (EventData::INACTIVE != state))
460       {
461         controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE);
462         relayoutNeeded = true;
463       }
464       else
465       {
466         if(controller.mImpl->IsShowingPlaceholderText() && !controller.mImpl->IsFocusedPlaceholderAvailable())
467         {
468           // Hide placeholder text
469           TextUpdater::ResetText(controller);
470         }
471
472         if(EventData::INACTIVE == state)
473         {
474           controller.mImpl->ChangeState(EventData::EDITING);
475         }
476         else if(!controller.mImpl->IsClipboardEmpty())
477         {
478           controller.mImpl->ChangeState(EventData::EDITING_WITH_POPUP);
479         }
480         relayoutNeeded = true;
481       }
482     }
483     else if(2u == tapCount)
484     {
485       if(controller.mImpl->mEventData->mSelectionEnabled &&
486          controller.mImpl->IsShowingRealText())
487       {
488         relayoutNeeded                                       = true;
489         controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
490         controller.mImpl->mEventData->mIsRightHandleSelected = true;
491       }
492     }
493
494     // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
495     if(relayoutNeeded)
496     {
497       Event event(Event::TAP_EVENT);
498       event.p1.mUint  = tapCount;
499       event.p2.mFloat = x;
500       event.p3.mFloat = y;
501       controller.mImpl->mEventData->mEventQueue.push_back(event);
502
503       controller.mImpl->RequestRelayout();
504     }
505   }
506
507   // Reset keyboard as tap event has occurred.
508   controller.mImpl->ResetInputMethodContext();
509 }
510
511 void Controller::EventHandler::PanEvent(Controller& controller, GestureState state, const Vector2& displacement)
512 {
513   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected PanEvent");
514
515   if(NULL != controller.mImpl->mEventData)
516   {
517     Event event(Event::PAN_EVENT);
518     event.p1.mInt   = static_cast<int>(state);
519     event.p2.mFloat = displacement.x;
520     event.p3.mFloat = displacement.y;
521     controller.mImpl->mEventData->mEventQueue.push_back(event);
522
523     controller.mImpl->RequestRelayout();
524   }
525 }
526
527 void Controller::EventHandler::LongPressEvent(Controller& controller, GestureState state, float x, float y)
528 {
529   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected LongPressEvent");
530
531   if((state == GestureState::STARTED) &&
532      (NULL != controller.mImpl->mEventData))
533   {
534     // The 1st long-press on inactive text-field is treated as tap
535     if(EventData::INACTIVE == controller.mImpl->mEventData->mState)
536     {
537       controller.mImpl->ChangeState(EventData::EDITING);
538
539       Event event(Event::TAP_EVENT);
540       event.p1.mUint  = 1;
541       event.p2.mFloat = x;
542       event.p3.mFloat = y;
543       controller.mImpl->mEventData->mEventQueue.push_back(event);
544
545       controller.mImpl->RequestRelayout();
546     }
547     else if(!controller.mImpl->IsShowingRealText())
548     {
549       Event event(Event::LONG_PRESS_EVENT);
550       event.p1.mInt   = static_cast<int>(state);
551       event.p2.mFloat = x;
552       event.p3.mFloat = y;
553       controller.mImpl->mEventData->mEventQueue.push_back(event);
554       controller.mImpl->RequestRelayout();
555     }
556     else if(!controller.mImpl->IsClipboardVisible())
557     {
558       // Reset the InputMethodContext to commit the pre-edit before selecting the text.
559       controller.mImpl->ResetInputMethodContext();
560
561       Event event(Event::LONG_PRESS_EVENT);
562       event.p1.mInt   = static_cast<int>(state);
563       event.p2.mFloat = x;
564       event.p3.mFloat = y;
565       controller.mImpl->mEventData->mEventQueue.push_back(event);
566       controller.mImpl->RequestRelayout();
567
568       controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
569       controller.mImpl->mEventData->mIsRightHandleSelected = true;
570     }
571   }
572 }
573
574 void Controller::EventHandler::SelectEvent(Controller& controller, float x, float y, SelectionType selectType)
575 {
576   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SelectEvent\n");
577
578   if(NULL != controller.mImpl->mEventData)
579   {
580     if(selectType == SelectionType::ALL)
581     {
582       Event event(Event::SELECT_ALL);
583       controller.mImpl->mEventData->mEventQueue.push_back(event);
584     }
585     else if(selectType == SelectionType::NONE)
586     {
587       Event event(Event::SELECT_NONE);
588       controller.mImpl->mEventData->mEventQueue.push_back(event);
589     }
590     else
591     {
592       Event event(Event::SELECT);
593       event.p2.mFloat = x;
594       event.p3.mFloat = y;
595       controller.mImpl->mEventData->mEventQueue.push_back(event);
596     }
597
598     controller.mImpl->mEventData->mCheckScrollAmount     = true;
599     controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
600     controller.mImpl->mEventData->mIsRightHandleSelected = true;
601     controller.mImpl->RequestRelayout();
602   }
603 }
604
605 void Controller::EventHandler::SelectEvent(Controller& controller, const uint32_t start, const uint32_t end, SelectionType selectType)
606 {
607   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SelectEvent\n");
608
609   if(NULL != controller.mImpl->mEventData)
610   {
611     if(selectType == SelectionType::RANGE)
612     {
613       Event event(Event::SELECT_RANGE);
614       event.p2.mUint = start;
615       event.p3.mUint = end;
616       controller.mImpl->mEventData->mEventQueue.push_back(event);
617     }
618
619     controller.mImpl->mEventData->mCheckScrollAmount     = true;
620     controller.mImpl->mEventData->mIsLeftHandleSelected  = true;
621     controller.mImpl->mEventData->mIsRightHandleSelected = true;
622     controller.mImpl->RequestRelayout();
623   }
624 }
625
626 void Controller::EventHandler::ProcessModifyEvents(Controller& controller)
627 {
628   Vector<ModifyEvent>& events = controller.mImpl->mModifyEvents;
629
630   if(0u == events.Count())
631   {
632     // Nothing to do.
633     return;
634   }
635
636   DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_MODIFY_EVENTS");
637
638   for(Vector<ModifyEvent>::ConstIterator it    = events.Begin(),
639                                          endIt = events.End();
640       it != endIt;
641       ++it)
642   {
643     const ModifyEvent& event = *it;
644
645     if(ModifyEvent::TEXT_REPLACED == event.type)
646     {
647       // A (single) replace event should come first, otherwise we wasted time processing NOOP events
648       DALI_ASSERT_DEBUG(it == events.Begin() && "Unexpected TEXT_REPLACED event");
649
650       TextReplacedEvent(controller);
651     }
652     else if(ModifyEvent::TEXT_INSERTED == event.type)
653     {
654       TextInsertedEvent(controller);
655     }
656     else if(ModifyEvent::TEXT_DELETED == event.type)
657     {
658       // Placeholder-text cannot be deleted
659       if(!controller.mImpl->IsShowingPlaceholderText())
660       {
661         TextDeletedEvent(controller);
662       }
663     }
664   }
665
666   if(NULL != controller.mImpl->mEventData)
667   {
668     uint32_t oldStart, oldEnd;
669     oldStart = controller.mImpl->mEventData->mLeftSelectionPosition;
670     oldEnd   = controller.mImpl->mEventData->mRightSelectionPosition;
671
672     // When the text is being modified, delay cursor blinking
673     controller.mImpl->mEventData->mDecorator->DelayCursorBlink();
674
675     // Update selection position after modifying the text
676     controller.mImpl->mEventData->mLeftSelectionPosition  = controller.mImpl->mEventData->mPrimaryCursorPosition;
677     controller.mImpl->mEventData->mRightSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
678
679     if(controller.mImpl->mSelectableControlInterface != nullptr && controller.mImpl->mEventData->mState == EventData::SELECTING)
680     {
681       controller.mImpl->mSelectableControlInterface->SelectionChanged(oldStart, oldEnd, controller.mImpl->mEventData->mLeftSelectionPosition, controller.mImpl->mEventData->mRightSelectionPosition);
682     }
683   }
684
685   // DISCARD temporary text
686   events.Clear();
687 }
688
689 void Controller::EventHandler::TextReplacedEvent(Controller& controller)
690 {
691   // The natural size needs to be re-calculated.
692   controller.mImpl->mRecalculateNaturalSize = true;
693
694   // The text direction needs to be updated.
695   controller.mImpl->mUpdateTextDirection = true;
696
697   // Apply modifications to the model
698   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
699 }
700
701 void Controller::EventHandler::TextInsertedEvent(Controller& controller)
702 {
703   DALI_ASSERT_DEBUG(NULL != controller.mImpl->mEventData && "Unexpected TextInsertedEvent");
704
705   if(NULL == controller.mImpl->mEventData)
706   {
707     return;
708   }
709
710   controller.mImpl->mEventData->mCheckScrollAmount = true;
711
712   // The natural size needs to be re-calculated.
713   controller.mImpl->mRecalculateNaturalSize = true;
714
715   // The text direction needs to be updated.
716   controller.mImpl->mUpdateTextDirection = true;
717
718   // Apply modifications to the model; TODO - Optimize this
719   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
720 }
721
722 void Controller::EventHandler::TextDeletedEvent(Controller& controller)
723 {
724   DALI_ASSERT_DEBUG(NULL != controller.mImpl->mEventData && "Unexpected TextDeletedEvent");
725
726   if(NULL == controller.mImpl->mEventData)
727   {
728     return;
729   }
730
731   if(!controller.IsEditable()) return;
732
733   controller.mImpl->mEventData->mCheckScrollAmount = true;
734
735   // The natural size needs to be re-calculated.
736   controller.mImpl->mRecalculateNaturalSize = true;
737
738   // The text direction needs to be updated.
739   controller.mImpl->mUpdateTextDirection = true;
740
741   // Apply modifications to the model; TODO - Optimize this
742   controller.mImpl->mOperationsPending = ALL_OPERATIONS;
743 }
744
745 bool Controller::EventHandler::DeleteEvent(Controller& controller, int keyCode)
746 {
747   DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::KeyEvent %p KeyCode : %d \n", &controller, keyCode);
748
749   bool removed = false;
750
751   if(NULL == controller.mImpl->mEventData)
752   {
753     return removed;
754   }
755
756   if(!controller.IsEditable()) return false;
757
758   // InputMethodContext is no longer handling key-events
759   controller.mImpl->ClearPreEditFlag();
760
761   if(EventData::SELECTING == controller.mImpl->mEventData->mState)
762   {
763     removed = TextUpdater::RemoveSelectedText(controller);
764   }
765   else if((controller.mImpl->mEventData->mPrimaryCursorPosition > 0) && (keyCode == Dali::DALI_KEY_BACKSPACE))
766   {
767     // Remove the character before the current cursor position
768     removed = TextUpdater::RemoveText(controller, -1, 1, UPDATE_INPUT_STYLE);
769   }
770   else if((controller.mImpl->mEventData->mPrimaryCursorPosition < controller.mImpl->mModel->mLogicalModel->mText.Count()) &&
771           (keyCode == Dali::DevelKey::DALI_KEY_DELETE))
772   {
773     // Remove the character after the current cursor position
774     removed = TextUpdater::RemoveText(controller, 0, 1, UPDATE_INPUT_STYLE);
775   }
776
777   if(removed)
778   {
779     if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
780        !controller.mImpl->IsPlaceholderAvailable())
781     {
782       controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
783     }
784     else
785     {
786       PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
787     }
788     controller.mImpl->mEventData->mUpdateCursorPosition = true;
789     controller.mImpl->mEventData->mScrollAfterDelete    = true;
790   }
791
792   return removed;
793 }
794
795 InputMethodContext::CallbackData Controller::EventHandler::OnInputMethodContextEvent(Controller& controller, InputMethodContext& inputMethodContext, const InputMethodContext::EventData& inputMethodContextEvent)
796 {
797   // Whether the text needs to be relaid-out.
798   bool requestRelayout = false;
799
800   // Whether to retrieve the text and cursor position to be sent to the InputMethodContext.
801   bool retrieveText   = false;
802   bool retrieveCursor = false;
803
804   switch(inputMethodContextEvent.eventName)
805   {
806     case InputMethodContext::COMMIT:
807     {
808       TextUpdater::InsertText(controller, inputMethodContextEvent.predictiveString, Text::Controller::COMMIT);
809       requestRelayout = true;
810       retrieveCursor  = true;
811       break;
812     }
813     case InputMethodContext::PRE_EDIT:
814     {
815       TextUpdater::InsertText(controller, inputMethodContextEvent.predictiveString, Text::Controller::PRE_EDIT);
816       requestRelayout = true;
817       retrieveCursor  = true;
818       break;
819     }
820     case InputMethodContext::DELETE_SURROUNDING:
821     {
822       const bool textDeleted = TextUpdater::RemoveText(controller,
823                                                        inputMethodContextEvent.cursorOffset,
824                                                        inputMethodContextEvent.numberOfChars,
825                                                        DONT_UPDATE_INPUT_STYLE);
826
827       if(textDeleted)
828       {
829         if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
830            !controller.mImpl->IsPlaceholderAvailable())
831         {
832           controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
833         }
834         else
835         {
836           PlaceholderHandler::ShowPlaceholderText(*controller.mImpl);
837         }
838         controller.mImpl->mEventData->mUpdateCursorPosition = true;
839         controller.mImpl->mEventData->mScrollAfterDelete    = true;
840
841         requestRelayout = true;
842       }
843       break;
844     }
845     case InputMethodContext::GET_SURROUNDING:
846     {
847       retrieveText   = true;
848       retrieveCursor = true;
849       break;
850     }
851     case InputMethodContext::PRIVATE_COMMAND:
852     {
853       // PRIVATECOMMAND event is just for getting the private command message
854       retrieveText   = true;
855       retrieveCursor = true;
856       break;
857     }
858     case InputMethodContext::SELECTION_SET:
859     {
860       uint32_t start = static_cast<uint32_t>(inputMethodContextEvent.startIndex);
861       uint32_t end   = static_cast<uint32_t>(inputMethodContextEvent.endIndex);
862       if(start == end)
863       {
864         controller.SetPrimaryCursorPosition(start, true);
865       }
866       else
867       {
868         controller.SelectText(start, end);
869       }
870
871       break;
872     }
873     case InputMethodContext::VOID:
874     {
875       // do nothing
876       break;
877     }
878   } // end switch
879
880   if(requestRelayout)
881   {
882     controller.mImpl->mOperationsPending = ALL_OPERATIONS;
883     controller.mImpl->RequestRelayout();
884   }
885
886   std::string    text;
887   CharacterIndex cursorPosition      = 0u;
888
889   if(retrieveCursor)
890   {
891     cursorPosition = controller.mImpl->GetLogicalCursorPosition();
892   }
893
894   if(retrieveText)
895   {
896     if(!controller.mImpl->IsShowingPlaceholderText())
897     {
898       // Retrieves the normal text string.
899       controller.mImpl->GetText(0u, text);
900     }
901     else
902     {
903       // When the current text is Placeholder Text, the surrounding text should be empty string.
904       // It means DALi should send empty string ("") to IME.
905       text = "";
906     }
907   }
908
909   InputMethodContext::CallbackData callbackData((retrieveText || retrieveCursor), cursorPosition, text, false);
910
911   if(requestRelayout &&
912      (NULL != controller.mImpl->mEditableControlInterface))
913   {
914     // Do this last since it provides callbacks into application code
915     controller.mImpl->mEditableControlInterface->TextChanged(false);
916   }
917
918   return callbackData;
919 }
920
921 void Controller::EventHandler::PasteClipboardItemEvent(Controller& controller, const std::string& text)
922 {
923   // Commit the current pre-edit text; the contents of the clipboard should be appended
924   controller.mImpl->ResetInputMethodContext();
925
926   // Temporary disable hiding clipboard
927   controller.mImpl->SetClipboardHideEnable(false);
928
929   // Paste
930   TextUpdater::PasteText(controller, text);
931
932   controller.mImpl->SetClipboardHideEnable(true);
933 }
934
935 void Controller::EventHandler::DecorationEvent(Controller& controller, HandleType handleType, HandleState state, float x, float y)
936 {
937   DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected DecorationEvent");
938
939   if(NULL != controller.mImpl->mEventData)
940   {
941     switch(handleType)
942     {
943       case GRAB_HANDLE:
944       {
945         Event event(Event::GRAB_HANDLE_EVENT);
946         event.p1.mUint  = state;
947         event.p2.mFloat = x;
948         event.p3.mFloat = y;
949
950         controller.mImpl->mEventData->mEventQueue.push_back(event);
951         break;
952       }
953       case LEFT_SELECTION_HANDLE:
954       {
955         Event event(Event::LEFT_SELECTION_HANDLE_EVENT);
956         event.p1.mUint  = state;
957         event.p2.mFloat = x;
958         event.p3.mFloat = y;
959
960         controller.mImpl->mEventData->mEventQueue.push_back(event);
961         break;
962       }
963       case RIGHT_SELECTION_HANDLE:
964       {
965         Event event(Event::RIGHT_SELECTION_HANDLE_EVENT);
966         event.p1.mUint  = state;
967         event.p2.mFloat = x;
968         event.p3.mFloat = y;
969
970         controller.mImpl->mEventData->mEventQueue.push_back(event);
971         break;
972       }
973       case LEFT_SELECTION_HANDLE_MARKER:
974       case RIGHT_SELECTION_HANDLE_MARKER:
975       {
976         // Markers do not move the handles.
977         break;
978       }
979       case HANDLE_TYPE_COUNT:
980       {
981         DALI_ASSERT_DEBUG(!"Controller::HandleEvent. Unexpected handle type");
982       }
983     }
984
985     controller.mImpl->RequestRelayout();
986   }
987 }
988
989 void Controller::EventHandler::TextPopupButtonTouched(Controller& controller, Dali::Toolkit::TextSelectionPopup::Buttons button)
990 {
991   if(NULL == controller.mImpl->mEventData)
992   {
993     return;
994   }
995
996   switch(button)
997   {
998     case Toolkit::TextSelectionPopup::CUT:
999     {
1000       controller.CutText();
1001       break;
1002     }
1003     case Toolkit::TextSelectionPopup::COPY:
1004     {
1005       controller.CopyText();
1006       break;
1007     }
1008     case Toolkit::TextSelectionPopup::PASTE:
1009     {
1010       controller.PasteText();
1011       break;
1012     }
1013     case Toolkit::TextSelectionPopup::SELECT:
1014     {
1015       const Vector2& currentCursorPosition = controller.mImpl->mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
1016
1017       if(controller.mImpl->mEventData->mSelectionEnabled)
1018       {
1019         // Creates a SELECT event.
1020         SelectEvent(controller, currentCursorPosition.x, currentCursorPosition.y, SelectionType::INTERACTIVE);
1021       }
1022       break;
1023     }
1024     case Toolkit::TextSelectionPopup::SELECT_ALL:
1025     {
1026       // Creates a SELECT_ALL event
1027       SelectEvent(controller, 0.f, 0.f, SelectionType::ALL);
1028       break;
1029     }
1030     case Toolkit::TextSelectionPopup::CLIPBOARD:
1031     {
1032       controller.mImpl->ShowClipboard();
1033       break;
1034     }
1035     case Toolkit::TextSelectionPopup::NONE:
1036     {
1037       // Nothing to do.
1038       break;
1039     }
1040   }
1041 }
1042
1043 } // namespace Text
1044
1045 } // namespace Toolkit
1046
1047 } // namespace Dali