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
90 * Whether the given style is the default style or not.
91 * @param[in] style The given style.
92 * @return \e true if the given style is the default. Otherwise it returns \e false.
94 bool IsDefaultStyle( const TextStyle& style )
96 return DEFAULT_TEXT_STYLE == style;
100 * Whether the given styled text is using the default style or not.
101 * @param[in] textArray The given text.
102 * @return \e true if the given styled text is using the default style. Otherwise it returns \e false.
104 bool IsTextDefaultStyle( const Toolkit::MarkupProcessor::StyledTextArray& textArray )
106 for( Toolkit::MarkupProcessor::StyledTextArray::const_iterator it = textArray.begin(), endIt = textArray.end(); it != endIt; ++it )
108 const TextStyle& style( (*it).mStyle );
110 if( !IsDefaultStyle( style ) )
119 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
121 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
125 if( ( *it ).mIsVisible )
127 return --cursorPosition;
136 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
138 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
140 if( ( *it ).mIsVisible )
142 return cursorPosition;
148 return cursorPosition;
152 * Whether the given position plus the cursor size offset is inside the given boundary.
154 * @param[in] position The given position.
155 * @param[in] cursorSize The cursor size.
156 * @param[in] controlSize The given boundary.
158 * @return whether the given position is inside the given boundary.
160 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
162 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
163 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
164 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
165 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
169 * Splits a text in two halves.
171 * If the text's number of characters is odd, firstHalf has one more character.
173 * @param[in] text The text to be split.
174 * @param[out] firstHalf The first half of the text.
175 * @param[out] secondHalf The second half of the text.
177 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
178 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
179 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
184 const std::size_t textLength = text.size();
185 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
187 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
188 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
191 } // end of namespace
199 const Property::Index TextInput::HIGHLIGHT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX;
200 const Property::Index TextInput::CUT_AND_PASTE_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+1;
201 const Property::Index TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+2;
202 const Property::Index TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+3;
203 const Property::Index TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+4;
204 const Property::Index TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+5;
205 const Property::Index TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+6;
206 const Property::Index TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+7;
207 const Property::Index TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+8;
208 const Property::Index TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+9;
209 const Property::Index TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+10;
210 const Property::Index TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+11;
211 const Property::Index TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+12;
212 const Property::Index TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+13;
213 const Property::Index TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+14;
214 const Property::Index TextInput::CURSOR_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+15;
225 return Toolkit::TextInput::New();
228 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
230 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
231 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
232 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
233 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
234 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
235 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
239 PropertyRegistration property1( typeRegistration, "highlight-color", Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
240 PropertyRegistration property2( typeRegistration, "cut-and-paste-bg-color", Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
241 PropertyRegistration property3( typeRegistration, "cut-and-paste-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
242 PropertyRegistration property4( typeRegistration, "cut-and-paste-icon-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
243 PropertyRegistration property5( typeRegistration, "cut-and-paste-icon-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
244 PropertyRegistration property6( typeRegistration, "cut-and-paste-text-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
245 PropertyRegistration property7( typeRegistration, "cut-and-paste-text-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
246 PropertyRegistration property8( typeRegistration, "cut-and-paste-border-color", Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
247 PropertyRegistration property9( typeRegistration, "cut-button-position-priority", Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
248 PropertyRegistration property10( typeRegistration, "copy-button-position-priority", Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
249 PropertyRegistration property11( typeRegistration, "paste-button-position-priority", Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
250 PropertyRegistration property12( typeRegistration, "select-button-position-priority", Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
251 PropertyRegistration property13( typeRegistration, "select-all-button-position-priority", Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
252 PropertyRegistration property14( typeRegistration, "clipboard-button-position-priority", Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
253 PropertyRegistration property15( typeRegistration, "popup-offset-from-text", Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
254 PropertyRegistration property16( typeRegistration, "cursor-color", Toolkit::TextInput::CURSOR_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
257 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
259 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
261 QuadCoordinates quad(x1, y1, x2, y2);
262 mQuadList.push_back( quad );
265 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
267 for(std::size_t i = 0;i < mQuadList.size(); i++)
269 QuadCoordinates& quad = mQuadList[i];
271 quad.min.Clamp(min, max);
272 quad.max.Clamp(min, max);
276 // [TextInput] ////////////////////////////////////////////////////////////////
278 Dali::Toolkit::TextInput TextInput::New()
280 // Create the implementation
281 TextInputPtr textInput(new TextInput());
282 // Pass ownership to CustomActor via derived handle
283 Dali::Toolkit::TextInput handle(*textInput);
284 handle.SetName( "TextInput");
286 textInput->Initialize();
290 TextInput::TextInput()
291 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
296 mDisplayedTextView(),
297 mStyledPlaceHolderText(),
298 mMaxStringLength( DEFAULT_MAX_SIZE ),
299 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
300 mCursorPosition( 0 ),
301 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
302 mIsSelectionHandleOneFlipped( false ),
303 mIsSelectionHandleTwoFlipped( false ),
304 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
305 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
306 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
307 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
308 mSelectionHandleOnePosition( 0 ),
309 mSelectionHandleTwoPosition( 0 ),
311 mPreEditStartPosition( 0 ),
312 mPreEditLength ( 0 ),
313 mNumberOfSurroundingCharactersDeleted( 0 ),
314 mTouchStartTime( 0 ),
316 mCurrentCopySelecton(),
319 mScrollDisplacement(),
320 mCurrentHandlePosition(),
321 mCurrentSelectionId(),
322 mCurrentSelectionHandlePosition(),
323 mRequestedSelection( 0, 0 ),
324 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
325 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
327 mMaterialColor( LIGHTBLUE ),
328 mPopupOffsetFromText ( Vector4( 0.0f, TOP_HANDLE_TOP_OFFSET, 0.0f, BOTTOM_HANDLE_BOTTOM_OFFSET ) ),
329 mOverrideAutomaticAlignment( false ),
330 mCursorRTLEnabled( false ),
331 mClosestCursorPositionEOL ( false ),
332 mCursorBlinkStatus( true ),
333 mCursorVisibility( false ),
334 mGrabHandleVisibility( false ),
335 mIsCursorInScrollArea( true ),
336 mIsGrabHandleInScrollArea( true ),
337 mEditModeActive( false ),
338 mEditOnTouch( true ),
339 mTextSelection( true ),
340 mExceedEnabled( true ),
341 mGrabHandleEnabled( true ),
342 mIsSelectionHandleFlipEnabled( true ),
343 mPreEditFlag( false ),
344 mIgnoreCommitFlag( false ),
345 mIgnoreFirstCommitFlag( false ),
346 mSelectingText( false ),
347 mPreserveCursorPosition( false ),
348 mSelectTextOnCommit( false ),
349 mUnderlinedPriorToPreEdit ( false ),
350 mCommitByKeyInput( false ),
351 mPlaceHolderSet( false ),
352 mMarkUpEnabled( false )
354 // Updates the line height accordingly with the input style.
358 TextInput::~TextInput()
360 StopCursorBlinkTimer();
365 std::string TextInput::GetText() const
369 // Return text-view's text only if the text-input's text is not empty
370 // in order to not to return the placeholder text.
371 if( !mStyledText.empty() )
373 text = mDisplayedTextView.GetText();
379 std::string TextInput::GetMarkupText() const
381 std::string markupString;
382 MarkupProcessor::GetMarkupString( mStyledText, markupString );
387 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
389 // Get the placeholder styled text array from the markup string.
390 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
392 if( mStyledText.empty() )
394 // Set the placeholder text only if the styled text is empty.
395 mDisplayedTextView.SetText( mStyledPlaceHolderText );
396 mPlaceHolderSet = true;
400 std::string TextInput::GetPlaceholderText()
402 // Traverses the styled placeholder array getting only the text.
403 // Note that for some languages a 'character' could be represented by more than one 'char'
405 std::string placeholderText;
406 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
408 placeholderText.append( (*it).mText.GetText() );
411 return placeholderText ;
414 void TextInput::SetInitialText(const std::string& initialText)
416 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
418 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
420 mPreEditFlag = false;
421 mIgnoreCommitFlag = true;
424 SetText( initialText );
425 PreEditReset( false ); // Reset keyboard as text changed
428 void TextInput::SetText(const std::string& initialText)
430 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
432 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
434 if( mStyledText.empty() )
436 // If the initial text is empty, set the placeholder text.
437 mDisplayedTextView.SetText( mStyledPlaceHolderText );
438 mPlaceHolderSet = true;
442 mDisplayedTextView.SetText( mStyledText );
443 mPlaceHolderSet = false;
448 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
450 ImfManager imfManager = ImfManager::Get();
453 imfManager.SetCursorPosition( mCursorPosition );
454 imfManager.SetSurroundingText( initialText );
455 imfManager.NotifyCursorPosition();
458 if( IsScrollEnabled() )
460 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
463 ShowGrabHandleAndSetVisibility( false );
472 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
474 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
476 mDisplayedTextView.SetText( styleText );
477 mPlaceHolderSet = false;
479 // If text alignment hasn't been manually set by application developer, then we
480 // automatically determine the alignment based on the content of the text i.e. what
481 // language the text begins with.
482 // TODO: This should determine different alignments for each line (broken by '\n') of text.
483 if(!mOverrideAutomaticAlignment)
485 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
486 bool leftToRight(true);
488 if( !styleText.empty() )
490 bool breakOut(false);
492 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
494 const Text& text = textIter->mText;
496 for( std::size_t i = 0; i < text.GetLength(); ++i )
498 Character character( text[i] );
499 if( character.GetCharacterDirection() != Character::Neutral )
501 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
509 // Based on this direction, either left or right align text if not manually set by application developer.
510 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
511 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
512 Toolkit::Alignment::VerticalTop ) );
513 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
519 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
521 mMaxStringLength = maxChars;
524 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
526 DALI_ASSERT_DEBUG( maxLines > 0 )
530 mNumberOflinesLimit = maxLines;
534 std::size_t TextInput::GetNumberOfLinesLimit() const
536 return mNumberOflinesLimit;
539 std::size_t TextInput::GetNumberOfCharacters() const
541 return mStyledText.size();
545 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
547 mMaterialColor = color;
548 if ( mCustomMaterial )
550 mCustomMaterial.SetDiffuseColor( mMaterialColor );
551 mMeshData.SetMaterial( mCustomMaterial );
555 const Vector4& TextInput::GetMaterialDiffuseColor() const
557 return mMaterialColor;
562 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
564 return mInputStartedSignalV2;
567 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
569 return mInputFinishedSignalV2;
572 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
574 return mCutAndPasteToolBarDisplayedV2;
577 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
579 return mStyleChangedSignalV2;
582 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
584 return mTextModifiedSignal;
587 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
589 return mMaxInputCharactersReachedSignalV2;
592 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
594 return mInputTextExceedBoundariesSignalV2;
597 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
599 Dali::BaseHandle handle( object );
601 bool connected( true );
602 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
604 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
606 textInput.InputStartedSignal().Connect( tracker, functor );
608 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
610 textInput.InputFinishedSignal().Connect( tracker, functor );
612 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
614 textInput.StyleChangedSignal().Connect( tracker, functor );
616 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
618 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
620 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
622 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
626 // signalName does not match any signal
633 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
637 // update line height before calculate the actual position.
642 if( setCursorOnTouchPoint )
644 // Sets the cursor position for the given touch point.
645 ReturnClosestIndex( touchPoint, mCursorPosition );
647 // Creates the grab handle.
648 if( IsGrabHandleEnabled() )
650 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
654 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
655 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
656 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
657 ShowGrabHandleAndSetVisibility( true );
659 // Scrolls the text-view if needed.
660 if( IsScrollEnabled() )
662 ScrollTextViewToMakeCursorVisible( cursorPosition );
668 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
680 bool TextInput::IsEditable() const
682 return mEditModeActive;
685 void TextInput::SetEditOnTouch( bool editOnTouch )
687 mEditOnTouch = editOnTouch;
690 bool TextInput::IsEditOnTouch() const
695 void TextInput::SetTextSelectable( bool textSelectable )
697 mTextSelection = textSelectable;
700 bool TextInput::IsTextSelectable() const
702 return mTextSelection;
705 bool TextInput::IsTextSelected() const
707 return mHighlightMeshActor;
710 void TextInput::DeSelectText()
717 void TextInput::SetGrabHandleImage(Dali::Image image )
721 CreateGrabHandle(image);
725 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
727 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
731 mCursor.SetImage( image );
732 mCursor.SetNinePatchBorder( border );
736 Vector3 TextInput::GetSelectionHandleSize()
738 return DEFAULT_SELECTION_HANDLE_SIZE;
741 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
743 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
747 mCursorRTL.SetImage( image);
748 mCursorRTL.SetNinePatchBorder( border );
752 void TextInput::EnableGrabHandle(bool toggle)
754 // enables grab handle with will in turn de-activate magnifier
755 mGrabHandleEnabled = toggle;
758 bool TextInput::IsGrabHandleEnabled()
760 // if false then magnifier will be shown instead.
761 return mGrabHandleEnabled;
764 void TextInput::EnableSelectionHandleFlip( bool toggle )
766 // Deprecated function. To be removed.
767 mIsSelectionHandleFlipEnabled = toggle;
770 bool TextInput::IsSelectionHandleFlipEnabled()
772 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
776 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
778 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
779 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
780 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
782 mSelectionHandleFlipMargin = margin;
785 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
787 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
788 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
790 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
791 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
793 const Vector4 boundary( originX,
795 originX + boundingRectangle.width,
796 originY + boundingRectangle.height );
798 mBoundingRectangleWorldCoordinates = boundary;
801 const Rect<float> TextInput::GetBoundingRectangle() const
803 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
805 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
806 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
808 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
813 const Vector4& TextInput::GetSelectionHandleFlipMargin()
815 return mSelectionHandleFlipMargin;
818 void TextInput::SetTextColor( const Vector4& color )
820 mDisplayedTextView.SetColor( color );
823 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
825 if( style != mInputStyle )
828 bool emitSignal = false;
830 // mask: modify style according to mask, if different emit signal.
831 const TextStyle oldInputStyle( mInputStyle );
833 // Copy the new style.
834 mInputStyle.Copy( style, mask );
836 // if style has changed, emit signal.
837 if( oldInputStyle != mInputStyle )
842 // Updates the line height accordingly with the input style.
845 // Changing font point size will require the cursor to be re-sized
850 EmitStyleChangedSignal();
855 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
857 if ( IsTextSelected() )
859 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
860 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
862 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
864 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
867 // Keeps the old style to be compared with the new one.
868 const TextStyle oldInputStyle( mInputStyle );
870 // Copy only those parameters from the style which are set in the mask.
871 mInputStyle.Copy( style, mask );
873 if( mInputStyle != oldInputStyle )
875 // Updates the line height accordingly with the input style.
878 EmitStyleChangedSignal();
883 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
885 if( !mStyledText.empty() )
887 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
891 TextStyle TextInput::GetStyleAtCursor() const
895 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
897 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
898 style = mStyledText.at( mCursorPosition-1 ).mStyle;
904 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
906 Dali::Font defaultFont = Dali::Font::New();
907 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
914 TextStyle TextInput::GetStyleAt( std::size_t position ) const
916 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
918 if( position >= mStyledText.size() )
920 position = mStyledText.size() - 1;
923 return mStyledText.at( position ).mStyle;
926 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
928 mDisplayedTextView.SetTextAlignment( align );
929 mOverrideAutomaticAlignment = true;
932 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
934 mDisplayedTextView.SetLineJustification( justification );
935 mOverrideAutomaticAlignment = true;
938 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
940 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
943 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
945 return mDisplayedTextView.GetFadeBoundary();
948 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
950 return mDisplayedTextView.GetTextAlignment();
953 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
955 mDisplayedTextView.SetMultilinePolicy( policy );
958 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
960 return mDisplayedTextView.GetMultilinePolicy();
963 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
965 mDisplayedTextView.SetWidthExceedPolicy( policy );
968 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
970 return mDisplayedTextView.GetWidthExceedPolicy();
973 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
975 mDisplayedTextView.SetHeightExceedPolicy( policy );
978 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
980 return mDisplayedTextView.GetHeightExceedPolicy();
983 void TextInput::SetExceedEnabled( bool enable )
985 mExceedEnabled = enable;
988 bool TextInput::GetExceedEnabled() const
990 return mExceedEnabled;
993 void TextInput::SetBackground(Dali::Image image )
995 // TODO Should add this function and add public api to match.
998 bool TextInput::OnTouchEvent(const TouchEvent& event)
1003 bool TextInput::OnKeyEvent(const KeyEvent& event)
1005 switch( event.state )
1007 case KeyEvent::Down:
1009 return OnKeyDownEvent(event);
1015 return OnKeyUpEvent(event);
1027 void TextInput::OnKeyInputFocusGained()
1029 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1031 mEditModeActive = true;
1033 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1035 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1037 // Updates the line height accordingly with the input style.
1040 // Connect the signals to use in text input.
1041 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1042 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1044 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1047 GetTextLayoutInfo();
1050 SetCursorVisibility( true );
1051 StartCursorBlinkTimer();
1053 Toolkit::TextInput handle( GetOwner() );
1054 mInputStartedSignalV2.Emit( handle );
1056 ImfManager imfManager = ImfManager::Get();
1060 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1062 // Notify that the text editing start.
1063 imfManager.Activate();
1065 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1066 imfManager.SetRestoreAferFocusLost( true );
1068 imfManager.SetCursorPosition( mCursorPosition );
1069 imfManager.NotifyCursorPosition();
1072 mClipboard = Clipboard::Get(); // Store handle to clipboard
1074 // Now in edit mode we can accept string to paste from clipboard
1075 if( Adaptor::IsAvailable() )
1077 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1080 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1085 void TextInput::OnKeyInputFocusLost()
1087 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1091 // If key input focus is lost, it removes the
1092 // underline from the last pre-edit text.
1093 RemovePreEditStyle();
1094 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1095 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1099 ImfManager imfManager = ImfManager::Get();
1102 // The text editing is finished. Therefore the imf manager don't have restore activation.
1103 imfManager.SetRestoreAferFocusLost( false );
1105 // Notify that the text editing finish.
1106 imfManager.Deactivate();
1108 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1110 // Disconnect signal used the text input.
1111 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1113 Toolkit::TextInput handle( GetOwner() );
1114 mInputFinishedSignalV2.Emit( handle );
1115 mEditModeActive = false;
1116 mPreEditFlag = false;
1118 SetCursorVisibility( false );
1119 StopCursorBlinkTimer();
1121 ShowGrabHandleAndSetVisibility( false );
1124 // No longer in edit mode so do not want to receive string from clipboard
1125 if( Adaptor::IsAvailable() )
1127 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1130 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1132 Clipboard clipboard = Clipboard::Get();
1136 clipboard.HideClipboard();
1141 void TextInput::OnControlStageConnection()
1143 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1145 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1147 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1151 void TextInput::CreateActiveLayer()
1153 Actor self = Self();
1154 mActiveLayer = Layer::New();
1155 mActiveLayer.SetName ( "ActiveLayerActor" );
1157 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1158 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1159 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1161 self.Add( mActiveLayer );
1162 mActiveLayer.RaiseToTop();
1165 void TextInput::OnInitialize()
1167 CreateTextViewActor();
1171 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1172 // different positions depending on language)
1173 mCursor = CreateCursor(DEFAULT_CURSOR_COLOR);
1174 mCursorRTL = CreateCursor(DEFAULT_CURSOR_COLOR);
1176 Actor self = Self();
1177 self.Add( mCursor );
1178 self.Add( mCursorRTL );
1180 mCursorVisibility = false;
1182 CreateActiveLayer(); // todo move this so layer only created when needed.
1184 // Assign names to image actors
1185 mCursor.SetName("mainCursor");
1186 mCursorRTL.SetName("rtlCursor");
1189 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1191 mDisplayedTextView.SetSize( targetSize );
1192 GetTextLayoutInfo();
1193 mActiveLayer.SetSize(targetSize);
1196 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1198 Relayout( mDisplayedTextView, size, container );
1199 Relayout( mPopupPanel.GetRootActor(), size, container );
1201 GetTextLayoutInfo();
1206 Vector3 TextInput::GetNaturalSize()
1208 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1210 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1212 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1213 naturalSize.height = mLineHeight;
1219 float TextInput::GetHeightForWidth( float width )
1221 float height = mDisplayedTextView.GetHeightForWidth( width );
1223 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1225 // If the height is zero, it means there is no text. Let's return the cursor height.
1226 height = mLineHeight;
1232 /*end of Virtual methods from parent*/
1234 // Private Internal methods
1236 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1238 switch (gesture.state)
1240 case Gesture::Started:
1241 // fall through so code not duplicated
1242 case Gesture::Continuing:
1244 if (actor == mGrabArea)
1246 SetCursorVisibility( true );
1247 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1248 MoveGrabHandle( gesture.displacement );
1249 HidePopup(); // Do not show popup whilst handle is moving
1251 else if (actor == mHandleOneGrabArea)
1253 // the displacement in PanGesture is affected by the actor's rotation.
1254 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1255 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1257 MoveSelectionHandle( HandleOne, gesture.displacement );
1259 mState = StateDraggingHandle;
1262 else 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 MoveSelectionHandle( HandleTwo, gesture.displacement );
1270 mState = StateDraggingHandle;
1276 case Gesture::Finished:
1278 // Revert back to non-pressed selection handle images
1279 if (actor == mGrabArea)
1281 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1282 SetCursorVisibility( true );
1283 SetUpPopupSelection();
1286 if (actor == mHandleOneGrabArea)
1288 // the displacement in PanGesture is affected by the actor's rotation.
1289 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1290 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1292 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1294 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1296 ShowPopupCutCopyPaste();
1298 if (actor == mHandleTwoGrabArea)
1300 // the displacement in PanGesture is affected by the actor's rotation.
1301 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1302 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1304 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1306 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1308 ShowPopupCutCopyPaste();
1317 // Stop the flashing animation so easy to see when moved.
1318 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1320 if (touch.GetPoint(0).state == TouchPoint::Down)
1322 SetCursorVisibility( true );
1323 StopCursorBlinkTimer();
1325 else if (touch.GetPoint(0).state == TouchPoint::Up)
1327 SetCursorVisibility( true );
1328 StartCursorBlinkTimer();
1333 // selection handle one
1334 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1336 if (touch.GetPoint(0).state == TouchPoint::Down)
1338 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1340 else if (touch.GetPoint(0).state == TouchPoint::Up)
1342 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1347 // selection handle two
1348 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1350 if (touch.GetPoint(0).state == TouchPoint::Down)
1352 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1354 else if (touch.GetPoint(0).state == TouchPoint::Up)
1356 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1361 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1363 // If text exists then select nearest word.
1364 if ( !mStyledText.empty())
1368 ShowGrabHandleAndSetVisibility( false );
1373 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1374 // converts the pre-edit word being displayed to a committed word.
1375 if ( !mUnderlinedPriorToPreEdit )
1378 style.SetUnderline( false );
1379 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1381 mPreEditFlag = false;
1382 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1383 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1384 PreEditReset( false );
1386 mCursorPosition = 0;
1388 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1389 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1391 ImfManager imfManager = ImfManager::Get();
1394 imfManager.SetCursorPosition ( mCursorPosition );
1395 imfManager.NotifyCursorPosition();
1398 std::size_t start = 0;
1399 std::size_t end = 0;
1400 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1402 SelectText( start, end );
1404 // if no text but clipboard has content then show paste option
1405 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1407 ShowPopupCutCopyPaste();
1410 // If no text and clipboard empty then do nothing
1413 // TODO: Change the function name to be more general.
1414 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1416 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1417 , (mEditOnTouch)?"true":"false"
1418 , (mEditModeActive)?"true":"false");
1420 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1425 if( mGrabArea == actor )
1427 if( mPopupPanel.GetState() == TextInputPopup::StateHidden || mPopupPanel.GetState() == TextInputPopup::StateHiding )
1429 SetUpPopupSelection();
1439 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1441 // Initially don't create the grab handle.
1442 bool createGrabHandle = false;
1444 if ( !mEditModeActive )
1446 // update line height before calculate the actual position.
1449 // Only start edit mode if TextInput configured to edit on touch
1452 // Set the initial cursor position in the tap point.
1453 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1455 // Create the grab handle.
1456 // TODO Make this a re-usable function.
1457 if ( IsGrabHandleEnabled() )
1459 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1463 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1464 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1465 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1466 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1470 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1471 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1472 // otherwise the Grab handle will be shown when selecting.
1479 // Show the keyboard if it was hidden.
1480 if (!VirtualKeyboard::IsVisible())
1482 VirtualKeyboard::Show();
1485 // Reset keyboard as tap event has occurred.
1486 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1487 PreEditReset( true );
1489 GetTextLayoutInfo();
1491 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1493 // 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.
1495 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1497 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1499 // Notify keyboard so it can 're-capture' word for predictive text.
1500 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1501 ImfManager imfManager = ImfManager::Get();
1504 imfManager.SetCursorPosition ( mCursorPosition );
1505 imfManager.NotifyCursorPosition();
1507 const TextStyle oldInputStyle( mInputStyle );
1509 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1513 // Create the grab handle.
1514 // Grab handle is created later.
1515 createGrabHandle = true;
1517 if( oldInputStyle != mInputStyle )
1519 // Updates the line height accordingly with the input style.
1522 EmitStyleChangedSignal();
1527 if ( createGrabHandle && IsGrabHandleEnabled() )
1529 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1533 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1534 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1535 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1536 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1541 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1543 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1545 // Ignore longpress if in selection mode already
1546 if( mHighlightMeshActor )
1551 if(longPress.state == Dali::Gesture::Started)
1553 // Start edit mode on long press
1554 if ( !mEditModeActive )
1559 // If text exists then select nearest word.
1560 if ( !mStyledText.empty())
1564 ShowGrabHandleAndSetVisibility( false );
1569 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1570 // converts the pre-edit word being displayed to a committed word.
1571 if ( !mUnderlinedPriorToPreEdit )
1574 style.SetUnderline( false );
1575 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1577 mPreEditFlag = false;
1578 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1579 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1580 PreEditReset( false );
1582 mCursorPosition = 0;
1584 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1585 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1587 ImfManager imfManager = ImfManager::Get();
1590 imfManager.SetCursorPosition ( mCursorPosition );
1591 imfManager.NotifyCursorPosition();
1593 std::size_t start = 0;
1594 std::size_t end = 0;
1595 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1597 SelectText( start, end );
1600 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1601 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1603 ShowPopupCutCopyPaste();
1608 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1610 const Text clipboardText( notifier.GetContent() );
1611 PasteText( clipboardText );
1613 SetCursorVisibility( true );
1614 StartCursorBlinkTimer();
1616 ShowGrabHandleAndSetVisibility( false );
1622 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1624 mPopupPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1626 const std::string& name = button.GetName();
1628 if(name == TextInputPopup::OPTION_SELECT_WORD)
1630 std::size_t start = 0;
1631 std::size_t end = 0;
1632 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1634 SelectText( start, end );
1636 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1638 SetCursorVisibility(false);
1639 StopCursorBlinkTimer();
1641 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1642 std::size_t start = 0;
1644 SelectText( start, end );
1646 else if(name == TextInputPopup::OPTION_CUT)
1648 bool ret = CopySelectedTextToClipboard();
1652 DeleteHighlightedText( true );
1656 SetCursorVisibility( true );
1657 StartCursorBlinkTimer();
1661 else if(name == TextInputPopup::OPTION_COPY)
1663 CopySelectedTextToClipboard();
1667 SetCursorVisibility( true );
1668 StartCursorBlinkTimer();
1672 else if(name == TextInputPopup::OPTION_PASTE)
1674 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1676 PasteText(retrievedString);
1678 SetCursorVisibility( true );
1679 StartCursorBlinkTimer();
1681 ShowGrabHandleAndSetVisibility( false );
1685 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1687 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1688 // Hence pass the false parameter for signalFinished.
1689 HidePopup( true, false );
1690 mClipboard.ShowClipboard();
1696 bool TextInput::OnCursorBlinkTimerTick()
1699 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1700 if ( mCursorRTLEnabled )
1702 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1704 mCursorBlinkStatus = !mCursorBlinkStatus;
1709 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1711 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1713 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1714 if(mHighlightMeshActor && mState == StateEdit)
1716 ShowPopupCutCopyPaste();
1720 //FIXME this routine needs to be re-written as it contains too many branches.
1721 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1723 std::string keyName = event.keyPressedName;
1724 std::string keyString = event.keyPressed;
1726 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1728 // Do not consume "Tab" and "Escape" keys.
1729 if(keyName == "Tab" || keyName == "Escape")
1731 // Escape key to end the edit mode
1737 HidePopup(); // If Pop-up shown then hides it as editing text.
1739 // Update Flag, indicates whether to update the text-input contents or not.
1740 // Any key stroke that results in a visual change of the text-input should
1741 // set this flag to true.
1744 // Whether to scroll text to cursor position.
1745 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1746 bool scroll = false;
1748 if (keyName == "Return")
1750 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1752 bool preEditFlagPreviouslySet( mPreEditFlag );
1754 if (mHighlightMeshActor)
1756 // replaces highlighted text with new line
1757 DeleteHighlightedText( false );
1759 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1761 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1762 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1765 mCommitByKeyInput = true;
1768 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1769 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1771 mPreEditFlag = true;
1772 mIgnoreCommitFlag = false;
1782 else if ( keyName == "space" )
1784 if ( mHighlightMeshActor )
1786 // Some text is selected so erase it before adding space.
1787 DeleteHighlightedText( true );
1791 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1793 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1794 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1797 mCommitByKeyInput = true;
1802 else if (keyName == "BackSpace")
1804 if ( mHighlightMeshActor )
1806 // Some text is selected so erase it
1807 DeleteHighlightedText( true );
1812 if ( mCursorPosition > 0 )
1814 DeleteCharacter( mCursorPosition );
1820 else if (keyName == "Right")
1825 else if (keyName == "Left")
1827 AdvanceCursor(true);
1830 else // event is a character
1832 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1833 if ( !keyString.empty() )
1835 if ( mHighlightMeshActor )
1837 // replaces highlighted text with new character
1838 DeleteHighlightedText( false );
1842 // Received key String
1843 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1849 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1850 // as this is a costly operation.
1856 if(update || scroll)
1858 if( IsScrollEnabled() )
1860 // Calculates the new cursor position (in actor coordinates)
1861 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1863 ScrollTextViewToMakeCursorVisible( cursorPosition );
1870 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1872 std::string keyName = event.keyPressedName;
1873 std::string keyString = event.keyPressed;
1875 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1877 // The selected text become deselected when the key code is DALI_KEY_BACK.
1878 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1887 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1889 // Updates the stored scroll position.
1890 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1892 const Vector3& controlSize = GetControlSize();
1893 Size cursorSize( CURSOR_THICKNESS, 0.f );
1895 // Updates the cursor and grab handle position and visibility.
1896 if( mGrabHandle || mCursor )
1898 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1899 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1901 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1903 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1907 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1908 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1913 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1914 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1918 // Updates the selection handles and highlighted text position and visibility.
1919 if( mSelectionHandleOne && mSelectionHandleTwo )
1921 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1922 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1923 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1924 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1925 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1926 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1928 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1929 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1931 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1932 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1933 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1934 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1936 if( mHighlightMeshActor )
1938 mHighlightMeshActor.SetVisible( true );
1944 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1946 // Scroll the text to make the cursor visible.
1947 const Size cursorSize( CURSOR_THICKNESS,
1948 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1950 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1952 const Vector3& controlSize = GetControlSize();
1954 // Calculates the new scroll position.
1955 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1956 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1958 scrollOffset.x += cursorPosition.x;
1961 if( cursorPosition.y - cursorSize.height < 0.f || cursorPosition.y > controlSize.height )
1963 scrollOffset.y += cursorPosition.y;
1966 // Sets the new scroll position.
1967 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1968 SetScrollPosition( scrollOffset );
1971 void TextInput::StartScrollTimer()
1975 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1976 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1979 if( !mScrollTimer.IsRunning() )
1981 mScrollTimer.Start();
1985 void TextInput::StopScrollTimer()
1989 mScrollTimer.Stop();
1993 bool TextInput::OnScrollTimerTick()
1995 // TODO: need to set the new style accordingly the new handle position.
1997 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1999 // nothing to do if all handles are invisible or doesn't exist.
2005 // Choose between the grab handle or the selection handles.
2006 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
2007 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
2008 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
2010 std::size_t newCursorPosition = 0;
2011 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
2013 // Whether the handle's position is different of the previous one and in the case of the selection handle,
2014 // the new selection handle's position needs to be different of the other one.
2015 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
2016 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
2017 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
2019 if( differentSelectionHandles )
2021 handlePosition = newCursorPosition;
2023 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2025 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
2027 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
2028 scrollPosition += scrollDelta;
2029 SetScrollPosition( scrollPosition );
2031 if( mDisplayedTextView.IsScrollPositionTrimmed() )
2036 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
2039 actualHandlePosition.x += mScrollDisplacement.x;
2040 actualHandlePosition.y += mScrollDisplacement.y;
2045 // Public Internal Methods (public for testing purpose)
2047 void TextInput::SetUpTouchEvents()
2049 if ( !mTapDetector )
2051 mTapDetector = TapGestureDetector::New();
2052 // Attach the actors and connect the signal
2053 mTapDetector.Attach(Self());
2055 // As contains children which may register for tap the default control detector is not used.
2056 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2059 if ( !mDoubleTapDetector )
2061 mDoubleTapDetector = TapGestureDetector::New();
2062 mDoubleTapDetector.SetTapsRequired( 2 );
2063 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2065 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2066 // so that we do not, unnecessarily, have a double tap request all the time
2069 if ( !mPanGestureDetector )
2071 mPanGestureDetector = PanGestureDetector::New();
2072 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2075 if ( !mLongPressDetector )
2077 mLongPressDetector = LongPressGestureDetector::New();
2078 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2079 mLongPressDetector.Attach(Self());
2083 void TextInput::CreateTextViewActor()
2085 mDisplayedTextView = Toolkit::TextView::New();
2086 mDisplayedTextView.SetName( "TextView ");
2087 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2088 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2089 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2090 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2091 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2092 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2093 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2094 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2095 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2096 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2098 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2100 Self().Add( mDisplayedTextView );
2103 // Start a timer to initiate, used by the cursor to blink.
2104 void TextInput::StartCursorBlinkTimer()
2106 if ( !mCursorBlinkTimer )
2108 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2109 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2112 if ( !mCursorBlinkTimer.IsRunning() )
2114 mCursorBlinkTimer.Start();
2118 // Start a timer to initiate, used by the cursor to blink.
2119 void TextInput::StopCursorBlinkTimer()
2121 if ( mCursorBlinkTimer )
2123 mCursorBlinkTimer.Stop();
2127 void TextInput::StartEditMode()
2129 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2131 if(!mEditModeActive)
2136 if ( mDoubleTapDetector )
2138 mDoubleTapDetector.Attach( Self() );
2142 void TextInput::EndEditMode()
2144 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2146 ClearKeyInputFocus();
2148 if ( mDoubleTapDetector )
2150 mDoubleTapDetector.Detach( Self() );
2154 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2156 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2158 mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2160 style.SetUnderline( true );
2161 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2165 void TextInput::RemovePreEditStyle()
2167 if ( !mUnderlinedPriorToPreEdit )
2170 style.SetUnderline( false );
2171 SetActiveStyle( style, TextStyle::UNDERLINE );
2175 // IMF related methods
2178 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2180 bool update( false );
2181 bool preeditResetRequired ( false );
2183 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2185 HidePopup(); // If Pop-up shown then hides it as editing text.
2188 switch ( imfEvent.eventName )
2190 case ImfManager::PREEDIT:
2192 mIgnoreFirstCommitFlag = false;
2194 // 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
2195 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2197 // replaces highlighted text with new character
2198 DeleteHighlightedText( false );
2201 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2203 if( IsScrollEnabled() )
2205 // Calculates the new cursor position (in actor coordinates)
2206 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2207 ScrollTextViewToMakeCursorVisible( cursorPosition );
2214 case ImfManager::COMMIT:
2216 if( mIgnoreFirstCommitFlag )
2218 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2219 mIgnoreFirstCommitFlag = false;
2223 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2225 // 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
2226 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2228 // replaces highlighted text with new character
2229 DeleteHighlightedText( false );
2232 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2233 // not needed, one such scenario is when the pre-edit word is too long to fit.
2234 if ( !mIgnoreCommitFlag )
2236 update = CommitReceived( imfEvent.predictiveString );
2240 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2246 if( IsScrollEnabled() )
2248 // Calculates the new cursor position (in actor coordinates)
2249 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2251 ScrollTextViewToMakeCursorVisible( cursorPosition );
2256 case ImfManager::DELETESURROUNDING:
2258 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2259 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2261 mPreEditFlag = false;
2263 std::size_t toDelete = 0;
2264 std::size_t numberOfCharacters = 0;
2266 if( mHighlightMeshActor )
2268 // delete highlighted text.
2269 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2270 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2274 if( static_cast<std::size_t>(std::abs( imfEvent.cursorOffset )) < mCursorPosition )
2276 toDelete = mCursorPosition + imfEvent.cursorOffset;
2278 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2280 numberOfCharacters = mStyledText.size() - toDelete;
2284 numberOfCharacters = imfEvent.numberOfChars;
2287 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2288 DeleteRange( toDelete, numberOfCharacters );
2290 mCursorPosition = toDelete;
2291 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2295 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2298 case ImfManager::GETSURROUNDING:
2300 // 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
2301 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2302 if (! ( mHighlightMeshActor || mSelectingText ) )
2304 std::string text( GetText() );
2305 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2307 imfManager.SetCursorPosition( mCursorPosition );
2308 imfManager.SetSurroundingText( text );
2311 if( 0 != mNumberOfSurroundingCharactersDeleted )
2313 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2314 mNumberOfSurroundingCharactersDeleted = 0;
2316 if( mStyledText.empty() )
2318 // Styled text is empty, so set the placeholder text.
2319 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2320 mPlaceHolderSet = true;
2325 case ImfManager::VOID:
2327 DALI_ASSERT_DEBUG( false );
2331 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2333 return callbackData;
2336 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2338 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2340 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2341 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2343 bool preeditResetRequest ( false );
2345 if( mPreEditFlag ) // Already in pre-edit state.
2347 if( mStyledText.size() >= mMaxStringLength )
2349 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2350 // Cannot fit these characters into field, clear pre-edit.
2351 if ( !mUnderlinedPriorToPreEdit )
2354 style.SetUnderline( false );
2355 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2357 mIgnoreCommitFlag = true;
2358 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2359 mPreEditFlag = false;
2360 EmitMaxInputCharactersReachedSignal();
2364 // delete existing pre-edit string
2365 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2367 // Store new pre-edit string
2368 mPreEditString.SetText( keyString );
2370 if ( keyString.empty() )
2372 mPreEditFlag = false;
2373 mCursorPosition = mPreEditStartPosition;
2375 if( mStyledText.empty() )
2377 // Styled text is empty, so set the placeholder text.
2378 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2379 mPlaceHolderSet = true;
2383 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2385 GetTextLayoutInfo();
2390 // Insert new pre-edit string. InsertAt updates the size and position table.
2391 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2392 // 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.
2393 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2394 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2395 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2398 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2402 else // mPreEditFlag not set
2404 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2406 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2407 // new pre-edit so move into pre-edit state by setting flag
2408 mPreEditFlag = true;
2409 mPreEditString.SetText( keyString ); // store new pre-edit string
2410 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2411 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2412 // 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.
2413 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2414 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2415 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2416 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2422 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2426 return preeditResetRequest;
2429 bool TextInput::CommitReceived(const std::string& keyString )
2431 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2432 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2434 bool update( false );
2436 RemovePreEditStyle();
2438 const std::size_t styledTextSize( mStyledText.size() );
2439 if( styledTextSize >= mMaxStringLength )
2441 // Cannot fit these characters into field, clear pre-edit.
2444 mIgnoreCommitFlag = true;
2445 mPreEditFlag = false;
2447 EmitMaxInputCharactersReachedSignal();
2453 // delete existing pre-edit string
2454 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2455 mPreEditFlag = false;
2457 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2458 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2460 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2462 // No need to update cursor position as Cursor location given by touch.
2463 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2464 mPreserveCursorPosition = false;
2468 // Cursor not set by touch so needs to be re-positioned to input more text
2469 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2471 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2472 if ( mCommitByKeyInput )
2474 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2475 mCommitByKeyInput = false;
2481 if ( mSelectTextOnCommit )
2483 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2488 else // mPreEditFlag not set
2490 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2492 if( mStyledText.empty() && mPlaceHolderSet )
2494 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2495 mDisplayedTextView.SetText( "" );
2496 mNumberOfSurroundingCharactersDeleted = 0;
2497 mPlaceHolderSet = false;
2499 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2501 mNumberOfSurroundingCharactersDeleted = 0;
2506 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2511 mSelectTextOnCommit = false;
2513 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2514 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2519 // End of IMF related methods
2521 std::size_t TextInput::DeletePreEdit()
2523 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2525 DALI_ASSERT_DEBUG( mPreEditFlag );
2527 const std::size_t preEditStringLength = mPreEditString.GetLength();
2528 const std::size_t styledTextSize = mStyledText.size();
2530 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2532 // Prevents erase items outside mStyledText bounds.
2533 if( mPreEditStartPosition > styledTextSize )
2535 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2536 mPreEditStartPosition = styledTextSize;
2539 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2541 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2542 endPosition = styledTextSize;
2545 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2547 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2548 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2550 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2552 return preEditStringLength;
2555 void TextInput::PreEditReset( bool preserveCursorPosition )
2557 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2558 preserveCursorPosition, mCursorPosition);
2560 // 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.
2561 mPreserveCursorPosition = preserveCursorPosition;
2563 // Reset incase we are in a pre-edit state.
2564 ImfManager imfManager = ImfManager::Get();
2567 imfManager.Reset(); // Will trigger a commit message
2571 void TextInput::CursorUpdate()
2575 ImfManager imfManager = ImfManager::Get();
2578 std::string text( GetText() );
2579 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2580 imfManager.SetCursorPosition ( mCursorPosition );
2581 imfManager.NotifyCursorPosition();
2585 /* Delete highlighted characters redisplay*/
2586 void TextInput::DeleteHighlightedText( bool inheritStyle )
2588 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2590 if(mHighlightMeshActor)
2592 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2594 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2595 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2597 // Get the styled text of the characters to be deleted as it may be needed if
2598 // the "exceed the text-input's boundaries" option is disabled.
2599 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2601 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2603 mStyledText.erase( start, end ); // erase range of characters
2605 // Remove text from TextView.
2607 if( mStyledText.empty() )
2609 // Styled text is empty, so set the placeholder text.
2610 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2611 mPlaceHolderSet = true;
2615 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2617 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2619 // It may happen than after removing a white space or a new line character,
2620 // two words merge, this new word could be big enough to not fit in its
2621 // current line, so moved to the next one, and make some part of the text to
2622 // exceed the text-input's boundary.
2623 if( !mExceedEnabled )
2625 // Get the new text layout after removing some characters.
2626 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2628 // Get text-input's size.
2629 const Vector3& size = GetControlSize();
2631 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2632 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2634 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2636 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2637 styledCharactersToDelete.begin(),
2638 styledCharactersToDelete.end() );
2642 GetTextLayoutInfo();
2650 const TextStyle oldInputStyle( mInputStyle );
2652 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2654 if( oldInputStyle != mInputStyle )
2656 // Updates the line height accordingly with the input style.
2659 EmitStyleChangedSignal();
2665 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2667 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2668 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2670 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2673 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2675 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2676 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2678 mStyledText.erase(itStart, itEnd);
2680 // update the selection handles if they are visible.
2681 if( mHighlightMeshActor )
2683 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2684 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2686 if( minHandle >= start + ncharacters )
2688 minHandle -= ncharacters;
2690 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2695 if( maxHandle >= start + ncharacters )
2697 maxHandle -= ncharacters;
2699 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2705 // 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.
2708 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2710 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2711 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2712 // Mean we do not re-draw the text more than we have too.
2715 /* Delete character at current cursor position and redisplay*/
2716 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2718 // Ensure positionToDelete is not out of bounds.
2719 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2720 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2721 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2723 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2726 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2728 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2730 // Get the styled text of the character to be deleted as it may be needed if
2731 // the "exceed the text-input's boundaries" option is disabled.
2732 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2734 mStyledText.erase(it); // erase the character left of positionToDelete
2736 if( mStyledText.empty() )
2738 // Styled text is empty, so set the placeholder text.
2739 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2740 mPlaceHolderSet = true;
2744 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2746 const Character characterToDelete = styledCharacterToDelete.mText[0];
2748 // It may happen than after removing a white space or a new line character,
2749 // two words merge, this new word could be big enough to not fit in its
2750 // current line, so moved to the next one, and make some part of the text to
2751 // exceed the text-input's boundary.
2752 if( !mExceedEnabled )
2754 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2756 // Get the new text layout after removing one character.
2757 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2759 // Get text-input's size.
2760 const Vector3& size = GetControlSize();
2762 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2763 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2765 MarkupProcessor::StyledTextArray array;
2766 array.push_back( styledCharacterToDelete );
2767 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2769 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2774 GetTextLayoutInfo();
2776 ShowGrabHandleAndSetVisibility( false );
2778 mCursorPosition = positionToDelete -1;
2780 const TextStyle oldInputStyle( mInputStyle );
2782 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2784 if( oldInputStyle != mInputStyle )
2786 // Updates the line height accordingly with the input style.
2789 EmitStyleChangedSignal();
2794 /*Insert new character into the string and (optionally) redisplay text-input*/
2795 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2797 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2799 // Ensure insertionPosition is not out of bounds.
2800 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2802 bool textExceedsMaximunNumberOfCharacters = false;
2803 bool textExceedsBoundary = false;
2804 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2806 ShowGrabHandleAndSetVisibility( false );
2808 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2812 mIgnoreCommitFlag = true;
2813 mPreEditFlag = false;
2814 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2815 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2818 if( textExceedsMaximunNumberOfCharacters )
2820 EmitMaxInputCharactersReachedSignal();
2823 if( textExceedsBoundary )
2825 EmitInputTextExceedsBoundariesSignal();
2826 PreEditReset( false );
2830 return insertedStringLength;
2833 ImageActor TextInput::CreateCursor( const Vector4& color)
2836 cursor = CreateSolidColorActor(color);
2838 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2839 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2840 cursor.SetVisible(false);
2845 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2847 // As cursor is not moving due to grab handle, handle should be hidden.
2848 ShowGrabHandleAndSetVisibility( false );
2850 bool cursorPositionChanged = false;
2853 if ( mCursorPosition >= places )
2855 mCursorPosition = mCursorPosition - places;
2856 cursorPositionChanged = true;
2861 if ((mCursorPosition + places) <= mStyledText.size())
2863 mCursorPosition = mCursorPosition + places;
2864 cursorPositionChanged = true;
2868 if( cursorPositionChanged )
2870 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2872 const TextStyle oldInputStyle( mInputStyle );
2873 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2877 if( oldInputStyle != mInputStyle )
2879 // Updates the line height accordingly with the input style.
2882 EmitStyleChangedSignal();
2885 ImfManager imfManager = ImfManager::Get();
2888 imfManager.SetCursorPosition ( mCursorPosition );
2889 imfManager.NotifyCursorPosition();
2894 void TextInput::DrawCursor(const std::size_t nthChar)
2896 // Get height of cursor and set its size
2897 Size size( CURSOR_THICKNESS, 0.0f );
2898 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2900 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2904 // Measure Font so know how big text will be if no initial text to measure.
2905 size.height = mLineHeight;
2908 mCursor.SetSize(size);
2910 // If the character is italic then the cursor also tilts.
2911 mCursor.SetRotation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2913 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2915 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2917 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2918 bool altPositionValid; // Alternate cursor validity flag.
2919 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2920 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2922 SetAltCursorEnabled( altPositionValid );
2924 if(!altPositionValid)
2926 mCursor.SetPosition( position + UI_OFFSET );
2930 size.height *= 0.5f;
2931 mCursor.SetSize(size);
2932 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2934 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2935 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2936 size.height = rowSize.height * 0.5f;
2937 mCursorRTL.SetSize(size);
2938 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2941 if( IsScrollEnabled() )
2943 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2944 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2949 void TextInput::SetAltCursorEnabled( bool enabled )
2951 mCursorRTLEnabled = enabled;
2952 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2955 void TextInput::SetCursorVisibility( bool visible )
2957 mCursorVisibility = visible;
2958 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2959 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2962 void TextInput::CreateGrabHandle( Dali::Image image )
2968 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2972 mGrabHandleImage = image;
2975 mGrabHandle = ImageActor::New(mGrabHandleImage);
2976 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2977 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2979 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2981 ShowGrabHandleAndSetVisibility( false );
2983 CreateGrabArea( mGrabHandle );
2985 mActiveLayer.Add(mGrabHandle);
2989 void TextInput::CreateGrabArea( Actor& parent )
2991 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2992 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2993 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2994 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2995 mTapDetector.Attach( mGrabArea );
2996 mPanGestureDetector.Attach( mGrabArea );
2998 parent.Add(mGrabArea);
3001 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
3003 Vector3 actualHandlePosition;
3007 mActualGrabHandlePosition.x += displacement.x;
3008 mActualGrabHandlePosition.y += displacement.y;
3010 // Grab handle should jump to the nearest character and take cursor with it
3011 std::size_t newCursorPosition = 0;
3012 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
3014 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
3016 bool handleVisible = true;
3018 if( IsScrollEnabled() )
3020 const Vector3 controlSize = GetControlSize();
3021 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
3022 // Scrolls the text if the handle is not in a visible position
3023 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3030 mCurrentHandlePosition = actualHandlePosition;
3031 mScrollDisplacement = Vector2::ZERO;
3035 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3037 mScrollDisplacement.x = -SCROLL_SPEED;
3039 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3041 mScrollDisplacement.x = SCROLL_SPEED;
3043 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) &&&