2 * Copyright (c) 2014 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.
18 #include <dali/dali.h>
20 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
21 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
22 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
23 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
24 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
26 #include <dali/integration-api/debug.h>
39 #if defined(DEBUG_ENABLED)
40 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
43 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
44 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
45 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
46 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
47 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
48 const Vector4 LIGHTBLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // Used for Selection highlight
50 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
51 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
52 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
53 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
54 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
56 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
57 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
58 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.1f ); ///< 1. Highlight rendered (z-offset).
59 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.2f ); ///< 2. Text rendered (z-offset).
60 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
62 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
63 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
64 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
65 const float TOP_HANDLE_TOP_OFFSET( 34.0f); ///< Offset between top handle and cutCopyPaste pop-up
66 const float BOTTOM_HANDLE_BOTTOM_OFFSET(34.0f); ///< Offset between bottom handle and cutCopyPaste pop-up
67 const float CURSOR_THICKNESS(4.0f);
68 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
69 const Vector4 DEFAULT_CURSOR_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
71 const std::string NEWLINE( "\n" );
73 const TextStyle DEFAULT_TEXT_STYLE;
75 const unsigned int SCROLL_TICK_INTERVAL = 50u;
76 const float SCROLL_THRESHOLD = 10.f;
77 const float SCROLL_SPEED = 15.f;
80 * Selection state enumeration (FSM)
84 SelectionNone, ///< Currently not encountered selected section.
85 SelectionStarted, ///< Encountered selected section
86 SelectionFinished ///< Finished selected section
89 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
91 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
95 if( ( *it ).mIsVisible )
97 return --cursorPosition;
106 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
108 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
110 if( ( *it ).mIsVisible )
112 return cursorPosition;
118 return cursorPosition;
122 * Whether the given position plus the cursor size offset is inside the given boundary.
124 * @param[in] position The given position.
125 * @param[in] cursorSize The cursor size.
126 * @param[in] controlSize The given boundary.
128 * @return whether the given position is inside the given boundary.
130 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
132 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
133 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
134 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
135 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
139 * Splits a text in two halves.
141 * If the text's number of characters is odd, firstHalf has one more character.
143 * @param[in] text The text to be split.
144 * @param[out] firstHalf The first half of the text.
145 * @param[out] secondHalf The second half of the text.
147 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
148 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
149 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
154 const std::size_t textLength = text.size();
155 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
157 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
158 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
161 } // end of namespace
169 const Property::Index TextInput::HIGHLIGHT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX;
170 const Property::Index TextInput::CUT_AND_PASTE_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+1;
171 const Property::Index TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+2;
172 const Property::Index TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+3;
173 const Property::Index TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+4;
174 const Property::Index TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+5;
175 const Property::Index TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+6;
176 const Property::Index TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+7;
177 const Property::Index TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+8;
178 const Property::Index TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+9;
179 const Property::Index TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+10;
180 const Property::Index TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+11;
181 const Property::Index TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+12;
182 const Property::Index TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+13;
183 const Property::Index TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+14;
184 const Property::Index TextInput::CURSOR_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+15;
195 return Toolkit::TextInput::New();
198 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
200 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
201 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
202 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
203 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
204 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
205 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
209 PropertyRegistration property1( typeRegistration, "highlight-color", Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
210 PropertyRegistration property2( typeRegistration, "cut-and-paste-bg-color", Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
211 PropertyRegistration property3( typeRegistration, "cut-and-paste-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
212 PropertyRegistration property4( typeRegistration, "cut-and-paste-icon-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
213 PropertyRegistration property5( typeRegistration, "cut-and-paste-icon-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
214 PropertyRegistration property6( typeRegistration, "cut-and-paste-text-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
215 PropertyRegistration property7( typeRegistration, "cut-and-paste-text-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
216 PropertyRegistration property8( typeRegistration, "cut-and-paste-border-color", Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
217 PropertyRegistration property9( typeRegistration, "cut-button-position-priority", Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
218 PropertyRegistration property10( typeRegistration, "copy-button-position-priority", Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
219 PropertyRegistration property11( typeRegistration, "paste-button-position-priority", Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
220 PropertyRegistration property12( typeRegistration, "select-button-position-priority", Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
221 PropertyRegistration property13( typeRegistration, "select-all-button-position-priority", Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
222 PropertyRegistration property14( typeRegistration, "clipboard-button-position-priority", Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
223 PropertyRegistration property15( typeRegistration, "popup-offset-from-text", Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
224 PropertyRegistration property16( typeRegistration, "cursor-color", Toolkit::TextInput::CURSOR_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
227 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
229 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
231 QuadCoordinates quad(x1, y1, x2, y2);
232 mQuadList.push_back( quad );
235 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
237 for(std::size_t i = 0;i < mQuadList.size(); i++)
239 QuadCoordinates& quad = mQuadList[i];
241 quad.min.Clamp(min, max);
242 quad.max.Clamp(min, max);
246 // [TextInput] ////////////////////////////////////////////////////////////////
248 Dali::Toolkit::TextInput TextInput::New()
250 // Create the implementation
251 TextInputPtr textInput(new TextInput());
252 // Pass ownership to CustomActor via derived handle
253 Dali::Toolkit::TextInput handle(*textInput);
254 handle.SetName( "TextInput");
256 textInput->Initialize();
260 TextInput::TextInput()
261 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
266 mDisplayedTextView(),
267 mStyledPlaceHolderText(),
268 mMaxStringLength( DEFAULT_MAX_SIZE ),
269 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
270 mCursorPosition( 0 ),
271 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
272 mIsSelectionHandleOneFlipped( false ),
273 mIsSelectionHandleTwoFlipped( false ),
274 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
275 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
276 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
277 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
278 mSelectionHandleOnePosition( 0 ),
279 mSelectionHandleTwoPosition( 0 ),
281 mPreEditStartPosition( 0 ),
282 mPreEditLength ( 0 ),
283 mNumberOfSurroundingCharactersDeleted( 0 ),
284 mTouchStartTime( 0 ),
286 mCurrentCopySelecton(),
289 mScrollDisplacement(),
290 mCurrentHandlePosition(),
291 mCurrentSelectionId(),
292 mCurrentSelectionHandlePosition(),
293 mRequestedSelection( 0, 0 ),
294 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
295 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
297 mMaterialColor( LIGHTBLUE ),
298 mPopupOffsetFromText ( Vector4( 0.0f, TOP_HANDLE_TOP_OFFSET, 0.0f, BOTTOM_HANDLE_BOTTOM_OFFSET ) ),
299 mOverrideAutomaticAlignment( false ),
300 mCursorRTLEnabled( false ),
301 mClosestCursorPositionEOL ( false ),
302 mCursorBlinkStatus( true ),
303 mCursorVisibility( false ),
304 mGrabHandleVisibility( false ),
305 mIsCursorInScrollArea( true ),
306 mIsGrabHandleInScrollArea( true ),
307 mEditModeActive( false ),
308 mEditOnTouch( true ),
309 mTextSelection( true ),
310 mExceedEnabled( true ),
311 mGrabHandleEnabled( true ),
312 mIsSelectionHandleFlipEnabled( true ),
313 mPreEditFlag( false ),
314 mIgnoreCommitFlag( false ),
315 mIgnoreFirstCommitFlag( false ),
316 mSelectingText( false ),
317 mPreserveCursorPosition( false ),
318 mSelectTextOnCommit( false ),
319 mUnderlinedPriorToPreEdit ( false ),
320 mCommitByKeyInput( false ),
321 mPlaceHolderSet( false ),
322 mMarkUpEnabled( false )
324 // Updates the line height accordingly with the input style.
328 TextInput::~TextInput()
330 StopCursorBlinkTimer();
335 std::string TextInput::GetText() const
339 // Return text-view's text only if the text-input's text is not empty
340 // in order to not to return the placeholder text.
341 if( !mStyledText.empty() )
343 text = mDisplayedTextView.GetText();
349 std::string TextInput::GetMarkupText() const
351 std::string markupString;
352 MarkupProcessor::GetMarkupString( mStyledText, markupString );
357 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
359 // Get the placeholder styled text array from the markup string.
360 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
362 if( mStyledText.empty() )
364 // Set the placeholder text only if the styled text is empty.
365 mDisplayedTextView.SetText( mStyledPlaceHolderText );
366 mPlaceHolderSet = true;
370 std::string TextInput::GetPlaceholderText()
372 // Traverses the styled placeholder array getting only the text.
373 // Note that for some languages a 'character' could be represented by more than one 'char'
375 std::string placeholderText;
376 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
378 placeholderText.append( (*it).mText.GetText() );
381 return placeholderText ;
384 void TextInput::SetInitialText(const std::string& initialText)
386 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
388 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
390 mPreEditFlag = false;
391 mIgnoreCommitFlag = true;
394 SetText( initialText );
395 PreEditReset( false ); // Reset keyboard as text changed
398 void TextInput::SetText(const std::string& initialText)
400 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
402 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
404 if( mStyledText.empty() )
406 // If the initial text is empty, set the placeholder text.
407 mDisplayedTextView.SetText( mStyledPlaceHolderText );
408 mPlaceHolderSet = true;
412 mDisplayedTextView.SetText( mStyledText );
413 mPlaceHolderSet = false;
418 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
420 ImfManager imfManager = ImfManager::Get();
423 imfManager.SetCursorPosition( mCursorPosition );
424 imfManager.SetSurroundingText( initialText );
425 imfManager.NotifyCursorPosition();
428 if( IsScrollEnabled() )
430 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
433 ShowGrabHandleAndSetVisibility( false );
442 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
444 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
446 mDisplayedTextView.SetText( styleText );
447 mPlaceHolderSet = false;
449 // If text alignment hasn't been manually set by application developer, then we
450 // automatically determine the alignment based on the content of the text i.e. what
451 // language the text begins with.
452 // TODO: This should determine different alignments for each line (broken by '\n') of text.
453 if(!mOverrideAutomaticAlignment)
455 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
456 bool leftToRight(true);
458 if( !styleText.empty() )
460 bool breakOut(false);
462 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
464 const Text& text = textIter->mText;
466 for( std::size_t i = 0; i < text.GetLength(); ++i )
468 Character character( text[i] );
469 if( character.GetCharacterDirection() != Character::Neutral )
471 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
479 // Based on this direction, either left or right align text if not manually set by application developer.
480 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
481 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
482 Toolkit::Alignment::VerticalTop ) );
483 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
489 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
491 mMaxStringLength = maxChars;
494 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
496 DALI_ASSERT_DEBUG( maxLines > 0 )
500 mNumberOflinesLimit = maxLines;
504 std::size_t TextInput::GetNumberOfLinesLimit() const
506 return mNumberOflinesLimit;
509 std::size_t TextInput::GetNumberOfCharacters() const
511 return mStyledText.size();
515 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
517 mMaterialColor = color;
518 if ( mCustomMaterial )
520 mCustomMaterial.SetDiffuseColor( mMaterialColor );
521 mMeshData.SetMaterial( mCustomMaterial );
525 const Vector4& TextInput::GetMaterialDiffuseColor() const
527 return mMaterialColor;
532 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
534 return mInputStartedSignalV2;
537 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
539 return mInputFinishedSignalV2;
542 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
544 return mCutAndPasteToolBarDisplayedV2;
547 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
549 return mStyleChangedSignalV2;
552 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
554 return mTextModifiedSignal;
557 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
559 return mMaxInputCharactersReachedSignalV2;
562 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
564 return mInputTextExceedBoundariesSignalV2;
567 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
569 Dali::BaseHandle handle( object );
571 bool connected( true );
572 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
574 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
576 textInput.InputStartedSignal().Connect( tracker, functor );
578 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
580 textInput.InputFinishedSignal().Connect( tracker, functor );
582 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
584 textInput.StyleChangedSignal().Connect( tracker, functor );
586 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
588 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
590 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
592 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
596 // signalName does not match any signal
603 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
607 // update line height before calculate the actual position.
612 if( setCursorOnTouchPoint )
614 // Sets the cursor position for the given touch point.
615 ReturnClosestIndex( touchPoint, mCursorPosition );
617 // Creates the grab handle.
618 if( IsGrabHandleEnabled() )
620 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
624 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
625 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
626 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
627 ShowGrabHandleAndSetVisibility( true );
629 // Scrolls the text-view if needed.
630 if( IsScrollEnabled() )
632 ScrollTextViewToMakeCursorVisible( cursorPosition );
638 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
650 bool TextInput::IsEditable() const
652 return mEditModeActive;
655 void TextInput::SetEditOnTouch( bool editOnTouch )
657 mEditOnTouch = editOnTouch;
660 bool TextInput::IsEditOnTouch() const
665 void TextInput::SetTextSelectable( bool textSelectable )
667 mTextSelection = textSelectable;
670 bool TextInput::IsTextSelectable() const
672 return mTextSelection;
675 bool TextInput::IsTextSelected() const
677 return mHighlightMeshActor;
680 void TextInput::DeSelectText()
687 void TextInput::SetGrabHandleImage(Dali::Image image )
691 CreateGrabHandle(image);
695 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
697 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
701 mCursor.SetImage( image );
702 mCursor.SetNinePatchBorder( border );
706 Vector3 TextInput::GetSelectionHandleSize()
708 return DEFAULT_SELECTION_HANDLE_SIZE;
711 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
713 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
717 mCursorRTL.SetImage( image);
718 mCursorRTL.SetNinePatchBorder( border );
722 void TextInput::EnableGrabHandle(bool toggle)
724 // enables grab handle with will in turn de-activate magnifier
725 mGrabHandleEnabled = toggle;
728 bool TextInput::IsGrabHandleEnabled()
730 // if false then magnifier will be shown instead.
731 return mGrabHandleEnabled;
734 void TextInput::EnableSelectionHandleFlip( bool toggle )
736 // Deprecated function. To be removed.
737 mIsSelectionHandleFlipEnabled = toggle;
740 bool TextInput::IsSelectionHandleFlipEnabled()
742 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
746 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
748 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
749 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
750 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
752 mSelectionHandleFlipMargin = margin;
755 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
757 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
758 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
760 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
761 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
763 const Vector4 boundary( originX,
765 originX + boundingRectangle.width,
766 originY + boundingRectangle.height );
768 mBoundingRectangleWorldCoordinates = boundary;
771 const Rect<float> TextInput::GetBoundingRectangle() const
773 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
775 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
776 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
778 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
783 const Vector4& TextInput::GetSelectionHandleFlipMargin()
785 return mSelectionHandleFlipMargin;
788 void TextInput::SetTextColor( const Vector4& color )
790 mDisplayedTextView.SetColor( color );
793 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
795 if( style != mInputStyle )
798 bool emitSignal = false;
800 // mask: modify style according to mask, if different emit signal.
801 const TextStyle oldInputStyle( mInputStyle );
803 // Copy the new style.
804 mInputStyle.Copy( style, mask );
806 // if style has changed, emit signal.
807 if( oldInputStyle != mInputStyle )
812 // Updates the line height accordingly with the input style.
815 // Changing font point size will require the cursor to be re-sized
820 EmitStyleChangedSignal();
825 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
827 if ( IsTextSelected() )
829 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
830 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
832 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
834 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
837 // Keeps the old style to be compared with the new one.
838 const TextStyle oldInputStyle( mInputStyle );
840 // Copy only those parameters from the style which are set in the mask.
841 mInputStyle.Copy( style, mask );
843 if( mInputStyle != oldInputStyle )
845 // Updates the line height accordingly with the input style.
848 EmitStyleChangedSignal();
853 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
855 if( !mStyledText.empty() )
857 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
861 TextStyle TextInput::GetStyleAtCursor() const
865 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
867 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
868 style = mStyledText.at( mCursorPosition-1 ).mStyle;
874 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
876 Dali::Font defaultFont = Dali::Font::New();
877 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
884 TextStyle TextInput::GetStyleAt( std::size_t position ) const
886 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
888 if( position >= mStyledText.size() )
890 position = mStyledText.size() - 1;
893 return mStyledText.at( position ).mStyle;
896 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
898 mDisplayedTextView.SetTextAlignment( align );
899 mOverrideAutomaticAlignment = true;
902 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
904 mDisplayedTextView.SetLineJustification( justification );
905 mOverrideAutomaticAlignment = true;
908 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
910 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
913 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
915 return mDisplayedTextView.GetFadeBoundary();
918 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
920 return mDisplayedTextView.GetTextAlignment();
923 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
925 mDisplayedTextView.SetMultilinePolicy( policy );
928 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
930 return mDisplayedTextView.GetMultilinePolicy();
933 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
935 mDisplayedTextView.SetWidthExceedPolicy( policy );
938 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
940 return mDisplayedTextView.GetWidthExceedPolicy();
943 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
945 mDisplayedTextView.SetHeightExceedPolicy( policy );
948 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
950 return mDisplayedTextView.GetHeightExceedPolicy();
953 void TextInput::SetExceedEnabled( bool enable )
955 mExceedEnabled = enable;
958 bool TextInput::GetExceedEnabled() const
960 return mExceedEnabled;
963 void TextInput::SetBackground(Dali::Image image )
965 // TODO Should add this function and add public api to match.
968 bool TextInput::OnTouchEvent(const TouchEvent& event)
973 bool TextInput::OnKeyEvent(const KeyEvent& event)
975 switch( event.state )
979 return OnKeyDownEvent(event);
985 return OnKeyUpEvent(event);
997 void TextInput::OnKeyInputFocusGained()
999 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1001 mEditModeActive = true;
1003 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1005 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1007 // Updates the line height accordingly with the input style.
1010 // Connect the signals to use in text input.
1011 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1012 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1014 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1017 GetTextLayoutInfo();
1020 SetCursorVisibility( true );
1021 StartCursorBlinkTimer();
1023 Toolkit::TextInput handle( GetOwner() );
1024 mInputStartedSignalV2.Emit( handle );
1026 ImfManager imfManager = ImfManager::Get();
1030 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1032 // Notify that the text editing start.
1033 imfManager.Activate();
1035 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1036 imfManager.SetRestoreAferFocusLost( true );
1038 imfManager.SetCursorPosition( mCursorPosition );
1039 imfManager.NotifyCursorPosition();
1042 mClipboard = Clipboard::Get(); // Store handle to clipboard
1044 // Now in edit mode we can accept string to paste from clipboard
1045 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1048 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1052 void TextInput::OnKeyInputFocusLost()
1054 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1058 // If key input focus is lost, it removes the
1059 // underline from the last pre-edit text.
1060 RemovePreEditStyle();
1061 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1062 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1066 ImfManager imfManager = ImfManager::Get();
1069 // The text editing is finished. Therefore the imf manager don't have restore activation.
1070 imfManager.SetRestoreAferFocusLost( false );
1072 // Notify that the text editing finish.
1073 imfManager.Deactivate();
1075 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1077 // Disconnect signal used the text input.
1078 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1080 Toolkit::TextInput handle( GetOwner() );
1081 mInputFinishedSignalV2.Emit( handle );
1082 mEditModeActive = false;
1083 mPreEditFlag = false;
1085 SetCursorVisibility( false );
1086 StopCursorBlinkTimer();
1088 ShowGrabHandleAndSetVisibility( false );
1091 // No longer in edit mode so do not want to receive string from clipboard
1092 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1095 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1098 Clipboard clipboard = Clipboard::Get();
1101 clipboard.HideClipboard();
1105 void TextInput::OnControlStageConnection()
1107 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1109 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1111 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1115 void TextInput::CreateActiveLayer()
1117 Actor self = Self();
1118 mActiveLayer = Layer::New();
1119 mActiveLayer.SetName ( "ActiveLayerActor" );
1121 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1122 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1123 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1125 self.Add( mActiveLayer );
1126 mActiveLayer.RaiseToTop();
1129 void TextInput::OnInitialize()
1131 CreateTextViewActor();
1135 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1136 // different positions depending on language)
1137 mCursor = CreateCursor(DEFAULT_CURSOR_COLOR);
1138 mCursorRTL = CreateCursor(DEFAULT_CURSOR_COLOR);
1140 Actor self = Self();
1141 self.Add( mCursor );
1142 self.Add( mCursorRTL );
1144 mCursorVisibility = false;
1146 CreateActiveLayer(); // todo move this so layer only created when needed.
1148 // Assign names to image actors
1149 mCursor.SetName("mainCursor");
1150 mCursorRTL.SetName("rtlCursor");
1153 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1155 mDisplayedTextView.SetSize( targetSize );
1156 GetTextLayoutInfo();
1157 mActiveLayer.SetSize(targetSize);
1160 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1162 Relayout( mDisplayedTextView, size, container );
1163 Relayout( mPopupPanel.GetRootActor(), size, container );
1165 GetTextLayoutInfo();
1170 Vector3 TextInput::GetNaturalSize()
1172 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1174 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1176 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1177 naturalSize.height = mLineHeight;
1183 float TextInput::GetHeightForWidth( float width )
1185 float height = mDisplayedTextView.GetHeightForWidth( width );
1187 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1189 // If the height is zero, it means there is no text. Let's return the cursor height.
1190 height = mLineHeight;
1196 /*end of Virtual methods from parent*/
1198 // Private Internal methods
1200 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1202 switch (gesture.state)
1204 case Gesture::Started:
1205 // fall through so code not duplicated
1206 case Gesture::Continuing:
1208 if (actor == mGrabArea)
1210 SetCursorVisibility( true );
1211 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1212 MoveGrabHandle( gesture.displacement );
1213 HidePopup(); // Do not show popup whilst handle is moving
1215 else if (actor == mHandleOneGrabArea)
1217 // the displacement in PanGesture is affected by the actor's rotation.
1218 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1219 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1221 MoveSelectionHandle( HandleOne, gesture.displacement );
1223 mState = StateDraggingHandle;
1226 else if (actor == mHandleTwoGrabArea)
1228 // the displacement in PanGesture is affected by the actor's rotation.
1229 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1230 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1232 MoveSelectionHandle( HandleTwo, gesture.displacement );
1234 mState = StateDraggingHandle;
1240 case Gesture::Finished:
1242 // Revert back to non-pressed selection handle images
1243 if (actor == mGrabArea)
1245 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1246 SetCursorVisibility( true );
1247 SetUpPopupSelection();
1250 if (actor == mHandleOneGrabArea)
1252 // the displacement in PanGesture is affected by the actor's rotation.
1253 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1254 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1256 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1258 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1260 ShowPopupCutCopyPaste();
1262 if (actor == mHandleTwoGrabArea)
1264 // the displacement in PanGesture is affected by the actor's rotation.
1265 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1266 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1268 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1270 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1272 ShowPopupCutCopyPaste();
1281 // Stop the flashing animation so easy to see when moved.
1282 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1284 if (touch.GetPoint(0).state == TouchPoint::Down)
1286 SetCursorVisibility( true );
1287 StopCursorBlinkTimer();
1289 else if (touch.GetPoint(0).state == TouchPoint::Up)
1291 SetCursorVisibility( true );
1292 StartCursorBlinkTimer();
1297 // selection handle one
1298 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1300 if (touch.GetPoint(0).state == TouchPoint::Down)
1302 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1304 else if (touch.GetPoint(0).state == TouchPoint::Up)
1306 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1311 // selection handle two
1312 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1314 if (touch.GetPoint(0).state == TouchPoint::Down)
1316 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1318 else if (touch.GetPoint(0).state == TouchPoint::Up)
1320 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1325 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1327 // If text exists then select nearest word.
1328 if ( !mStyledText.empty())
1332 ShowGrabHandleAndSetVisibility( false );
1337 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1338 // converts the pre-edit word being displayed to a committed word.
1339 if ( !mUnderlinedPriorToPreEdit )
1342 style.SetUnderline( false );
1343 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1345 mPreEditFlag = false;
1346 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1347 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1348 PreEditReset( false );
1350 mCursorPosition = 0;
1352 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1353 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1355 std::size_t start = 0;
1356 std::size_t end = 0;
1357 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1359 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1361 ImfManager imfManager = ImfManager::Get();
1364 imfManager.SetCursorPosition ( mCursorPosition );
1365 imfManager.NotifyCursorPosition();
1368 if ( !mStyledText.at(end-1).mText[0].IsWhiteSpace() )
1370 SelectText( start, end );
1371 ShowPopupCutCopyPaste();
1375 RemoveHighlight( false ); // Remove highlight but do not auto hide popup
1376 HidePopup( false ); // Hide popup with setting to do auto show.
1377 SetUpPopupSelection( false ); // Set to false so if nearest word is whitespace it will not show cut button.
1381 else if ( mClipboard && mClipboard.NumberOfItems() )
1383 ShowPopupCutCopyPaste();
1386 // If no text and clipboard empty then do nothing
1389 // TODO: Change the function name to be more general.
1390 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1392 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1393 , (mEditOnTouch)?"true":"false"
1394 , (mEditModeActive)?"true":"false");
1396 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1401 if( mGrabArea == actor )
1403 if( mPopupPanel.GetState() == TextInputPopup::StateHidden || mPopupPanel.GetState() == TextInputPopup::StateHiding )
1405 SetUpPopupSelection();
1415 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1417 // Initially don't create the grab handle.
1418 bool createGrabHandle = false;
1420 if ( !mEditModeActive )
1422 // update line height before calculate the actual position.
1425 // Only start edit mode if TextInput configured to edit on touch
1428 // Set the initial cursor position in the tap point.
1429 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1435 // Show the keyboard if it was hidden.
1436 if (!VirtualKeyboard::IsVisible())
1438 VirtualKeyboard::Show();
1441 // Reset keyboard as tap event has occurred.
1442 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1443 PreEditReset( true );
1445 GetTextLayoutInfo();
1447 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1449 // As already in edit mode, reposition cursor near tap and show grab handle for cursor, if grab handle not enabled then magnifier will be used instead.
1451 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1453 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1455 // Notify keyboard so it can 're-capture' word for predictive text.
1456 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1457 ImfManager imfManager = ImfManager::Get();
1460 imfManager.SetCursorPosition ( mCursorPosition );
1461 imfManager.NotifyCursorPosition();
1463 const TextStyle oldInputStyle( mInputStyle );
1465 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1469 // Create the grab handle.
1470 // Grab handle is created later.
1471 createGrabHandle = true;
1473 if( oldInputStyle != mInputStyle )
1475 // Updates the line height accordingly with the input style.
1478 EmitStyleChangedSignal();
1483 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1484 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1485 // otherwise the Grab handle will be shown when selecting.
1486 if ( createGrabHandle && IsGrabHandleEnabled() )
1488 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1492 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1493 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1494 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1495 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1500 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1502 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1504 // Ignore longpress if in selection mode already
1505 if( mHighlightMeshActor )
1510 if(longPress.state == Dali::Gesture::Started)
1512 // Start edit mode on long press
1513 if ( !mEditModeActive )
1518 // If text exists then select nearest word.
1519 if ( !mStyledText.empty())
1523 ShowGrabHandleAndSetVisibility( false );
1528 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1529 // converts the pre-edit word being displayed to a committed word.
1530 if ( !mUnderlinedPriorToPreEdit )
1533 style.SetUnderline( false );
1534 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1536 mPreEditFlag = false;
1537 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1538 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1539 PreEditReset( false );
1541 mCursorPosition = 0;
1543 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1544 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1546 std::size_t start = 0;
1547 std::size_t end = 0;
1548 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1550 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1552 ImfManager imfManager = ImfManager::Get();
1555 imfManager.SetCursorPosition ( mCursorPosition );
1556 imfManager.NotifyCursorPosition();
1559 SelectText( start, end );
1562 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1563 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1565 ShowPopupCutCopyPaste();
1570 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1572 const Text clipboardText( notifier.GetContent() );
1573 PasteText( clipboardText );
1575 SetCursorVisibility( true );
1576 StartCursorBlinkTimer();
1578 ShowGrabHandleAndSetVisibility( false );
1584 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1586 mPopupPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1588 const std::string& name = button.GetName();
1590 if(name == TextInputPopup::OPTION_SELECT_WORD)
1592 std::size_t start = 0;
1593 std::size_t end = 0;
1594 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1596 SelectText( start, end );
1598 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1600 SetCursorVisibility(false);
1601 StopCursorBlinkTimer();
1603 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1604 std::size_t start = 0;
1606 SelectText( start, end );
1608 else if(name == TextInputPopup::OPTION_CUT)
1610 bool ret = CopySelectedTextToClipboard();
1614 DeleteHighlightedText( true );
1618 SetCursorVisibility( true );
1619 StartCursorBlinkTimer();
1623 else if(name == TextInputPopup::OPTION_COPY)
1625 CopySelectedTextToClipboard();
1629 SetCursorVisibility( true );
1630 StartCursorBlinkTimer();
1634 else if(name == TextInputPopup::OPTION_PASTE)
1636 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1638 PasteText(retrievedString);
1640 SetCursorVisibility( true );
1641 StartCursorBlinkTimer();
1643 ShowGrabHandleAndSetVisibility( false );
1647 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1649 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1650 // Hence pass the false parameter for signalFinished.
1651 HidePopup( true, false );
1652 mClipboard.ShowClipboard();
1658 bool TextInput::OnCursorBlinkTimerTick()
1661 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1662 if ( mCursorRTLEnabled )
1664 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1666 mCursorBlinkStatus = !mCursorBlinkStatus;
1671 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1673 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1675 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1676 if(mHighlightMeshActor && mState == StateEdit)
1678 ShowPopupCutCopyPaste();
1682 //FIXME this routine needs to be re-written as it contains too many branches.
1683 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1685 std::string keyName = event.keyPressedName;
1686 std::string keyString = event.keyPressed;
1688 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1690 // Do not consume "Tab" and "Escape" keys.
1691 if(keyName == "Tab" || keyName == "Escape")
1693 // Escape key to end the edit mode
1699 HidePopup(); // If Pop-up shown then hides it as editing text.
1701 // Update Flag, indicates whether to update the text-input contents or not.
1702 // Any key stroke that results in a visual change of the text-input should
1703 // set this flag to true.
1706 // Whether to scroll text to cursor position.
1707 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1708 bool scroll = false;
1710 if (keyName == "Return")
1712 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1714 bool preEditFlagPreviouslySet( mPreEditFlag );
1716 // replaces highlighted text with new line
1717 DeleteHighlightedText( false );
1719 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1721 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1722 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1725 mCommitByKeyInput = true;
1728 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1729 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1731 mPreEditFlag = true;
1732 mIgnoreCommitFlag = false;
1742 else if ( keyName == "space" )
1744 if ( mHighlightMeshActor )
1746 // Some text is selected so erase it before adding space.
1747 DeleteHighlightedText( true );
1751 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1753 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1754 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1757 mCommitByKeyInput = true;
1762 else if (keyName == "BackSpace")
1764 if ( mHighlightMeshActor )
1766 // Some text is selected so erase it
1767 DeleteHighlightedText( true );
1772 if ( mCursorPosition > 0 )
1774 DeleteCharacter( mCursorPosition );
1780 else if (keyName == "Right")
1785 else if (keyName == "Left")
1787 AdvanceCursor(true);
1790 else // event is a character
1792 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1793 if ( !keyString.empty() )
1795 // replaces highlighted text with new character
1796 DeleteHighlightedText( false );
1798 // Received key String
1799 mCursorPosition += InsertAt( Text( keyString ), mCursorPosition, 0 );
1805 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1806 // as this is a costly operation.
1812 if(update || scroll)
1814 if( IsScrollEnabled() )
1816 // Calculates the new cursor position (in actor coordinates)
1817 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1819 ScrollTextViewToMakeCursorVisible( cursorPosition );
1826 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1828 std::string keyName = event.keyPressedName;
1829 std::string keyString = event.keyPressed;
1831 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1833 // The selected text become deselected when the key code is DALI_KEY_BACK.
1834 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1843 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1845 // Updates the stored scroll position.
1846 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1848 const Vector3& controlSize = GetControlSize();
1849 Size cursorSize( CURSOR_THICKNESS, 0.f );
1851 // Updates the cursor and grab handle position and visibility.
1852 if( mGrabHandle || mCursor )
1854 cursorSize.height = GetRowRectFromCharacterPosition( mCursorPosition ).height;
1855 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1857 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1859 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1863 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1864 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1869 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1870 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1874 // Updates the selection handles and highlighted text position and visibility.
1875 if( mSelectionHandleOne && mSelectionHandleTwo )
1877 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1878 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1879 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1880 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1881 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1882 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1884 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1885 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1887 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1888 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1889 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1890 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1892 if( mHighlightMeshActor )
1894 mHighlightMeshActor.SetVisible( true );
1900 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1902 // Scroll the text to make the cursor visible.
1903 const Size cursorSize( CURSOR_THICKNESS,
1904 GetRowRectFromCharacterPosition( mCursorPosition ).height );
1906 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1908 const Vector3& controlSize = GetControlSize();
1910 // Calculates the new scroll position.
1911 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1912 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1914 scrollOffset.x += cursorPosition.x;
1917 if( cursorPosition.y - cursorSize.height < 0.f || cursorPosition.y > controlSize.height )
1919 scrollOffset.y += cursorPosition.y;
1922 // Sets the new scroll position.
1923 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1924 SetScrollPosition( scrollOffset );
1927 void TextInput::StartScrollTimer()
1931 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1932 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1935 if( !mScrollTimer.IsRunning() )
1937 mScrollTimer.Start();
1941 void TextInput::StopScrollTimer()
1945 mScrollTimer.Stop();
1949 bool TextInput::OnScrollTimerTick()
1951 // TODO: need to set the new style accordingly the new handle position.
1953 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1955 // nothing to do if all handles are invisible or doesn't exist.
1961 // Choose between the grab handle or the selection handles.
1962 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1963 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1964 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1966 std::size_t newCursorPosition = 0;
1967 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1969 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1970 // the new selection handle's position needs to be different of the other one.
1971 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1972 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1973 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1975 if( differentSelectionHandles )
1977 handlePosition = newCursorPosition;
1979 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1981 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1983 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1984 scrollPosition += scrollDelta;
1985 SetScrollPosition( scrollPosition );
1987 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1992 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1995 actualHandlePosition.x += mScrollDisplacement.x;
1996 actualHandlePosition.y += mScrollDisplacement.y;
2001 // Public Internal Methods (public for testing purpose)
2003 void TextInput::SetUpTouchEvents()
2005 if ( !mTapDetector )
2007 mTapDetector = TapGestureDetector::New();
2008 // Attach the actors and connect the signal
2009 mTapDetector.Attach(Self());
2011 // As contains children which may register for tap the default control detector is not used.
2012 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2015 if ( !mDoubleTapDetector )
2017 mDoubleTapDetector = TapGestureDetector::New();
2018 mDoubleTapDetector.SetTapsRequired( 2 );
2019 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2021 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2022 // so that we do not, unnecessarily, have a double tap request all the time
2025 if ( !mPanGestureDetector )
2027 mPanGestureDetector = PanGestureDetector::New();
2028 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2031 if ( !mLongPressDetector )
2033 mLongPressDetector = LongPressGestureDetector::New();
2034 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2035 mLongPressDetector.Attach(Self());
2039 void TextInput::CreateTextViewActor()
2041 mDisplayedTextView = Toolkit::TextView::New();
2042 mDisplayedTextView.SetName( "DisplayedTextView ");
2043 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2044 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2045 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2046 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2047 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2048 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2049 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2050 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2051 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2052 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2054 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2056 Self().Add( mDisplayedTextView );
2059 // Start a timer to initiate, used by the cursor to blink.
2060 void TextInput::StartCursorBlinkTimer()
2062 if ( !mCursorBlinkTimer )
2064 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2065 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2068 if ( !mCursorBlinkTimer.IsRunning() )
2070 mCursorBlinkTimer.Start();
2074 // Start a timer to initiate, used by the cursor to blink.
2075 void TextInput::StopCursorBlinkTimer()
2077 if ( mCursorBlinkTimer )
2079 mCursorBlinkTimer.Stop();
2083 void TextInput::StartEditMode()
2085 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2087 if(!mEditModeActive)
2092 if ( mDoubleTapDetector )
2094 mDoubleTapDetector.Attach( Self() );
2098 void TextInput::EndEditMode()
2100 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2102 ClearKeyInputFocus();
2104 if ( mDoubleTapDetector )
2106 mDoubleTapDetector.Detach( Self() );
2110 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2112 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2114 mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2116 style.SetUnderline( true );
2117 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2121 void TextInput::RemovePreEditStyle()
2123 if ( !mUnderlinedPriorToPreEdit )
2126 style.SetUnderline( false );
2127 SetActiveStyle( style, TextStyle::UNDERLINE );
2131 // IMF related methods
2134 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2136 bool update( false );
2137 bool preeditResetRequired ( false );
2139 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2141 HidePopup(); // If Pop-up shown then hides it as editing text.
2144 switch ( imfEvent.eventName )
2146 case ImfManager::PREEDIT:
2148 mIgnoreFirstCommitFlag = false;
2150 // Some text may be selected, hiding keyboard causes an empty predictive string to be sent, we don't want to delete highlight in this case
2151 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2153 // replaces highlighted text with new character
2154 DeleteHighlightedText( false );
2157 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2159 if( IsScrollEnabled() )
2161 // Calculates the new cursor position (in actor coordinates)
2162 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2163 ScrollTextViewToMakeCursorVisible( cursorPosition );
2170 case ImfManager::COMMIT:
2172 if( mIgnoreFirstCommitFlag )
2174 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2175 mIgnoreFirstCommitFlag = false;
2179 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2181 // Some text may be selected, hiding keyboard causes an empty predictive string to be sent, we don't want to delete highlight in this case
2182 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2184 // replaces highlighted text with new character
2185 DeleteHighlightedText( false );
2188 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2189 // not needed, one such scenario is when the pre-edit word is too long to fit.
2190 if ( !mIgnoreCommitFlag )
2192 update = CommitReceived( imfEvent.predictiveString );
2196 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2202 if( IsScrollEnabled() )
2204 // Calculates the new cursor position (in actor coordinates)
2205 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2207 ScrollTextViewToMakeCursorVisible( cursorPosition );
2212 case ImfManager::DELETESURROUNDING:
2214 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2215 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2217 mPreEditFlag = false;
2219 std::size_t toDelete = 0;
2220 std::size_t numberOfCharacters = 0;
2222 if( mHighlightMeshActor )
2224 // delete highlighted text.
2225 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2226 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2230 if( static_cast<std::size_t>(std::abs( imfEvent.cursorOffset )) < mCursorPosition )
2232 toDelete = mCursorPosition + imfEvent.cursorOffset;
2234 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2236 numberOfCharacters = mStyledText.size() - toDelete;
2240 numberOfCharacters = imfEvent.numberOfChars;
2243 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2244 DeleteRange( toDelete, numberOfCharacters );
2246 mCursorPosition = toDelete;
2247 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2251 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2254 case ImfManager::GETSURROUNDING:
2256 // If text is selected/highlighted and surrounding text received we do not want the keyboard to store the word at cursor and return it as a predictive word along with
2257 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2258 if (! ( mHighlightMeshActor || mSelectingText ) )
2260 std::string text( GetText() );
2261 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2263 imfManager.SetCursorPosition( mCursorPosition );
2264 imfManager.SetSurroundingText( text );
2267 if( 0 != mNumberOfSurroundingCharactersDeleted )
2269 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2270 mNumberOfSurroundingCharactersDeleted = 0;
2272 if( mStyledText.empty() )
2274 // Styled text is empty, so set the placeholder text.
2275 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2276 mPlaceHolderSet = true;
2281 case ImfManager::VOID:
2283 DALI_ASSERT_DEBUG( false );
2287 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2289 return callbackData;
2292 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2294 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2296 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2297 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2299 bool preeditResetRequest ( false );
2301 if( mPreEditFlag ) // Already in pre-edit state.
2303 if( mStyledText.size() >= mMaxStringLength )
2305 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2306 // Cannot fit these characters into field, clear pre-edit.
2307 if ( !mUnderlinedPriorToPreEdit )
2310 style.SetUnderline( false );
2311 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2313 mIgnoreCommitFlag = true;
2314 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2315 mPreEditFlag = false;
2316 EmitMaxInputCharactersReachedSignal();
2320 // delete existing pre-edit string
2321 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2323 // Store new pre-edit string
2324 mPreEditString.SetText( keyString );
2326 if ( keyString.empty() )
2328 mPreEditFlag = false;
2329 mCursorPosition = mPreEditStartPosition;
2331 if( mStyledText.empty() )
2333 // Styled text is empty, so set the placeholder text.
2334 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2335 mPlaceHolderSet = true;
2339 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2341 GetTextLayoutInfo();
2346 // Insert new pre-edit string. InsertAt updates the size and position table.
2347 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2348 // If word was too long to be inserted then cursorOffset would be out of range as keyboard assumes there is not limit. Hence use of std::min.
2349 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2350 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2351 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2354 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2358 else // mPreEditFlag not set
2360 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2362 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2363 // new pre-edit so move into pre-edit state by setting flag
2364 mPreEditFlag = true;
2365 mPreEditString.SetText( keyString ); // store new pre-edit string
2366 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2367 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2368 // If word was too long to be inserted then cursorOffset would be out of range as keyboard assumes there is not limit. Hence use of std::min.
2369 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2370 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2371 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2372 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2378 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2382 return preeditResetRequest;
2385 bool TextInput::CommitReceived(const std::string& keyString )
2387 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2388 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2390 bool update( false );
2392 RemovePreEditStyle();
2394 const std::size_t styledTextSize( mStyledText.size() );
2395 if( styledTextSize >= mMaxStringLength )
2397 // Cannot fit these characters into field, clear pre-edit.
2400 mIgnoreCommitFlag = true;
2401 mPreEditFlag = false;
2403 EmitMaxInputCharactersReachedSignal();
2409 // delete existing pre-edit string
2410 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2411 mPreEditFlag = false;
2413 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2414 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2416 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2418 // No need to update cursor position as Cursor location given by touch.
2419 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2420 mPreserveCursorPosition = false;
2424 // Cursor not set by touch so needs to be re-positioned to input more text
2425 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2427 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2428 if ( mCommitByKeyInput )
2430 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2431 mCommitByKeyInput = false;
2437 if ( mSelectTextOnCommit )
2439 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2444 else // mPreEditFlag not set
2446 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2448 if( mStyledText.empty() && mPlaceHolderSet )
2450 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2451 mDisplayedTextView.SetText( "" );
2452 mNumberOfSurroundingCharactersDeleted = 0;
2453 mPlaceHolderSet = false;
2455 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2457 mNumberOfSurroundingCharactersDeleted = 0;
2462 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2467 mSelectTextOnCommit = false;
2469 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2470 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2475 // End of IMF related methods
2477 std::size_t TextInput::DeletePreEdit()
2479 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2481 DALI_ASSERT_DEBUG( mPreEditFlag );
2483 const std::size_t preEditStringLength = mPreEditString.GetLength();
2484 const std::size_t styledTextSize = mStyledText.size();
2486 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2488 // Prevents erase items outside mStyledText bounds.
2489 if( mPreEditStartPosition > styledTextSize )
2491 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2492 mPreEditStartPosition = styledTextSize;
2495 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2497 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2498 endPosition = styledTextSize;
2501 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2503 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2504 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2506 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2508 return preEditStringLength;
2511 void TextInput::PreEditReset( bool preserveCursorPosition )
2513 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2514 preserveCursorPosition, mCursorPosition);
2516 // Store flag to indicate that we do not want to lose the cursor position as the reset may have occurred due to touch event moving the cursor.
2517 mPreserveCursorPosition = preserveCursorPosition;
2519 // Reset incase we are in a pre-edit state.
2520 ImfManager imfManager = ImfManager::Get();
2523 imfManager.Reset(); // Will trigger a commit message
2527 void TextInput::CursorUpdate()
2531 ImfManager imfManager = ImfManager::Get();
2534 std::string text( GetText() );
2535 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2536 imfManager.SetCursorPosition ( mCursorPosition );
2537 imfManager.NotifyCursorPosition();
2541 /* Delete highlighted characters redisplay*/
2542 void TextInput::DeleteHighlightedText( bool inheritStyle )
2544 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2546 if( mHighlightMeshActor )
2548 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2550 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2551 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2553 // Get the styled text of the characters to be deleted as it may be needed if
2554 // the "exceed the text-input's boundaries" option is disabled.
2555 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2557 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2559 mStyledText.erase( start, end ); // erase range of characters
2561 // Remove text from TextView.
2563 if( mStyledText.empty() )
2565 // Styled text is empty, so set the placeholder text.
2566 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2567 mPlaceHolderSet = true;
2571 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2573 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2575 // It may happen than after removing a white space or a new line character,
2576 // two words merge, this new word could be big enough to not fit in its
2577 // current line, so moved to the next one, and make some part of the text to
2578 // exceed the text-input's boundary.
2579 if( !mExceedEnabled )
2581 // Get the new text layout after removing some characters.
2582 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2584 // Get text-input's size.
2585 const Vector3& size = GetControlSize();
2587 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2588 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2590 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2592 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2593 styledCharactersToDelete.begin(),
2594 styledCharactersToDelete.end() );
2598 GetTextLayoutInfo();
2606 const TextStyle oldInputStyle( mInputStyle );
2608 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2610 if( oldInputStyle != mInputStyle )
2612 // Updates the line height accordingly with the input style.
2615 EmitStyleChangedSignal();
2621 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2623 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2624 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2626 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2629 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2631 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2632 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2634 mStyledText.erase(itStart, itEnd);
2636 // update the selection handles if they are visible.
2637 if( mHighlightMeshActor )
2639 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2640 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2642 if( minHandle >= start + ncharacters )
2644 minHandle -= ncharacters;
2646 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2651 if( maxHandle >= start + ncharacters )
2653 maxHandle -= ncharacters;
2655 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2661 // Set text is not called here as currently it can not process the set text from deletion and then the set text from the in-coming pre-edit.
2664 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2666 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2667 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2668 // Mean we do not re-draw the text more than we have too.
2671 /* Delete character at current cursor position and redisplay*/
2672 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2674 // Ensure positionToDelete is not out of bounds.
2675 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2676 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2677 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2679 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2682 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2684 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2686 // Get the styled text of the character to be deleted as it may be needed if
2687 // the "exceed the text-input's boundaries" option is disabled.
2688 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2690 mStyledText.erase(it); // erase the character left of positionToDelete
2692 if( mStyledText.empty() )
2694 // Styled text is empty, so set the placeholder text.
2695 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2696 mPlaceHolderSet = true;
2700 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2702 const Character characterToDelete = styledCharacterToDelete.mText[0];
2704 // It may happen than after removing a white space or a new line character,
2705 // two words merge, this new word could be big enough to not fit in its
2706 // current line, so moved to the next one, and make some part of the text to
2707 // exceed the text-input's boundary.
2708 if( !mExceedEnabled )
2710 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2712 // Get the new text layout after removing one character.
2713 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2715 // Get text-input's size.
2716 const Vector3& size = GetControlSize();
2718 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2719 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2721 MarkupProcessor::StyledTextArray array;
2722 array.push_back( styledCharacterToDelete );
2723 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2725 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2730 GetTextLayoutInfo();
2732 ShowGrabHandleAndSetVisibility( false );
2734 mCursorPosition = positionToDelete -1;
2736 const TextStyle oldInputStyle( mInputStyle );
2738 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2740 if( oldInputStyle != mInputStyle )
2742 // Updates the line height accordingly with the input style.
2745 EmitStyleChangedSignal();
2750 /*Insert new character into the string and (optionally) redisplay text-input*/
2751 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2753 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2755 // Ensure insertionPosition is not out of bounds.
2756 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2758 bool textExceedsMaximunNumberOfCharacters = false;
2759 bool textExceedsBoundary = false;
2760 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2762 ShowGrabHandleAndSetVisibility( false );
2764 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2768 mIgnoreCommitFlag = true;
2769 mPreEditFlag = false;
2770 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2771 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2774 if( textExceedsMaximunNumberOfCharacters )
2776 EmitMaxInputCharactersReachedSignal();
2779 if( textExceedsBoundary )
2781 EmitInputTextExceedsBoundariesSignal();
2782 PreEditReset( false );
2786 return insertedStringLength;
2789 ImageActor TextInput::CreateCursor( const Vector4& color)
2792 cursor = CreateSolidColorActor(color);
2793 cursor.SetName( "Cursor" );
2795 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2796 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_LEFT);
2797 cursor.SetVisible(false);
2802 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2804 // As cursor is not moving due to grab handle, handle should be hidden.
2805 ShowGrabHandleAndSetVisibility( false );
2807 bool cursorPositionChanged = false;
2810 if ( mCursorPosition >= places )
2812 mCursorPosition = mCursorPosition - places;
2813 cursorPositionChanged = true;
2818 if ((mCursorPosition + places) <= mStyledText.size())
2820 mCursorPosition = mCursorPosition + places;
2821 cursorPositionChanged = true;
2825 if( cursorPositionChanged )
2827 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2829 const TextStyle oldInputStyle( mInputStyle );
2830 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2834 if( oldInputStyle != mInputStyle )
2836 // Updates the line height accordingly with the input style.
2839 EmitStyleChangedSignal();
2842 ImfManager imfManager = ImfManager::Get();
2845 imfManager.SetCursorPosition ( mCursorPosition );
2846 imfManager.NotifyCursorPosition();
2851 void TextInput::DrawCursor()
2853 const Size rowRect = GetRowRectFromCharacterPosition( mCursorPosition );
2855 // Get height of cursor and set its size
2856 Size size( CURSOR_THICKNESS, 0.0f );
2857 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
2859 size.height = rowRect.height;
2863 // Measure Font so know how big text will be if no initial text to measure.
2864 size.height = mLineHeight;
2867 mCursor.SetSize(size);
2869 // If the character is italic then the cursor also tilts.
2870 mCursor.SetRotation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2872 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2874 if( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2876 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2877 bool altPositionValid; // Alternate cursor validity flag.
2878 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2879 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2881 SetAltCursorEnabled( altPositionValid );
2883 if( !altPositionValid )
2885 mCursor.SetPosition( position + UI_OFFSET );
2889 size.height *= 0.5f;
2890 mCursor.SetSize(size);
2891 mCursor.SetPosition( position + UI_OFFSET - Vector3( 0.0f, directionRTL ? 0.0f : size.height, 0.0f ) );
2893 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2894 size.height = rowRect.height * 0.5f;
2895 mCursorRTL.SetSize(size);
2896 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3( 0.0f, directionRTL ? size.height : 0.0f, 0.0f ) );
2899 if( IsScrollEnabled() )
2901 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2902 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2907 void TextInput::SetAltCursorEnabled( bool enabled )
2909 mCursorRTLEnabled = enabled;
2910 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2913 void TextInput::SetCursorVisibility( bool visible )
2915 mCursorVisibility = visible;
2916 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2917 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2920 void TextInput::CreateGrabHandle( Dali::Image image )
2926 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2930 mGrabHandleImage = image;
2933 mGrabHandle = ImageActor::New(mGrabHandleImage);
2934 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2935 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2937 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2939 ShowGrabHandleAndSetVisibility( false );
2941 CreateGrabArea( mGrabHandle );
2943 mActiveLayer.Add(mGrabHandle);
2947 void TextInput::CreateGrabArea( Actor& parent )
2949 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2950 mGrabArea.SetName( "GrabArea" );
2951 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2952 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2953 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2954 mTapDetector.Attach( mGrabArea );
2955 mPanGestureDetector.Attach( mGrabArea );
2956 mLongPressDetector.Attach( mGrabArea );
2958 parent.Add(mGrabArea);
2961 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2963 Vector3 actualHandlePosition;
2967 mActualGrabHandlePosition.x += displacement.x;
2968 mActualGrabHandlePosition.y += displacement.y;
2970 // Grab handle should jump to the nearest character and take cursor with it
2971 std::size_t newCursorPosition = 0;
2972 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2974 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2975 bool altPositionValid; // Alternate cursor validity flag.
2976 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2977 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition, directionRTL, altPosition, altPositionValid );
2979 if( altPositionValid )
2981 // Check which of the positions is the closest.
2982 if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( actualHandlePosition.x - mActualGrabHandlePosition.x ) )
2984 actualHandlePosition = altPosition;
2988 bool handleVisible = true;
2990 if( IsScrollEnabled() )
2992 const Vector3 controlSize = GetControlSize();
2993 const Size cursorSize = GetRowRectFromCharacterPosition( newCursorPosition );
2994 // Scrolls the text if the handle is not in a visible position
2995 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3002 mCurrentHandlePosition = actualHandlePosition;
3003 mScrollDisplacement = Vector2::ZERO;
3007 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3009 mScrollDisplacement.x = -SCROLL_SPEED;
3011 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3013 mScrollDisplacement.x = SCROLL_SPEED;
3015 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3017 mScrollDisplacement.y = -SCROLL_SPEED;
3019 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3021 mScrollDisplacement.y = SCROLL_SPEED;
3027 if( handleVisible && // Only redraw cursor and do updates if position changed
3028 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3030 mCursorPosition = newCursorPosition;
3032 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3034 const TextStyle oldInputStyle( mInputStyle );
3036 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3038 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3040 if( oldInputStyle != mInputStyle )
3042 // Updates the line height accordingly with the input style.
3045 EmitStyleChangedSignal();
3050 return actualHandlePosition;
3053 void TextInput::ShowGrabHandle( bool visible )
3055 if ( IsGrabHandleEnabled() )
3059 mGrabHandle.SetVisible( mGrabHandleVisibility );
3061 StartMonitoringStageForTouch();
3065 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3067 mGrabHandleVisibility = visible;
3068 ShowGrabHandle( visible );
3071 // Callbacks connected to be Property notifications for Boundary checking.
3073 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3075 mIsSelectionHandleOneFlipped = true;
3076 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3077 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3080 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3082 mIsSelectionHandleOneFlipped = false;
3083 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3084 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3087 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3089 mIsSelectionHandleTwoFlipped = true;
3090 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3091 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3094 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3096 mIsSelectionHandleTwoFlipped = false;
3097 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3098 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3101 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3102 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3104 mSelectionHandleOne.SetOpacity(0.0f);
3107 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3109 mSelectionHandleOne.SetOpacity(1.0f);
3112 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3114 mSelectionHandleTwo.SetOpacity(0.0f);
3117 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3119 mSelectionHandleTwo.SetOpacity(1.0f);
3122 // End of Callbacks connected to be Property notifications for Boundary checking.
3124 void TextInput::SetUpHandlePropertyNotifications()
3126 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3128 Vector3 handlesize = GetSelectionHandleSize();
3130 // Exceeding horizontal boundary
3131 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3132 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3134 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3135 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3137 // Within horizontal boundary
3138 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3139 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3141 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3142 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3144 // Exceeding vertical boundary
3145 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3146 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3147 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3148 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3150 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3151 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3152 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3153 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3155 // Within vertical boundary
3156 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3157 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3158 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3159 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3161 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3162 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3163 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3164 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3167 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3169 mSelectionHandleOnePosition = start;
3170 mSelectionHandleTwoPosition = end;
3172 if ( !mSelectionHandleOne )
3174 // create normal and pressed images
3175 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3176 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3178 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3179 mSelectionHandleOne.SetName("SelectionHandleOne");
3180 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3181 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3182 mIsSelectionHandleOneFlipped = false;
3183 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3185 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3186 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3188 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3189 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3191 mTapDetector.Attach( mHandleOneGrabArea );
3192 mPanGestureDetector.Attach( mHandleOneGrabArea );
3194 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3196 mSelectionHandleOne.Add( mHandleOneGrabArea );
3197 mActiveLayer.Add( mSelectionHandleOne );
3200 if ( !mSelectionHandleTwo )
3202 // create normal and pressed images
3203 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3204 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3206 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3207 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3208 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3209 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3210 mIsSelectionHandleTwoFlipped = false;
3211 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3213 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3214 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3215 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3216 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3218 mTapDetector.Attach( mHandleTwoGrabArea );
3219 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3221 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3223 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3225 mActiveLayer.Add( mSelectionHandleTwo );
3228 SetUpHandlePropertyNotifications();
3230 // update table as text may have changed.
3231 GetTextLayoutInfo();
3233 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3234 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3236 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3237 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3239 // Calculates and set the visibility if the scroll mode is enabled.
3240 bool isSelectionHandleOneVisible = true;
3241 bool isSelectionHandleTwoVisible = true;
3242 if( IsScrollEnabled() )
3244 const Vector3& controlSize( GetControlSize() );
3245 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3246 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3247 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3248 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3251 CreateHighlight(); // function will only create highlight if not already created.
3254 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3256 Vector3 actualHandlePosition;
3258 if ( mSelectionHandleOne && mSelectionHandleTwo )
3260 const Vector3& controlSize = GetControlSize();
3262 Size cursorSize( CURSOR_THICKNESS, 0.f );
3264 // Get a reference of the wanted selection handle (handle one or two).
3265 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3267 // Get a reference for the current position of the handle and a copy of its pair
3268 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3269 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3271 // Get a handle of the selection handle actor
3272 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3274 // Selection handles should jump to the nearest character
3275 std::size_t newHandlePosition = 0;
3276 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3278 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3280 bool handleVisible = true;
3282 if( IsScrollEnabled() )
3284 mCurrentSelectionId = handleId;
3286 cursorSize.height = GetRowRectFromCharacterPosition( newHandlePosition ).height;
3287 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3288 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3295 mCurrentSelectionHandlePosition = actualHandlePosition;
3296 mScrollDisplacement = Vector2::ZERO;
3300 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3302 mScrollDisplacement.x = -SCROLL_SPEED;
3304 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3306 mScrollDisplacement.x = SCROLL_SPEED;
3308 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3310 mScrollDisplacement.y = -SCROLL_SPEED;
3312 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3314 mScrollDisplacement.y = SCROLL_SPEED;
3320 if ( handleVisible && // Ensure the handle is visible.
3321 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3322 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3324 currentSelectionHandlePosition = newHandlePosition;
3326 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3327 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3331 if ( handleId == HandleOne )
3333 const TextStyle oldInputStyle( mInputStyle );
3335 // Set Active Style to that of first character in selection
3336 if( mSelectionHandleOnePosition < mStyledText.size() )
3338 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3341 if( oldInputStyle != mInputStyle )
3343 // Updates the line height accordingly with the input style.
3346 EmitStyleChangedSignal();
3352 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3355 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3357 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3358 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3360 if ( selectionHandleActor )
3362 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3363 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3364 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3366 if( IsScrollEnabled() )
3368 const Size cursorSize( CURSOR_THICKNESS,
3369 GetRowRectFromCharacterPosition( selectionHandlePosition ).height );
3370 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3372 GetControlSize() ) );
3377 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3379 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3380 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3381 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3382 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3384 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3386 // Deselect text prior to startSelectionIt
3387 for(;it!=startSelectionIt;++it)
3389 selectedVisualText[*it] = false;
3392 // Select text from startSelectionIt -> endSelectionIt
3393 for(;it!=endSelectionIt;++it)
3395 selectedVisualText[*it] = true;
3398 // Deselect text after endSelection
3401 selectedVisualText[*it] = false;
3404 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3407 // Calculate the dimensions of the quads they will make the highlight mesh
3408 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3410 // At the moment there is no public API to modify the block alignment option.
3411 const bool blockAlignEnabled = true;
3413 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3415 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3417 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3418 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3420 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3421 std::vector<bool> selectedVisualText;
3422 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3423 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3424 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3426 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3427 float rowLeft = 0.0f;
3428 float rowRight = 0.0f;
3429 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3430 float maxRowLeft = std::numeric_limits<float>::max();
3431 float maxRowRight = 0.0f;
3433 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3435 // Scan through entire text.
3438 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3440 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3441 bool charSelected( false );
3442 if( selectedIt != selectedEndIt )
3444 charSelected = *selectedIt++;
3447 if(selectionState == SelectionNone)
3451 selectionState = SelectionStarted;
3452 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3453 rowRight = rowLeft + charInfo.mSize.width;
3456 else if(selectionState == SelectionStarted)
3458 // break selection on:
3459 // 1. new line causing selection break. (\n or wordwrap)
3460 // 2. character not selected.
3461 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3464 // finished selection.
3465 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3466 // that it resides on. That way this enumeration is not necessary.
3468 if(lastIt->mIsNewParagraphChar)
3470 // If the last character is a new line, then to get the row rect, we need to scan from the character before the new line.
3471 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3473 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3474 maxRowLeft = std::min(maxRowLeft, min.x);
3475 maxRowRight = std::max(maxRowRight, max.x);
3476 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3477 float rowTop = rowBottom - rowSize.height;
3479 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3480 if(charSelected && blockAlignEnabled)
3482 rowRight = std::numeric_limits<float>::max();
3484 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3486 selectionState = SelectionNone;
3488 // Still selected? start a new selection
3491 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3492 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3493 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3494 selectionState = SelectionStarted;
3499 // build up highlight(s) with this selection data.
3500 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3501 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3508 // If reached end, and still on selection, then close selection.
3511 if(selectionState == SelectionStarted)
3513 // finished selection.
3515 if(lastIt->mIsNewParagraphChar)
3517 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3519 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3520 maxRowLeft = std::min(maxRowLeft, min.x);
3521 maxRowRight = std::max(maxRowRight, max.x);
3522 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3523 float rowTop = rowBottom - rowSize.height;
3524 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3528 // Get the top left and bottom right corners.
3529 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3530 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3531 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3533 // Clamp quads so they appear to clip to borders of the whole text.
3534 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3536 // For block-align align Further Clamp quads to max left and right extents
3537 if(blockAlignEnabled)
3539 // BlockAlign: Will adjust highlight to block:
3541 // H[ello] (top row right = max of all rows right)
3542 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3543 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3544 // [text] (bottom row left = min of all rows left)
3545 // (common in SMS messaging selection)
3547 // As opposed to the default which is tight text highlighting.
3552 // (common in regular text editors/web browser selection)
3554 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3557 // Finally clamp quads again so they don't exceed the boundry of the control.
3558 const Vector3& controlSize = GetControlSize();
3559 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3562 return mNewHighlightInfo;
3565 void TextInput::UpdateHighlight()
3567 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3569 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3571 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3572 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3573 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3574 // [BOTTOM] [ MIDDLE ]
3577 // Each quad is created as 2 triangles.
3578 // Middle is just 1 quad regardless of its size.
3592 if ( mHighlightMeshActor )
3594 // vertex and triangle buffers should always be present if MeshActor is alive.
3595 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3596 MeshData::VertexContainer vertices;
3597 Dali::MeshData::FaceIndices faceIndices;
3599 if( !newHighlightInfo.mQuadList.empty() )
3601 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3602 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3604 // vertex position defaults to (0 0 0)
3605 MeshData::Vertex vertex;
3606 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3609 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3611 // Add each quad geometry (a sub-selection) to the mesh data.
3621 QuadCoordinates& quad = *iter;
3623 vertex.x = quad.min.x;
3624 vertex.y = quad.min.y;
3625 vertices.push_back( vertex );
3628 vertex.x = quad.max.x;
3629 vertex.y = quad.min.y;
3630 vertices.push_back( vertex );
3632 // bottom-left (v+2)
3633 vertex.x = quad.min.x;
3634 vertex.y = quad.max.y;
3635 vertices.push_back( vertex );
3637 // bottom-right (v+3)
3638 vertex.x = quad.max.x;
3639 vertex.y = quad.max.y;
3640 vertices.push_back( vertex );
3642 // triangle A (3, 1, 0)
3643 faceIndices.push_back( v + 3 );
3644 faceIndices.push_back( v + 1 );
3645 faceIndices.push_back( v );
3647 // triangle B (0, 2, 3)
3648 faceIndices.push_back( v );
3649 faceIndices.push_back( v + 2 );
3650 faceIndices.push_back( v + 3 );
3652 mMeshData.SetFaceIndices( faceIndices );
3655 BoneContainer bones(0); // passed empty as bones not required
3656 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3657 mHighlightMesh.UpdateMeshData(mMeshData);
3662 void TextInput::ClearPopup()
3664 mPopupPanel.Clear();
3667 void TextInput::AddPopupOptions()
3669 mPopupPanel.AddPopupOptions();
3672 void TextInput::SetPopupPosition( const Vector3& position, const Vector2& alternativePosition )
3674 const Vector3& visiblePopUpSize = mPopupPanel.GetVisibileSize();
3676 Vector3 clampedPosition ( position );
3677 Vector3 tailOffsetPosition ( position );
3679 float xOffSet( 0.0f );
3681 Actor self = Self();
3682 const Vector3 textViewTopLeftWorldPosition = self.GetCurrentWorldPosition() - self.GetCurrentSize()*0.5f;
3684 const float popUpLeft = textViewTopLeftWorldPosition.x + position.x - visiblePopUpSize.width*0.5f;
3685 const float popUpTop = textViewTopLeftWorldPosition.y + position.y - visiblePopUpSize.height;
3687 // Clamp to left or right or of boundary
3688 if( popUpLeft < mBoundingRectangleWorldCoordinates.x )
3690 xOffSet = mBoundingRectangleWorldCoordinates.x - popUpLeft ;
3692 else if ( popUpLeft + visiblePopUpSize.width > mBoundingRectangleWorldCoordinates.z )
3694 xOffSet = mBoundingRectangleWorldCoordinates.z - ( popUpLeft + visiblePopUpSize.width );
3697 clampedPosition.x = position.x + xOffSet;
3698 tailOffsetPosition.x = -xOffSet;
3700 // Check if top left of PopUp outside of top bounding rectangle, if so then flip to lower position.
3701 bool flipTail( false );
3703 if ( popUpTop < mBoundingRectangleWorldCoordinates.y )
3705 clampedPosition.y = alternativePosition.y + visiblePopUpSize.height;
3709 mPopupPanel.GetRootActor().SetPosition( clampedPosition );
3710 mPopupPanel.SetTailPosition( tailOffsetPosition, flipTail );
3713 void TextInput::HidePopup(bool animate, bool signalFinished )
3715 if ( ( mPopupPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopupPanel.GetState() == TextInputPopup::StateShown ) )
3717 mPopupPanel.Hide( animate );
3719 if( animate && signalFinished )
3721 mPopupPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3726 void TextInput::ShowPopup( bool animate )
3729 Vector2 alternativePopupPosition;
3731 if(mHighlightMeshActor && mState == StateEdit)
3734 Vector3 bottomHandle; // referring to the bottom most point of the handle or the bottom line of selection.
3736 // When text is selected, show popup above top handle (and text), or below bottom handle.
3737 // topHandle: referring to the top most point of the handle or the top line of selection.
3738 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3740 topHandle = mSelectionHandleOneActualPosition;
3741 bottomHandle = mSelectionHandleTwoActualPosition;
3742 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3746 topHandle = mSelectionHandleTwoActualPosition;
3747 bottomHandle = mSelectionHandleOneActualPosition;
3748 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3750 topHandle.y += -mPopupOffsetFromText.y - rowSize.height;
3751 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3753 float xPosition = ( fabsf( topHandle.x - bottomHandle.x ) )*0.5f + std::min( mSelectionHandleOneActualPosition.x , mSelectionHandleTwoActualPosition.x );
3755 position.x = xPosition;
3757 // Alternative position if no upper space
3758 bottomHandle.y += GetSelectionHandleSize().y + mPopupOffsetFromText.w;
3759 alternativePopupPosition = Vector2 ( position.x, bottomHandle.y );
3763 // When no text is selected, show popup at world position of grab handle or cursor
3764 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3765 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3766 position.y -= ( mPopupOffsetFromText.y + rowSize.height );
3767 // if can't be positioned above, then position below row.
3768 alternativePopupPosition = Vector2( position.x, position.y ); // default if no grab handle
3771 // If grab handle enabled then position pop-up below the grab handle.
3772 alternativePopupPosition.y = rowSize.height + mGrabHandle.GetCurrentSize().height + mPopupOffsetFromText.w +50.0f;
3776 SetPopupPosition( position, alternativePopupPosition );
3779 mPopupPanel.Show( Self(), animate );
3780 StartMonitoringStageForTouch();
3782 mPopupPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3785 void TextInput::ShowPopupCutCopyPaste()
3789 mPopupPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3790 // Check the selected text is whole text or not.
3791 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3793 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3796 if ( !mStyledText.empty() && IsTextSelected() )
3798 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCopy, true );
3799 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCut, true );
3802 if( mClipboard && mClipboard.NumberOfItems() )
3804 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsPaste, true );
3805 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3810 mPopupPanel.Hide(false);
3814 void TextInput::SetUpPopupSelection( bool showCutButton )
3817 mPopupPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3818 // If no text exists then don't offer to select
3819 if ( !mStyledText.empty() )
3821 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3822 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelect, true );
3823 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCut, ( showCutButton && IsTextSelected() ) );
3825 // if clipboard has valid contents then offer paste option
3826 if( mClipboard && mClipboard.NumberOfItems() )
3828 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsPaste, true );
3829 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3834 mPopupPanel.Hide(false);
3837 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3842 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3843 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3844 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3845 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3847 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3849 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3851 float closestYdifference = std::numeric_limits<float>::max();
3852 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3853 std::size_t numberOfMatchedCharacters = 0;
3855 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3856 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3858 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3860 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3861 float baselinePosition = info.mPosition.y - info.mDescender;
3863 if( info.mIsVisible )
3865 // store difference between source y point and the y position of the current character
3866 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3868 if( currentYdifference < closestYdifference )
3870 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3871 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3872 closestYdifference = currentYdifference;
3873 matchedCharacters.clear();
3874 numberOfMatchedCharacters = 0; // reset count
3877 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3878 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3880 // ignore new line character.
3881 if( !info.mIsNewParagraphChar )
3883 matchedCharacters.push_back( info );
3884 numberOfMatchedCharacters++;
3888 } // End of loop checking each character's y position in the character layout table
3890 // Check if last character is a newline, if it is
3891 // then need pretend there is an imaginary line afterwards,
3892 // and check if user is touching below previous line.
3893 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3895 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewParagraphChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3897 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3901 // 2 Iterate through matching list of y positions and find closest matching X position.
3903 bool matched( false );
3905 // Traverse the characters in the visual order. VCC TODO: check for more than one line.
3906 std::size_t visualIndex = 0u;
3907 const std::size_t matchedCharactersSize = matchedCharacters.size();
3908 for( ; visualIndex < matchedCharactersSize; ++visualIndex )
3910 const Toolkit::TextView::CharacterLayoutInfo& info( *( matchedCharacters.begin() + mTextLayoutInfo.mCharacterVisualToLogicalMap[visualIndex] ) );
3912 if( info.mIsVisible )
3914 // stop when on left side of character's center.
3915 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3916 if( sourceScrollOffset.x < characterMidPointPosition )
3918 if(info.mIsRightToLeftCharacter)
3920 rightToLeftChar = true;
3922 glyphIntersection = info.mPosition.x;
3927 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3931 if( visualIndex == matchedCharactersSize )
3933 rightToLeftChar = lastRightToLeftChar;
3936 closestIndex = lineOffset + visualIndex;
3938 mClosestCursorPositionEOL = false; // reset
3939 if( ( visualIndex == matchedCharactersSize ) && !matched )
3941 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3944 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3945 if( rightToLeftChar && lastRightToLeftChar )
3947 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3952 // closestIndex is the visual index, need to convert it to the logical index
3953 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3955 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3957 // Checks for situations where user is touching between LTR and RTL
3958 // characters. To identify if the user means the end of a LTR string
3959 // or the beginning of an RTL string, and vice versa.
3960 if( closestIndex > 0 )
3962 if( rightToLeftChar && !lastRightToLeftChar )
3967 // A: In this touch range, the user is indicating that they wish to place
3968 // the cursor at the end of the LTR text.
3969 // B: In this touch range, the user is indicating that they wish to place
3970 // the cursor at the end of the RTL text.
3972 // Result of touching A area:
3973 // [.....LTR]|[RTL......]+
3975 // |: primary cursor (for typing LTR chars)
3976 // +: secondary cursor (for typing RTL chars)
3978 // Result of touching B area:
3979 // [.....LTR]+[RTL......]|
3981 // |: primary cursor (for typing RTL chars)
3982 // +: secondary cursor (for typing LTR chars)
3984 if( sourceScrollOffset.x < glyphIntersection )
3989 else if( !rightToLeftChar && lastRightToLeftChar )
3991 if( sourceScrollOffset.x < glyphIntersection )
3998 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3999 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
4000 // one further ahead
4001 if( rightToLeftChar && !lastRightToLeftChar )
4006 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
4008 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
4010 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
4019 float TextInput::GetLineJustificationPosition() const
4021 const Vector3& size = mDisplayedTextView.GetCurrentSize();
4022 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4023 float alignmentOffset = 0.f;
4025 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4026 if( alignment & Toolkit::Alignment::HorizontalLeft )
4028 alignmentOffset = 0.f;
4030 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4032 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4034 else if( alignment & Toolkit::Alignment::HorizontalRight )
4036 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4039 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4040 float justificationOffset = 0.f;
4042 switch( justification )
4044 case Toolkit::TextView::Left:
4046 justificationOffset = 0.f;
4049 case Toolkit::TextView::Center:
4051 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4054 case Toolkit::TextView::Right:
4056 justificationOffset = mTextLayoutInfo.mTextSize.width;
4059 case Toolkit::TextView::Justified:
4061 justificationOffset = 0.f;
4066 DALI_ASSERT_ALWAYS( false );
4070 return alignmentOffset + justificationOffset;
4073 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4075 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4076 A newline character is not inserted in this case */
4078 Vector3 cursorPosition;
4080 Toolkit::TextView::CharacterLayoutInfo currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4084 if( characterPosition > 0u )
4086 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1u ];
4088 // If previous character on a different line then use current characters position
4089 if( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4091 // VCC TODO: PositionCursorAfterWordWrap currently doesn't work for multiline. Need to check this branch.
4092 if ( mClosestCursorPositionEOL )
4094 cursorPosition = Vector3( previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z ) ;
4098 cursorPosition = Vector3( currentCharInfo.mPosition );
4107 // If the character is left to right, the position is the character's position plus its width.
4108 const float ltrOffset = !currentCharInfo.mIsRightToLeftCharacter ? currentCharInfo.mSize.width : 0.f;
4110 cursorPosition.x = currentCharInfo.mPosition.x + ltrOffset;
4111 cursorPosition.y = currentCharInfo.mPosition.y;
4114 return cursorPosition;
4117 Vector3 TextInput::GetActualPositionFromCharacterPosition( std::size_t characterPosition ) const
4119 bool direction = false;
4120 Vector3 alternatePosition;
4121 bool alternatePositionValid = false;
4123 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4126 Vector3 TextInput::GetActualPositionFromCharacterPosition( std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4128 DALI_ASSERT_DEBUG( ( mTextLayoutInfo.mCharacterLayoutInfoTable.size() == mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ) &&
4129 ( mTextLayoutInfo.mCharacterLayoutInfoTable.size() == mTextLayoutInfo.mCharacterVisualToLogicalMap.size() ) &&
4130 "TextInput::GetActualPositionFromCharacterPosition. All layout tables must have the same size." );
4132 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4134 alternatePositionValid = false;
4135 directionRTL = false;
4137 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4139 if( characterPosition == 0u )
4141 // When the cursor position is at the beginning, it should be at the start of the current character.
4142 // If the current character is LTR, then the start is on the right side of the glyph.
4143 // If the current character is RTL, then the start is on the left side of the glyph.
4145 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() ) ).mIsVisible )
4147 characterPosition = FindVisibleCharacter( Right, 0u );
4150 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4151 const float rtlOffset = info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f;
4153 cursorPosition.x = info.mPosition.x + rtlOffset;
4154 cursorPosition.y = info.mPosition.y;
4155 directionRTL = info.mIsRightToLeftCharacter;
4157 else if( characterPosition > 0u )
4159 // Get the direction of the paragraph.
4160 const std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition );
4161 const bool isParagraphRightToLeft = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + startCharacterPosition ) ).mIsRightToLeftCharacter;
4163 // When cursor is not at beginning, consider possibility of
4164 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4166 // Cursor position should be the end of the last character.
4167 // If the last character is LTR, then the end is on the right side of the glyph.
4168 // If the last character is RTL, then the end is on the left side of the glyph.
4170 --characterPosition;
4172 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition ) ).mIsVisible )
4174 characterPosition = FindVisibleCharacter( Left, characterPosition );
4177 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4178 if( ( characterPosition > 0u ) && info.mIsNewParagraphChar && !IsScrollEnabled() )
4180 // VCC TODO : check for a new paragraph character.
4182 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4183 const Vector3& size = GetControlSize();
4185 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4187 --characterPosition;
4189 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4192 if( !info.mIsNewParagraphChar )
4194 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4198 // VCC TODO : check for a new paragraph character.
4200 // When cursor points to first character on new line, position cursor at the start of this glyph.
4201 if( characterPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4203 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4204 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4206 cursorPosition.x = infoNext.mPosition.x + start;
4207 cursorPosition.y = infoNext.mPosition.y;
4211 // If cursor points to the end of text, then can only position
4212 // cursor where the new line starts based on the line-justification position.
4213 cursorPosition.x = GetLineJustificationPosition();
4215 if( characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size() )
4217 // If this is after the last character, then we can assume that the new cursor
4218 // should be exactly one row below the current row.
4220 const Size rowRect = GetRowRectFromCharacterPosition( characterPosition - 1u );
4221 cursorPosition.y = info.mPosition.y + rowRect.height;
4225 // If this is not after last character, then we can use this row's height.
4226 // should be exactly one row below the current row.
4228 const Size rowRect = GetRowRectFromCharacterPosition( characterPosition );
4229 cursorPosition.y = info.mPosition.y + rowRect.height;
4234 directionRTL = info.mIsRightToLeftCharacter;
4236 if( 1u < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4238 // 1. When the cursor is neither at the beginning or the end,
4239 // we can show multiple cursors under situations when the cursor is
4240 // between RTL and LTR text...
4241 if( characterPosition + 1u < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4243 std::size_t characterAltPosition = characterPosition + 1u;
4245 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterAltPosition ];
4247 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4249 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4250 // Text: [...LTR...]|[...RTL...]
4252 // Alternate cursor pos: ^
4253 // In which case we need to display an alternate cursor for the RTL text.
4255 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4256 alternatePosition.y = infoAlt.mPosition.y;
4257 alternatePositionValid = true;
4259 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4261 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4262 // Text: |[...RTL...] [...LTR....]
4264 // Alternate cursor pos: ^
4265 // In which case we need to display an alternate cursor for the RTL text.
4267 alternatePosition.x = infoAlt.mPosition.x;
4268 alternatePosition.y = infoAlt.mPosition.y;
4269 alternatePositionValid = true;
4274 // 2. When the cursor is at the end of the text,
4275 // and we have multi-directional text,
4276 // we can also consider showing mulitple cursors.
4277 // The rule here is:
4278 // If first and last characters on row are different
4279 // Directions, then two cursors need to be displayed.
4281 if( info.mIsRightToLeftCharacter != isParagraphRightToLeft )
4283 // The last character's direction is differernt than the first one of current paragraph.
4286 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ GetFirstCharacterWithSameDirection( characterPosition ) ];
4288 if(info.mIsRightToLeftCharacter)
4290 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4291 // Text: [...LTR...]|[...RTL...]
4293 // Alternate cursor pos: ^
4294 // In which case we need to display an alternate cursor for the RTL text, this cursor
4295 // should be at the end of the given line.
4297 alternatePosition.x = infoStart.mPosition.x + infoStart.mSize.width;
4298 alternatePosition.y = infoStart.mPosition.y;
4299 alternatePositionValid = true;
4301 else if(!info.mIsRightToLeftCharacter) // starting RTL
4303 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4304 // Text: |[...RTL...] [...LTR....]
4306 // Alternate cursor pos: ^
4307 // In which case we need to display an alternate cursor for the RTL text.
4309 alternatePosition.x = infoStart.mPosition.x;
4310 alternatePosition.y = infoStart.mPosition.y;
4311 alternatePositionValid = true;
4316 } // characterPosition > 0
4320 // If the character table is void, place the cursor accordingly the text alignment.
4321 const Vector3& size = GetControlSize();
4323 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4324 float alignmentOffset = 0.f;
4326 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4327 if( alignment & Toolkit::Alignment::HorizontalLeft )
4329 alignmentOffset = 0.f;
4331 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4333 alignmentOffset = 0.5f * ( size.width );
4335 else if( alignment & Toolkit::Alignment::HorizontalRight )
4337 alignmentOffset = size.width;
4340 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4341 cursorPosition.x = alignmentOffset;
4343 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4344 if( alignment & Toolkit::Alignment::VerticalTop )
4346 cursorPosition.y = mLineHeight;
4348 else if( alignment & Toolkit::Alignment::VerticalCenter )
4350 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4352 else if( alignment & Toolkit::Alignment::VerticalBottom )
4354 cursorPosition.y = size.height;
4358 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4359 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4360 if( alternatePositionValid )
4362 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4363 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4366 return cursorPosition;
4369 std::size_t TextInput::GetRowStartFromCharacterPosition( std::size_t logicalPosition ) const
4371 // scan string from current position to beginning of current line to note direction of line
4372 while( logicalPosition )
4375 if( mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsNewParagraphChar )
4382 return logicalPosition;
4385 std::size_t TextInput::GetFirstCharacterWithSameDirection( std::size_t logicalPosition ) const
4387 const bool isRightToLeft = mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsRightToLeftCharacter;
4389 while( logicalPosition )
4392 if( isRightToLeft != mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsRightToLeftCharacter )
4399 return logicalPosition;
4402 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4406 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4409 Size TextInput::GetRowRectFromCharacterPosition( std::size_t characterPosition, Vector2& min, Vector2& max ) const
4411 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4412 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4414 min = Vector2::ZERO;
4415 max = Vector2(0.0f, mLineHeight);
4419 DALI_ASSERT_DEBUG( characterPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4421 // Initializes the min and max position.
4422 const std::size_t initialPosition = ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) ? characterPosition - 1u : characterPosition;
4423 min = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + initialPosition ) ).mPosition.GetVectorXY();
4427 // 1) Find the line where the character is laid-out.
4428 for( Toolkit::TextView::LineLayoutInfoContainer::const_iterator lineIt = mTextLayoutInfo.mLines.begin(), lineEndIt = mTextLayoutInfo.mLines.end();
4429 !found && ( lineIt != mTextLayoutInfo.mLines.end() );
4432 const Toolkit::TextView::LineLayoutInfo& lineInfo( *lineIt );
4434 // Index within the whole text to the last character of the current line.
4435 std::size_t lastCharacterOfLine = 0u;
4437 Toolkit::TextView::LineLayoutInfoContainer::const_iterator lineNextIt = lineIt + 1u;
4438 if( lineNextIt != lineEndIt )
4440 lastCharacterOfLine = (*lineNextIt).mCharacterGlobalIndex - 1u;
4444 lastCharacterOfLine = mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1u;
4447 // Check if the given chracter position is within the line.
4448 if( ( lineInfo.mCharacterGlobalIndex <= initialPosition ) && ( initialPosition <= lastCharacterOfLine ) )
4450 // 2) Get the row rect of all laid-out characters on the line.
4452 // Need to scan all characters of the line because they are in the logical position.
4453 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + lineInfo.mCharacterGlobalIndex,
4454 endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + lastCharacterOfLine + 1u;
4458 const Toolkit::TextView::CharacterLayoutInfo& characterInfo( *it );
4460 min.x = std::min( min.x, characterInfo.mPosition.x );
4461 min.y = std::min( min.y, characterInfo.mPosition.y );
4462 max.x = std::max( max.x, characterInfo.mPosition.x + characterInfo.mSize.width );
4463 max.y = std::max( max.y, characterInfo.mPosition.y + characterInfo.mSize.height );
4470 return Size( max.x - min.x, max.y - min.y );
4473 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4475 Actor popUpPanel = mPopupPanel.GetRootActor();
4477 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4483 Dali::Actor parent( touchedActor.GetParent() );
4487 return WasTouchedCheck( parent );
4494 void TextInput::StartMonitoringStageForTouch()
4496 Stage stage = Stage::GetCurrent();
4497 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4500 void TextInput::EndMonitoringStageForTouch()
4502 Stage stage = Stage::GetCurrent();
4503 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4506 void TextInput::OnStageTouched(const TouchEvent& event)
4508 if( event.GetPointCount() > 0 )
4510 if ( TouchPoint::Down == event.GetPoint(0).state )
4512 const Actor touchedActor(event.GetPoint(0).hitActor);
4514 bool popUpShown( false );
4516 if ( ( mPopupPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopupPanel.GetState() == TextInputPopup::StateShown ) )
4521 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4523 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4525 EndMonitoringStageForTouch();
4526 HidePopup( true, false );
4529 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4531 EndMonitoringStageForTouch();
4532 ShowGrabHandleAndSetVisibility( false );
4538 void TextInput::SelectText(std::size_t start, std::size_t end)
4540 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4541 IsGrabHandleEnabled()?"true":"false",
4542 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4543 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4544 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4546 StartMonitoringStageForTouch();
4548 if ( mEditModeActive ) // Only allow text selection when in edit mode
4550 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4551 mSelectingText = true;
4553 std::size_t selectionStartPosition = std::min( start, end );
4555 // Hide grab handle when selecting.
4556 ShowGrabHandleAndSetVisibility( false );
4558 if( start != end ) // something to select
4560 SetCursorVisibility( false );
4561 StopCursorBlinkTimer();
4563 CreateSelectionHandles(start, end);
4566 const TextStyle oldInputStyle( mInputStyle );
4567 mInputStyle = GetStyleAt( selectionStartPosition ); // Inherit style from selected position.
4569 if( oldInputStyle != mInputStyle )
4571 // Updates the line height accordingly with the input style.
4574 EmitStyleChangedSignal();
4580 mSelectingText = false;
4584 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4586 MarkupProcessor::StyledTextArray currentSelectedText;
4588 if ( IsTextSelected() )
4590 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4591 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4593 for(; it != end; ++it)
4595 MarkupProcessor::StyledText& styledText( *it );
4596 currentSelectedText.push_back( styledText );
4599 return currentSelectedText;
4602 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4604 const std::size_t beginIndex = std::min( begin, end );
4605 const std::size_t endIndex = std::max( begin, end );
4608 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4610 // Create a styled text array used to replace the text into the text-view.
4611 MarkupProcessor::StyledTextArray text;
4612 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4614 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4615 GetTextLayoutInfo();
4617 if( IsScrollEnabled() )
4619 // Need to set the scroll position as the text's size may have changed.
4620 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4623 ShowGrabHandleAndSetVisibility( false );
4629 // Set Handle positioning as the new style may have repositioned the characters.
4630 SetSelectionHandlePosition(HandleOne);
4631 SetSelectionHandlePosition(HandleTwo);
4634 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4636 // Just hide the grab handle when keyboard is hidden.
4637 if (!keyboardShown )
4639 ShowGrabHandleAndSetVisibility( false );
4641 // If the keyboard is not now being shown, then hide the popup panel
4642 mPopupPanel.Hide( true );
4646 // Removes highlight and resumes edit mode state
4647 void TextInput::RemoveHighlight( bool hidePopup )
4649 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4651 if ( mHighlightMeshActor )
4653 if ( mSelectionHandleOne )
4655 mActiveLayer.Remove( mSelectionHandleOne );
4656 mSelectionHandleOne.Reset();
4657 mSelectionHandleOneOffset.x = 0.0f;
4659 if ( mSelectionHandleTwo )
4661 mActiveLayer.Remove( mSelectionHandleTwo );
4662 mSelectionHandleTwo.Reset();
4663 mSelectionHandleTwoOffset.x = 0.0f;
4666 mNewHighlightInfo.mQuadList.clear();
4668 Self().Remove( mHighlightMeshActor );
4670 SetCursorVisibility( true );
4671 StartCursorBlinkTimer();
4673 mHighlightMeshActor.Reset();
4674 // NOTE: We cannot dereference mHighlightMesh, due
4675 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4683 mSelectionHandleOnePosition = 0;
4684 mSelectionHandleTwoPosition = 0;
4687 void TextInput::CreateHighlight()
4689 if ( !mHighlightMeshActor )
4691 mMeshData = MeshData( );
4692 mMeshData.SetHasNormals( true );
4694 mCustomMaterial = Material::New("CustomMaterial");
4695 mCustomMaterial.SetDiffuseColor( mMaterialColor );
4697 mMeshData.SetMaterial( mCustomMaterial );
4699 mHighlightMesh = Mesh::New( mMeshData );
4701 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4702 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4703 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4704 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4705 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4706 mHighlightMeshActor.SetAffectedByLighting(false);
4708 Self().Add(mHighlightMeshActor);
4713 bool TextInput::CopySelectedTextToClipboard()
4715 mCurrentCopySelecton.clear();
4717 mCurrentCopySelecton = GetSelectedText();
4719 std::string stringToStore;
4721 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4722 * a marked up string.
4724 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4725 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4726 bool success = mClipboard.SetItem( stringToStore );
4730 void TextInput::PasteText( const Text& text )
4732 // Update Flag, indicates whether to update the text-input contents or not.
4733 // Any key stroke that results in a visual change of the text-input should
4734 // set this flag to true.
4735 bool update = false;
4736 if( mHighlightMeshActor )
4738 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4739 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4741 ImfManager imfManager = ImfManager::Get();
4744 imfManager.SetCursorPosition( mCursorPosition );
4745 imfManager.NotifyCursorPosition();
4747 DeleteHighlightedText( true );
4751 bool textExceedsMaximunNumberOfCharacters = false;
4752 bool textExceedsBoundary = false;
4754 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4756 mCursorPosition += insertedStringLength;
4757 ImfManager imfManager = ImfManager::Get();
4760 imfManager.SetCursorPosition ( mCursorPosition );
4761 imfManager.NotifyCursorPosition();
4764 update = update || ( insertedStringLength > 0 );
4771 if( insertedStringLength < text.GetLength() )
4773 EmitMaxInputCharactersReachedSignal();
4776 if( textExceedsBoundary )
4778 EmitInputTextExceedsBoundariesSignal();
4782 void TextInput::SetTextDirection()
4784 // Put the cursor to the right if we are empty and an RTL language is being used.
4785 if ( mStyledText.empty() )
4787 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4789 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4790 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4792 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4793 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4795 int alignment( mDisplayedTextView.GetTextAlignment() &
4796 ( Toolkit::Alignment::VerticalTop |
4797 Toolkit::Alignment::VerticalCenter |
4798 Toolkit::Alignment::VerticalBottom |
4799 Toolkit::Alignment::HorizontalCenter ) );
4800 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4802 // If our alignment is in the center, then do not change.
4803 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4805 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4808 // If our justification is in the center, then do not change.
4809 if ( justification != Toolkit::TextView::Center )
4811 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4814 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4815 mDisplayedTextView.SetLineJustification( justification );
4819 void TextInput::UpdateLineHeight()
4821 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4822 mLineHeight = font.GetLineHeight();
4824 // If the height exceed policy is shrink or exceed the boundaries of the text-input is not allowed, then modify the line height is needed.
4826 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4828 if( !mExceedEnabled || shrink )
4830 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4834 std::size_t TextInput::FindVisibleCharacter( FindVisibleCharacterDirection direction , std::size_t cursorPosition ) const
4836 // VCC check if we need do this in the visual order ...
4837 std::size_t position = 0u;
4839 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4845 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4847 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1u : position ) ) ).mIsVisible )
4849 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4855 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4856 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1u : position ) ) ).mIsVisible )
4858 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4864 position = FindVisibleCharacterLeft( 0u, mTextLayoutInfo.mCharacterLayoutInfoTable );
4869 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4876 void TextInput::SetSortModifier( float depthOffset )
4878 if(mDisplayedTextView)
4880 mDisplayedTextView.SetSortModifier(depthOffset);
4884 void TextInput::SetSnapshotModeEnabled( bool enable )
4886 if(mDisplayedTextView)
4888 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4892 bool TextInput::IsSnapshotModeEnabled() const
4894 bool snapshotEnabled = false;
4896 if(mDisplayedTextView)
4898 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4901 return snapshotEnabled;
4904 void TextInput::SetMarkupProcessingEnabled( bool enable )
4906 mMarkUpEnabled = enable;
4909 bool TextInput::IsMarkupProcessingEnabled() const
4911 return mMarkUpEnabled;
4914 void TextInput::SetScrollEnabled( bool enable )
4916 if( mDisplayedTextView )
4918 mDisplayedTextView.SetScrollEnabled( enable );
4923 // Don't set cursor's and handle's visibility to false if they are outside the
4924 // boundaries of the text-input.
4925 mIsCursorInScrollArea = true;
4926 mIsGrabHandleInScrollArea = true;
4927 if( mSelectionHandleOne && mSelectionHandleTwo )
4929 mSelectionHandleOne.SetVisible( true );
4930 mSelectionHandleTwo.SetVisible( true );
4932 if( mHighlightMeshActor )
4934 mHighlightMeshActor.SetVisible( true );
4940 bool TextInput::IsScrollEnabled() const
4942 bool scrollEnabled = false;
4944 if( mDisplayedTextView )
4946 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4949 return scrollEnabled;
4952 void TextInput::SetScrollPosition( const Vector2& position )
4954 if( mDisplayedTextView )
4956 mDisplayedTextView.SetScrollPosition( position );
4960 Vector2 TextInput::GetScrollPosition() const
4962 Vector2 scrollPosition;
4964 if( mDisplayedTextView )
4966 scrollPosition = mDisplayedTextView.GetScrollPosition();
4969 return scrollPosition;
4972 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4974 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4975 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4976 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4978 // Add style to the new input text.
4979 MarkupProcessor::StyledTextArray textToInsert;
4980 for( std::size_t i = 0; i < insertedStringLength; ++i )
4982 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4983 textToInsert.push_back( newStyledCharacter );
4986 //Insert text to the TextView.
4987 const bool emptyTextView = mStyledText.empty();
4988 if( emptyTextView && mPlaceHolderSet )
4990 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4991 mDisplayedTextView.SetText( textToInsert );
4995 if( 0 == numberOfCharactersToReplace )
4997 mDisplayedTextView.InsertTextAt( position, textToInsert );
5001 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
5004 mPlaceHolderSet = false;
5006 if( textToInsert.empty() )
5008 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
5009 GetTextLayoutInfo();
5013 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
5014 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5017 textExceedsBoundary = false;
5019 if( !mExceedEnabled )
5021 const Vector3& size = GetControlSize();
5023 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5025 // If new text does not fit within TextView
5026 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
5027 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
5028 GetTextLayoutInfo();
5029 textExceedsBoundary = true;
5030 insertedStringLength = 0;
5033 if( textExceedsBoundary )
5035 // Add the part of the text which fits on the text-input.
5037 // Split the text which doesn't fit in two halves.
5038 MarkupProcessor::StyledTextArray firstHalf;
5039 MarkupProcessor::StyledTextArray secondHalf;
5040 SplitText( textToInsert, firstHalf, secondHalf );
5042 // Clear text. This text will be filled with the text inserted.
5043 textToInsert.clear();
5045 // Where to insert the text.
5046 std::size_t positionToInsert = position;
5048 bool end = text.GetLength() <= 1;
5051 // Insert text and check ...
5052 const std::size_t textLength = firstHalf.size();
5053 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5054 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5056 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5058 // Inserted text doesn't fit.
5060 // Remove inserted text
5061 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5062 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5064 // The iteration finishes when only one character doesn't fit.
5065 end = textLength <= 1;
5069 // Prepare next two halves for next iteration.
5070 MarkupProcessor::StyledTextArray copyText = firstHalf;
5071 SplitText( copyText, firstHalf, secondHalf );
5078 // store text to be inserted in mStyledText.
5079 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5081 // Increase the inserted characters counter.
5082 insertedStringLength += textLength;
5084 // Prepare next two halves for next iteration.
5085 MarkupProcessor::StyledTextArray copyText = secondHalf;
5086 SplitText( copyText, firstHalf, secondHalf );
5088 // Update where next text has to be inserted
5089 positionToInsert += textLength;
5095 if( textToInsert.empty() && emptyTextView )
5097 // No character has been added and the text-view was empty.
5098 // Set the placeholder text.
5099 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5100 mPlaceHolderSet = true;
5104 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5105 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5106 mPlaceHolderSet = false;
5109 return insertedStringLength;
5112 void TextInput::GetTextLayoutInfo()
5114 if( mStyledText.empty() )
5116 // The text-input has no text, clear the text-view's layout info.
5117 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5121 if( mDisplayedTextView )
5123 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5127 // There is no text-view.
5128 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5133 void TextInput::SetOffsetFromText( const Vector4& offset )
5135 mPopupOffsetFromText = offset;
5138 const Vector4& TextInput::GetOffsetFromText() const
5140 return mPopupOffsetFromText;
5143 void TextInput::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value )
5145 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5149 TextInput& textInputImpl( GetImpl( textInput ) );
5151 switch ( propertyIndex )
5153 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5155 textInputImpl.SetMaterialDiffuseColor( value.Get< Vector4 >() );
5158 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5160 textInputImpl.mPopupPanel.SetCutPastePopupColor( value.Get< Vector4 >() );
5163 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5165 textInputImpl.mPopupPanel.SetCutPastePopupPressedColor( value.Get< Vector4 >() );
5168 case Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY:
5170 textInputImpl.mPopupPanel.SetCutPastePopupBorderColor( value.Get< Vector4 >() );
5173 case Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY:
5175 textInputImpl.mPopupPanel.SetCutPastePopupIconColor( value.Get< Vector4 >() );
5178 case Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY:
5180 textInputImpl.mPopupPanel.SetCutPastePopupIconPressedColor( value.Get< Vector4 >() );
5183 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY:
5185 textInputImpl.mPopupPanel.SetCutPastePopupTextColor( value.Get< Vector4 >() );
5188 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY:
5190 textInputImpl.mPopupPanel.SetCutPastePopupTextPressedColor( value.Get< Vector4 >() );
5193 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5195 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCut, value.Get<unsigned int>() );
5198 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5200 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCopy, value.Get<unsigned int>() );
5203 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5205 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsPaste, value.Get<unsigned int>() );
5208 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5210 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelect, value.Get<unsigned int>() );
5213 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5215 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll, value.Get<unsigned int>() );
5218 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5220 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsClipboard, value.Get<unsigned int>() );
5223 case Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY:
5225 textInputImpl.SetOffsetFromText( value.Get< Vector4 >() );
5228 case Toolkit::TextInput::CURSOR_COLOR_PROPERTY:
5230 textInputImpl.mCursor.SetColor( value.Get< Vector4 >() );
5236 Property::Value TextInput::GetProperty( BaseObject* object, Property::Index propertyIndex )
5238 Property::Value value;
5240 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5244 TextInput& textInputImpl( GetImpl( textInput ) );
5246 switch ( propertyIndex )
5248 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5250 value = textInputImpl.GetMaterialDiffuseColor();
5253 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5255 value = textInputImpl.mPopupPanel.GetCutPastePopupColor();
5258 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5260 value = textInputImpl.mPopupPanel.GetCutPastePopupPressedColor();
5263 case Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY :
5265 value = textInputImpl.mPopupPanel.GetCutPastePopupBorderColor();
5268 case Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY:
5270 value = textInputImpl.mPopupPanel.GetCutPastePopupIconColor();
5273 case Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY:
5275 value = textInputImpl.mPopupPanel.GetCutPastePopupIconPressedColor();
5278 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY:
5280 value = textInputImpl.mPopupPanel.GetCutPastePopupTextColor();
5283 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY:
5285 value = textInputImpl.mPopupPanel.GetCutPastePopupTextPressedColor();
5288 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5290 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCut );
5293 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5295 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCopy );
5298 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5300 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsPaste );
5303 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5305 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelect );
5308 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5310 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll );
5313 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5315 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsClipboard );
5318 case Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY:
5320 value = textInputImpl.GetOffsetFromText();
5323 case Toolkit::TextInput::CURSOR_COLOR_PROPERTY:
5325 value = textInputImpl.mCursor.GetCurrentColor();
5332 void TextInput::EmitStyleChangedSignal()
5334 // emit signal if input style changes.
5335 Toolkit::TextInput handle( GetOwner() );
5336 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5339 void TextInput::EmitTextModified()
5341 // emit signal when text changes.
5342 Toolkit::TextInput handle( GetOwner() );
5343 mTextModifiedSignal.Emit( handle );
5347 void TextInput::EmitMaxInputCharactersReachedSignal()
5349 // emit signal if max characters is reached during text input.
5350 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5352 Toolkit::TextInput handle( GetOwner() );
5353 mMaxInputCharactersReachedSignalV2.Emit( handle );
5356 void TextInput::EmitInputTextExceedsBoundariesSignal()
5358 // Emit a signal when the input text exceeds the boundaries of the text input.
5360 Toolkit::TextInput handle( GetOwner() );
5361 mInputTextExceedBoundariesSignalV2.Emit( handle );
5364 } // namespace Internal
5366 } // namespace Toolkit