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