2 * Copyright (c) 2021 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include <dali-toolkit/internal/text/text-controller-event-handler.h>
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>
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-editable-control-interface.h>
33 #if defined(DEBUG_ENABLED)
34 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, true, "LOG_TEXT_CONTROLS");
37 const std::string KEY_C_NAME = "c";
38 const std::string KEY_V_NAME = "v";
39 const std::string KEY_X_NAME = "x";
40 const std::string KEY_A_NAME = "a";
41 const std::string KEY_INSERT_NAME = "Insert";
51 void Controller::EventHandler::KeyboardFocusGainEvent(Controller& controller)
53 DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected KeyboardFocusGainEvent");
55 if(NULL != controller.mImpl->mEventData)
57 if((EventData::INACTIVE == controller.mImpl->mEventData->mState) ||
58 (EventData::INTERRUPTED == controller.mImpl->mEventData->mState))
60 controller.mImpl->ChangeState(EventData::EDITING);
61 controller.mImpl->mEventData->mUpdateCursorPosition = true; //If editing started without tap event, cursor update must be triggered.
62 controller.mImpl->mEventData->mUpdateInputStyle = true;
63 controller.mImpl->mEventData->mScrollAfterUpdatePosition = true;
65 controller.mImpl->NotifyInputMethodContextMultiLineStatus();
66 if(controller.mImpl->IsShowingPlaceholderText())
68 // Show alternative placeholder-text when editing
69 controller.ShowPlaceholderText();
72 controller.mImpl->RequestRelayout();
76 void Controller::EventHandler::KeyboardFocusLostEvent(Controller& controller)
78 DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected KeyboardFocusLostEvent");
80 if(NULL != controller.mImpl->mEventData)
82 if(EventData::INTERRUPTED != controller.mImpl->mEventData->mState)
84 // Init selection position
85 if(controller.mImpl->mEventData->mState == EventData::SELECTING)
87 controller.mImpl->mEventData->mLeftSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
88 controller.mImpl->mEventData->mRightSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
91 controller.mImpl->ChangeState(EventData::INACTIVE);
93 if(!controller.mImpl->IsShowingRealText())
95 // Revert to regular placeholder-text when not editing
96 controller.ShowPlaceholderText();
100 controller.mImpl->RequestRelayout();
103 bool Controller::EventHandler::KeyEvent(Controller& controller, const Dali::KeyEvent& keyEvent)
105 DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected KeyEvent");
107 bool textChanged = false;
108 bool relayoutNeeded = false;
110 if((NULL != controller.mImpl->mEventData) &&
111 (keyEvent.GetState() == KeyEvent::DOWN))
113 int keyCode = keyEvent.GetKeyCode();
114 const std::string& keyString = keyEvent.GetKeyString();
115 const std::string keyName = keyEvent.GetKeyName();
116 // Key will produce same logical-key value when ctrl
117 // is down, regardless of language layout
118 const std::string logicalKey = keyEvent.GetLogicalKey();
120 const bool isNullKey = (0 == keyCode) && (keyString.empty());
122 // Pre-process to separate modifying events from non-modifying input events.
125 // In some platforms arrive key events with no key code.
129 else if(Dali::DALI_KEY_ESCAPE == keyCode || Dali::DALI_KEY_BACK == keyCode || Dali::DALI_KEY_SEARCH == keyCode)
134 else if((Dali::DALI_KEY_CURSOR_LEFT == keyCode) ||
135 (Dali::DALI_KEY_CURSOR_RIGHT == keyCode) ||
136 (Dali::DALI_KEY_CURSOR_UP == keyCode) ||
137 (Dali::DALI_KEY_CURSOR_DOWN == keyCode))
139 // If don't have any text, do nothing.
140 if(!controller.mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters)
145 uint32_t cursorPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
146 uint32_t numberOfCharacters = controller.mImpl->mTextUpdateInfo.mPreviousNumberOfCharacters;
147 uint32_t cursorLine = controller.mImpl->mModel->mVisualModel->GetLineOfCharacter(cursorPosition);
148 uint32_t numberOfLines = controller.mImpl->mModel->GetNumberOfLines();
150 // Logic to determine whether this text control will lose focus or not.
151 if((Dali::DALI_KEY_CURSOR_LEFT == keyCode && 0 == cursorPosition && !keyEvent.IsShiftModifier()) ||
152 (Dali::DALI_KEY_CURSOR_RIGHT == keyCode && numberOfCharacters == cursorPosition && !keyEvent.IsShiftModifier()) ||
153 (Dali::DALI_KEY_CURSOR_DOWN == keyCode && cursorLine == numberOfLines - 1) ||
154 (Dali::DALI_KEY_CURSOR_DOWN == keyCode && numberOfCharacters == cursorPosition && cursorLine - 1 == numberOfLines - 1) ||
155 (Dali::DALI_KEY_CURSOR_UP == keyCode && cursorLine == 0) ||
156 (Dali::DALI_KEY_CURSOR_UP == keyCode && numberOfCharacters == cursorPosition && cursorLine == 1))
158 // Release the active highlight.
159 if(controller.mImpl->mEventData->mState == EventData::SELECTING)
161 controller.mImpl->ChangeState(EventData::EDITING);
163 // Update selection position.
164 controller.mImpl->mEventData->mLeftSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
165 controller.mImpl->mEventData->mRightSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
166 controller.mImpl->mEventData->mUpdateCursorPosition = true;
167 controller.mImpl->RequestRelayout();
172 controller.mImpl->mEventData->mCheckScrollAmount = true;
173 Event event(Event::CURSOR_KEY_EVENT);
174 event.p1.mInt = keyCode;
175 event.p2.mBool = keyEvent.IsShiftModifier();
176 controller.mImpl->mEventData->mEventQueue.push_back(event);
178 // Will request for relayout.
179 relayoutNeeded = true;
181 else if(Dali::DevelKey::DALI_KEY_CONTROL_LEFT == keyCode || Dali::DevelKey::DALI_KEY_CONTROL_RIGHT == keyCode)
183 // Left or Right Control key event is received before Ctrl-C/V/X key event is received
184 // If not handle it here, any selected text will be deleted
189 else if(keyEvent.IsCtrlModifier() && !keyEvent.IsShiftModifier())
191 bool consumed = false;
192 if(keyName == KEY_C_NAME || keyName == KEY_INSERT_NAME || logicalKey == KEY_C_NAME || logicalKey == KEY_INSERT_NAME)
194 // Ctrl-C or Ctrl+Insert to copy the selected text
195 controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::COPY);
198 else if(keyName == KEY_V_NAME || logicalKey == KEY_V_NAME)
200 // Ctrl-V to paste the copied text
201 controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::PASTE);
204 else if(keyName == KEY_X_NAME || logicalKey == KEY_X_NAME)
206 // Ctrl-X to cut the selected text
207 controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::CUT);
210 else if(keyName == KEY_A_NAME || logicalKey == KEY_A_NAME)
212 // Ctrl-A to select All the text
213 controller.TextPopupButtonTouched(Toolkit::TextSelectionPopup::SELECT_ALL);
218 else if((Dali::DALI_KEY_BACKSPACE == keyCode) ||
219 (Dali::DevelKey::DALI_KEY_DELETE == keyCode))
221 textChanged = controller.DeleteEvent(keyCode);
223 // Will request for relayout.
224 relayoutNeeded = true;
226 else if(IsKey(keyEvent, Dali::DALI_KEY_POWER) ||
227 IsKey(keyEvent, Dali::DALI_KEY_MENU) ||
228 IsKey(keyEvent, Dali::DALI_KEY_HOME))
230 // Power key/Menu/Home key behaviour does not allow edit mode to resume.
231 controller.mImpl->ChangeState(EventData::INACTIVE);
233 // Will request for relayout.
234 relayoutNeeded = true;
236 // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
238 else if((Dali::DALI_KEY_SHIFT_LEFT == keyCode) || (Dali::DALI_KEY_SHIFT_RIGHT == keyCode))
240 // 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
241 // and a character is typed after the type of a upper case latin character.
246 else if((Dali::DALI_KEY_VOLUME_UP == keyCode) || (Dali::DALI_KEY_VOLUME_DOWN == keyCode))
248 // This branch avoids calling the InsertText() method of the 'else' branch which can delete selected text.
254 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::KeyEvent %p keyString %s\n", &controller, keyString.c_str());
255 if(!controller.IsEditable()) return false;
257 std::string refinedKey = keyString;
258 if(controller.mImpl->mInputFilter != NULL && !refinedKey.empty())
260 bool accepted = false;
261 bool rejected = false;
262 accepted = controller.mImpl->mInputFilter->Contains(Toolkit::InputFilter::Property::ACCEPTED, keyString);
263 rejected = controller.mImpl->mInputFilter->Contains(Toolkit::InputFilter::Property::REJECTED, keyString);
267 // The filtered key is set to empty.
269 // Signal emits when the character to be inserted is filtered by the accepted filter.
270 controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::ACCEPTED);
274 // The filtered key is set to empty.
276 // Signal emits when the character to be inserted is filtered by the rejected filter.
277 controller.mImpl->mEditableControlInterface->InputFiltered(Toolkit::InputFilter::Property::REJECTED);
281 if(!refinedKey.empty())
283 // InputMethodContext is no longer handling key-events
284 controller.mImpl->ClearPreEditFlag();
286 controller.InsertText(refinedKey, COMMIT);
290 // Will request for relayout.
291 relayoutNeeded = true;
295 if((controller.mImpl->mEventData->mState != EventData::INTERRUPTED) &&
296 (controller.mImpl->mEventData->mState != EventData::INACTIVE) &&
298 (Dali::DALI_KEY_SHIFT_LEFT != keyCode) &&
299 (Dali::DALI_KEY_SHIFT_RIGHT != keyCode) &&
300 (Dali::DALI_KEY_VOLUME_UP != keyCode) &&
301 (Dali::DALI_KEY_VOLUME_DOWN != keyCode))
303 // Should not change the state if the key is the shift send by the InputMethodContext.
304 // Otherwise, when the state is SELECTING the text controller can't send the right
305 // surrounding info to the InputMethodContext.
306 controller.mImpl->ChangeState(EventData::EDITING);
308 // Will request for relayout.
309 relayoutNeeded = true;
314 controller.mImpl->RequestRelayout();
319 (NULL != controller.mImpl->mEditableControlInterface))
321 // Do this last since it provides callbacks into application code
322 controller.mImpl->mEditableControlInterface->TextChanged(false);
328 void Controller::EventHandler::AnchorEvent(Controller& controller, float x, float y)
330 if(!controller.mImpl->mMarkupProcessorEnabled ||
331 !controller.mImpl->mModel->mLogicalModel->mAnchors.Count() ||
332 !controller.mImpl->IsShowingRealText())
337 CharacterIndex cursorPosition = 0u;
339 // Convert from control's coords to text's coords.
340 const float xPosition = x - controller.mImpl->mModel->mScrollPosition.x;
341 const float yPosition = y - controller.mImpl->mModel->mScrollPosition.y;
343 // Whether to touch point hits on a glyph.
344 bool matchedCharacter = false;
345 cursorPosition = Text::GetClosestCursorIndex(controller.mImpl->mModel->mVisualModel,
346 controller.mImpl->mModel->mLogicalModel,
347 controller.mImpl->mMetrics,
350 CharacterHitTest::TAP,
353 for(const auto& anchor : controller.mImpl->mModel->mLogicalModel->mAnchors)
355 // Anchor clicked if the calculated cursor position is within the range of anchor.
356 if(cursorPosition >= anchor.startIndex && cursorPosition < anchor.endIndex)
358 if(controller.mImpl->mAnchorControlInterface && anchor.href)
360 std::string href(anchor.href);
361 controller.mImpl->mAnchorControlInterface->AnchorClicked(href);
368 void Controller::EventHandler::TapEvent(Controller& controller, unsigned int tapCount, float x, float y)
370 DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected TapEvent");
372 if(NULL != controller.mImpl->mEventData)
374 DALI_LOG_INFO(gLogFilter, Debug::Concise, "TapEvent state:%d \n", controller.mImpl->mEventData->mState);
375 EventData::State state(controller.mImpl->mEventData->mState);
376 bool relayoutNeeded(false); // to avoid unnecessary relayouts when tapping an empty text-field
378 if(controller.mImpl->IsClipboardVisible())
380 if(EventData::INACTIVE == state || EventData::EDITING == state)
382 controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE);
384 relayoutNeeded = true;
386 else if(1u == tapCount)
388 if(EventData::EDITING_WITH_POPUP == state || EventData::EDITING_WITH_PASTE_POPUP == state)
390 controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE); // If Popup shown hide it here so can be shown again if required.
393 if(controller.mImpl->IsShowingRealText() && (EventData::INACTIVE != state))
395 controller.mImpl->ChangeState(EventData::EDITING_WITH_GRAB_HANDLE);
396 relayoutNeeded = true;
400 if(controller.mImpl->IsShowingPlaceholderText() && !controller.mImpl->IsFocusedPlaceholderAvailable())
402 // Hide placeholder text
403 controller.ResetText();
406 if(EventData::INACTIVE == state)
408 controller.mImpl->ChangeState(EventData::EDITING);
410 else if(!controller.mImpl->IsClipboardEmpty())
412 controller.mImpl->ChangeState(EventData::EDITING_WITH_POPUP);
414 relayoutNeeded = true;
417 else if(2u == tapCount)
419 if(controller.mImpl->mEventData->mSelectionEnabled &&
420 controller.mImpl->IsShowingRealText())
422 relayoutNeeded = true;
423 controller.mImpl->mEventData->mIsLeftHandleSelected = true;
424 controller.mImpl->mEventData->mIsRightHandleSelected = true;
428 // Handles & cursors must be repositioned after Relayout() i.e. after the Model has been updated
431 Event event(Event::TAP_EVENT);
432 event.p1.mUint = tapCount;
435 controller.mImpl->mEventData->mEventQueue.push_back(event);
437 controller.mImpl->RequestRelayout();
441 // Reset keyboard as tap event has occurred.
442 controller.mImpl->ResetInputMethodContext();
445 void Controller::EventHandler::PanEvent(Controller& controller, GestureState state, const Vector2& displacement)
447 DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected PanEvent");
449 if(NULL != controller.mImpl->mEventData)
451 Event event(Event::PAN_EVENT);
452 event.p1.mInt = static_cast<int>(state);
453 event.p2.mFloat = displacement.x;
454 event.p3.mFloat = displacement.y;
455 controller.mImpl->mEventData->mEventQueue.push_back(event);
457 controller.mImpl->RequestRelayout();
461 void Controller::EventHandler::LongPressEvent(Controller& controller, GestureState state, float x, float y)
463 DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected LongPressEvent");
465 if((state == GestureState::STARTED) &&
466 (NULL != controller.mImpl->mEventData))
468 // The 1st long-press on inactive text-field is treated as tap
469 if(EventData::INACTIVE == controller.mImpl->mEventData->mState)
471 controller.mImpl->ChangeState(EventData::EDITING);
473 Event event(Event::TAP_EVENT);
477 controller.mImpl->mEventData->mEventQueue.push_back(event);
479 controller.mImpl->RequestRelayout();
481 else if(!controller.mImpl->IsShowingRealText())
483 Event event(Event::LONG_PRESS_EVENT);
484 event.p1.mInt = static_cast<int>(state);
487 controller.mImpl->mEventData->mEventQueue.push_back(event);
488 controller.mImpl->RequestRelayout();
490 else if(!controller.mImpl->IsClipboardVisible())
492 // Reset the InputMethodContext to commit the pre-edit before selecting the text.
493 controller.mImpl->ResetInputMethodContext();
495 Event event(Event::LONG_PRESS_EVENT);
496 event.p1.mInt = static_cast<int>(state);
499 controller.mImpl->mEventData->mEventQueue.push_back(event);
500 controller.mImpl->RequestRelayout();
502 controller.mImpl->mEventData->mIsLeftHandleSelected = true;
503 controller.mImpl->mEventData->mIsRightHandleSelected = true;
508 void Controller::EventHandler::SelectEvent(Controller& controller, float x, float y, SelectionType selectType)
510 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SelectEvent\n");
512 if(NULL != controller.mImpl->mEventData)
514 if(selectType == SelectionType::ALL)
516 Event event(Event::SELECT_ALL);
517 controller.mImpl->mEventData->mEventQueue.push_back(event);
519 else if(selectType == SelectionType::NONE)
521 Event event(Event::SELECT_NONE);
522 controller.mImpl->mEventData->mEventQueue.push_back(event);
526 Event event(Event::SELECT);
529 controller.mImpl->mEventData->mEventQueue.push_back(event);
532 controller.mImpl->mEventData->mCheckScrollAmount = true;
533 controller.mImpl->mEventData->mIsLeftHandleSelected = true;
534 controller.mImpl->mEventData->mIsRightHandleSelected = true;
535 controller.mImpl->RequestRelayout();
539 void Controller::EventHandler::SelectEvent(Controller& controller, const uint32_t start, const uint32_t end, SelectionType selectType)
541 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::SelectEvent\n");
543 if(NULL != controller.mImpl->mEventData)
545 if(selectType == SelectionType::RANGE)
547 Event event(Event::SELECT_RANGE);
548 event.p2.mUint = start;
549 event.p3.mUint = end;
550 controller.mImpl->mEventData->mEventQueue.push_back(event);
553 controller.mImpl->mEventData->mCheckScrollAmount = true;
554 controller.mImpl->mEventData->mIsLeftHandleSelected = true;
555 controller.mImpl->mEventData->mIsRightHandleSelected = true;
556 controller.mImpl->RequestRelayout();
560 void Controller::EventHandler::ProcessModifyEvents(Controller& controller)
562 Vector<ModifyEvent>& events = controller.mImpl->mModifyEvents;
564 if(0u == events.Count())
570 for(Vector<ModifyEvent>::ConstIterator it = events.Begin(),
571 endIt = events.End();
575 const ModifyEvent& event = *it;
577 if(ModifyEvent::TEXT_REPLACED == event.type)
579 // A (single) replace event should come first, otherwise we wasted time processing NOOP events
580 DALI_ASSERT_DEBUG(it == events.Begin() && "Unexpected TEXT_REPLACED event");
582 controller.TextReplacedEvent();
584 else if(ModifyEvent::TEXT_INSERTED == event.type)
586 controller.TextInsertedEvent();
588 else if(ModifyEvent::TEXT_DELETED == event.type)
590 // Placeholder-text cannot be deleted
591 if(!controller.mImpl->IsShowingPlaceholderText())
593 controller.TextDeletedEvent();
598 if(NULL != controller.mImpl->mEventData)
600 // When the text is being modified, delay cursor blinking
601 controller.mImpl->mEventData->mDecorator->DelayCursorBlink();
603 // Update selection position after modifying the text
604 controller.mImpl->mEventData->mLeftSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
605 controller.mImpl->mEventData->mRightSelectionPosition = controller.mImpl->mEventData->mPrimaryCursorPosition;
608 // DISCARD temporary text
612 void Controller::EventHandler::TextReplacedEvent(Controller& controller)
614 // The natural size needs to be re-calculated.
615 controller.mImpl->mRecalculateNaturalSize = true;
617 // The text direction needs to be updated.
618 controller.mImpl->mUpdateTextDirection = true;
620 // Apply modifications to the model
621 controller.mImpl->mOperationsPending = ALL_OPERATIONS;
624 void Controller::EventHandler::TextInsertedEvent(Controller& controller)
626 DALI_ASSERT_DEBUG(NULL != controller.mImpl->mEventData && "Unexpected TextInsertedEvent");
628 if(NULL == controller.mImpl->mEventData)
633 controller.mImpl->mEventData->mCheckScrollAmount = true;
635 // The natural size needs to be re-calculated.
636 controller.mImpl->mRecalculateNaturalSize = true;
638 // The text direction needs to be updated.
639 controller.mImpl->mUpdateTextDirection = true;
641 // Apply modifications to the model; TODO - Optimize this
642 controller.mImpl->mOperationsPending = ALL_OPERATIONS;
645 void Controller::EventHandler::TextDeletedEvent(Controller& controller)
647 DALI_ASSERT_DEBUG(NULL != controller.mImpl->mEventData && "Unexpected TextDeletedEvent");
649 if(NULL == controller.mImpl->mEventData)
654 if(!controller.IsEditable()) return;
656 controller.mImpl->mEventData->mCheckScrollAmount = true;
658 // The natural size needs to be re-calculated.
659 controller.mImpl->mRecalculateNaturalSize = true;
661 // The text direction needs to be updated.
662 controller.mImpl->mUpdateTextDirection = true;
664 // Apply modifications to the model; TODO - Optimize this
665 controller.mImpl->mOperationsPending = ALL_OPERATIONS;
668 bool Controller::EventHandler::DeleteEvent(Controller& controller, int keyCode)
670 DALI_LOG_INFO(gLogFilter, Debug::Verbose, "Controller::KeyEvent %p KeyCode : %d \n", &controller, keyCode);
672 bool removed = false;
674 if(NULL == controller.mImpl->mEventData)
679 if(!controller.IsEditable()) return false;
681 // InputMethodContext is no longer handling key-events
682 controller.mImpl->ClearPreEditFlag();
684 if(EventData::SELECTING == controller.mImpl->mEventData->mState)
686 removed = controller.RemoveSelectedText();
688 else if((controller.mImpl->mEventData->mPrimaryCursorPosition > 0) && (keyCode == Dali::DALI_KEY_BACKSPACE))
690 // Remove the character before the current cursor position
691 removed = controller.RemoveText(-1,
695 else if((controller.mImpl->mEventData->mPrimaryCursorPosition < controller.mImpl->mModel->mLogicalModel->mText.Count()) &&
696 (keyCode == Dali::DevelKey::DALI_KEY_DELETE))
698 // Remove the character after the current cursor position
699 removed = controller.RemoveText(0,
706 if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
707 !controller.mImpl->IsPlaceholderAvailable())
709 controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
713 controller.ShowPlaceholderText();
715 controller.mImpl->mEventData->mUpdateCursorPosition = true;
716 controller.mImpl->mEventData->mScrollAfterDelete = true;
722 InputMethodContext::CallbackData Controller::EventHandler::OnInputMethodContextEvent(Controller& controller, InputMethodContext& inputMethodContext, const InputMethodContext::EventData& inputMethodContextEvent)
724 // Whether the text needs to be relaid-out.
725 bool requestRelayout = false;
727 // Whether to retrieve the text and cursor position to be sent to the InputMethodContext.
728 bool retrieveText = false;
729 bool retrieveCursor = false;
731 switch(inputMethodContextEvent.eventName)
733 case InputMethodContext::COMMIT:
735 controller.InsertText(inputMethodContextEvent.predictiveString, Text::Controller::COMMIT);
736 requestRelayout = true;
737 retrieveCursor = true;
740 case InputMethodContext::PRE_EDIT:
742 controller.InsertText(inputMethodContextEvent.predictiveString, Text::Controller::PRE_EDIT);
743 requestRelayout = true;
744 retrieveCursor = true;
747 case InputMethodContext::DELETE_SURROUNDING:
749 const bool textDeleted = controller.RemoveText(inputMethodContextEvent.cursorOffset,
750 inputMethodContextEvent.numberOfChars,
751 DONT_UPDATE_INPUT_STYLE);
755 if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
756 !controller.mImpl->IsPlaceholderAvailable())
758 controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
762 controller.ShowPlaceholderText();
764 controller.mImpl->mEventData->mUpdateCursorPosition = true;
765 controller.mImpl->mEventData->mScrollAfterDelete = true;
767 requestRelayout = true;
771 case InputMethodContext::GET_SURROUNDING:
774 retrieveCursor = true;
777 case InputMethodContext::PRIVATE_COMMAND:
779 // PRIVATECOMMAND event is just for getting the private command message
781 retrieveCursor = true;
784 case InputMethodContext::VOID:
793 controller.mImpl->mOperationsPending = ALL_OPERATIONS;
794 controller.mImpl->RequestRelayout();
798 CharacterIndex cursorPosition = 0u;
799 Length numberOfWhiteSpaces = 0u;
803 numberOfWhiteSpaces = controller.mImpl->GetNumberOfWhiteSpaces(0u);
805 cursorPosition = controller.mImpl->GetLogicalCursorPosition();
807 if(cursorPosition < numberOfWhiteSpaces)
813 cursorPosition -= numberOfWhiteSpaces;
819 if(!controller.mImpl->IsShowingPlaceholderText())
821 // Retrieves the normal text string.
822 controller.mImpl->GetText(numberOfWhiteSpaces, text);
826 // When the current text is Placeholder Text, the surrounding text should be empty string.
827 // It means DALi should send empty string ("") to IME.
832 InputMethodContext::CallbackData callbackData((retrieveText || retrieveCursor), cursorPosition, text, false);
834 if(requestRelayout &&
835 (NULL != controller.mImpl->mEditableControlInterface))
837 // Do this last since it provides callbacks into application code
838 controller.mImpl->mEditableControlInterface->TextChanged(false);
844 void Controller::EventHandler::PasteClipboardItemEvent(Controller& controller)
846 // Retrieve the clipboard contents first
847 ClipboardEventNotifier notifier(ClipboardEventNotifier::Get());
848 std::string stringToPaste(notifier.GetContent());
850 // Commit the current pre-edit text; the contents of the clipboard should be appended
851 controller.mImpl->ResetInputMethodContext();
853 // Temporary disable hiding clipboard
854 controller.mImpl->SetClipboardHideEnable(false);
857 controller.PasteText(stringToPaste);
859 controller.mImpl->SetClipboardHideEnable(true);
862 void Controller::EventHandler::DecorationEvent(Controller& controller, HandleType handleType, HandleState state, float x, float y)
864 DALI_ASSERT_DEBUG(controller.mImpl->mEventData && "Unexpected DecorationEvent");
866 if(NULL != controller.mImpl->mEventData)
872 Event event(Event::GRAB_HANDLE_EVENT);
873 event.p1.mUint = state;
877 controller.mImpl->mEventData->mEventQueue.push_back(event);
880 case LEFT_SELECTION_HANDLE:
882 Event event(Event::LEFT_SELECTION_HANDLE_EVENT);
883 event.p1.mUint = state;
887 controller.mImpl->mEventData->mEventQueue.push_back(event);
890 case RIGHT_SELECTION_HANDLE:
892 Event event(Event::RIGHT_SELECTION_HANDLE_EVENT);
893 event.p1.mUint = state;
897 controller.mImpl->mEventData->mEventQueue.push_back(event);
900 case LEFT_SELECTION_HANDLE_MARKER:
901 case RIGHT_SELECTION_HANDLE_MARKER:
903 // Markers do not move the handles.
906 case HANDLE_TYPE_COUNT:
908 DALI_ASSERT_DEBUG(!"Controller::HandleEvent. Unexpected handle type");
912 controller.mImpl->RequestRelayout();
916 void Controller::EventHandler::TextPopupButtonTouched(Controller& controller, Dali::Toolkit::TextSelectionPopup::Buttons button)
918 if(NULL == controller.mImpl->mEventData)
925 case Toolkit::TextSelectionPopup::CUT:
927 if(!controller.IsEditable()) return;
928 controller.mImpl->SendSelectionToClipboard(true); // Synchronous call to modify text
929 controller.mImpl->mOperationsPending = ALL_OPERATIONS;
931 if((0u != controller.mImpl->mModel->mLogicalModel->mText.Count()) ||
932 !controller.mImpl->IsPlaceholderAvailable())
934 controller.mImpl->QueueModifyEvent(ModifyEvent::TEXT_DELETED);
938 controller.ShowPlaceholderText();
941 controller.mImpl->mEventData->mUpdateCursorPosition = true;
942 controller.mImpl->mEventData->mScrollAfterDelete = true;
944 controller.mImpl->RequestRelayout();
946 if(NULL != controller.mImpl->mEditableControlInterface)
948 controller.mImpl->mEditableControlInterface->TextChanged(true);
952 case Toolkit::TextSelectionPopup::COPY:
954 controller.mImpl->SendSelectionToClipboard(false); // Text not modified
956 controller.mImpl->mEventData->mUpdateCursorPosition = true;
958 controller.mImpl->RequestRelayout(); // Cursor, Handles, Selection Highlight, Popup
961 case Toolkit::TextSelectionPopup::PASTE:
963 controller.mImpl->RequestGetTextFromClipboard(); // Request clipboard service to retrieve an item
966 case Toolkit::TextSelectionPopup::SELECT:
968 const Vector2& currentCursorPosition = controller.mImpl->mEventData->mDecorator->GetPosition(PRIMARY_CURSOR);
970 if(controller.mImpl->mEventData->mSelectionEnabled)
972 // Creates a SELECT event.
973 controller.SelectEvent(currentCursorPosition.x, currentCursorPosition.y, SelectionType::INTERACTIVE);
977 case Toolkit::TextSelectionPopup::SELECT_ALL:
979 // Creates a SELECT_ALL event
980 controller.SelectEvent(0.f, 0.f, SelectionType::ALL);
983 case Toolkit::TextSelectionPopup::CLIPBOARD:
985 controller.mImpl->ShowClipboard();
988 case Toolkit::TextSelectionPopup::NONE:
998 } // namespace Toolkit