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