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.
19 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
25 #include <dali/public-api/adaptor-framework/virtual-keyboard.h>
26 #include <dali/public-api/animation/constraints.h>
27 #include <dali/public-api/common/stage.h>
28 #include <dali/public-api/events/key-event.h>
29 #include <dali/public-api/events/touch-event.h>
30 #include <dali/public-api/object/type-registry.h>
31 #include <dali/public-api/object/property-notification.h>
32 #include <dali/integration-api/debug.h>
33 #include <dali/public-api/images/resource-image.h>
36 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
37 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
38 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
39 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
47 #if defined(DEBUG_ENABLED)
48 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
51 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
52 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
53 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
54 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
55 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
56 const Vector4 LIGHTBLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // Used for Selection highlight
58 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
59 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
60 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
61 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
62 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
64 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
65 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
66 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.1f ); ///< 1. Highlight rendered (z-offset).
67 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.2f ); ///< 2. Text rendered (z-offset).
68 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
70 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
71 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
72 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
73 const float TOP_HANDLE_TOP_OFFSET( 34.0f); ///< Offset between top handle and cutCopyPaste pop-up
74 const float BOTTOM_HANDLE_BOTTOM_OFFSET(34.0f); ///< Offset between bottom handle and cutCopyPaste pop-up
75 const float CURSOR_THICKNESS(4.0f);
76 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
77 const Vector4 DEFAULT_CURSOR_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
79 const std::string NEWLINE( "\n" );
81 const TextStyle DEFAULT_TEXT_STYLE;
83 const unsigned int SCROLL_TICK_INTERVAL = 50u;
84 const float SCROLL_THRESHOLD = 10.f;
85 const float SCROLL_SPEED = 15.f;
88 * Selection state enumeration (FSM)
92 SelectionNone, ///< Currently not encountered selected section.
93 SelectionStarted, ///< Encountered selected section
94 SelectionFinished ///< Finished selected section
97 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
99 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
103 if( ( *it ).mIsVisible )
105 return --cursorPosition;
114 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
116 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
118 if( ( *it ).mIsVisible )
120 return cursorPosition;
126 return cursorPosition;
130 * Whether the given position plus the cursor size offset is inside the given boundary.
132 * @param[in] position The given position.
133 * @param[in] cursorSize The cursor size.
134 * @param[in] controlSize The given boundary.
136 * @return whether the given position is inside the given boundary.
138 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
140 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
141 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
142 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
143 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
147 * Splits a text in two halves.
149 * If the text's number of characters is odd, firstHalf has one more character.
151 * @param[in] text The text to be split.
152 * @param[out] firstHalf The first half of the text.
153 * @param[out] secondHalf The second half of the text.
155 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
156 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
157 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
162 const std::size_t textLength = text.size();
163 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
165 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
166 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
169 } // end of namespace
177 const Property::Index TextInput::HIGHLIGHT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX;
178 const Property::Index TextInput::CUT_AND_PASTE_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+1;
179 const Property::Index TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+2;
180 const Property::Index TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+3;
181 const Property::Index TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+4;
182 const Property::Index TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+5;
183 const Property::Index TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+6;
184 const Property::Index TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+7;
185 const Property::Index TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+8;
186 const Property::Index TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+9;
187 const Property::Index TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+10;
188 const Property::Index TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+11;
189 const Property::Index TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+12;
190 const Property::Index TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+13;
191 const Property::Index TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+14;
192 const Property::Index TextInput::CURSOR_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+15;
203 return Toolkit::TextInput::New();
206 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
208 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
209 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
210 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
211 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
212 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
213 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
217 PropertyRegistration property1( typeRegistration, "highlight-color", Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
218 PropertyRegistration property2( typeRegistration, "cut-and-paste-bg-color", Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
219 PropertyRegistration property3( typeRegistration, "cut-and-paste-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
220 PropertyRegistration property4( typeRegistration, "cut-and-paste-icon-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
221 PropertyRegistration property5( typeRegistration, "cut-and-paste-icon-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
222 PropertyRegistration property6( typeRegistration, "cut-and-paste-text-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
223 PropertyRegistration property7( typeRegistration, "cut-and-paste-text-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
224 PropertyRegistration property8( typeRegistration, "cut-and-paste-border-color", Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
225 PropertyRegistration property9( typeRegistration, "cut-button-position-priority", Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
226 PropertyRegistration property10( typeRegistration, "copy-button-position-priority", Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
227 PropertyRegistration property11( typeRegistration, "paste-button-position-priority", Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
228 PropertyRegistration property12( typeRegistration, "select-button-position-priority", Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
229 PropertyRegistration property13( typeRegistration, "select-all-button-position-priority", Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
230 PropertyRegistration property14( typeRegistration, "clipboard-button-position-priority", Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
231 PropertyRegistration property15( typeRegistration, "popup-offset-from-text", Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
232 PropertyRegistration property16( typeRegistration, "cursor-color", Toolkit::TextInput::CURSOR_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
235 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
237 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
239 QuadCoordinates quad(x1, y1, x2, y2);
240 mQuadList.push_back( quad );
243 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
245 for(std::size_t i = 0;i < mQuadList.size(); i++)
247 QuadCoordinates& quad = mQuadList[i];
249 quad.min.Clamp(min, max);
250 quad.max.Clamp(min, max);
254 // [TextInput] ////////////////////////////////////////////////////////////////
256 Dali::Toolkit::TextInput TextInput::New()
258 // Create the implementation
259 TextInputPtr textInput(new TextInput());
260 // Pass ownership to CustomActor via derived handle
261 Dali::Toolkit::TextInput handle(*textInput);
262 handle.SetName( "TextInput");
264 textInput->Initialize();
268 TextInput::TextInput()
269 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
274 mDisplayedTextView(),
275 mStyledPlaceHolderText(),
276 mMaxStringLength( DEFAULT_MAX_SIZE ),
277 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
278 mCursorPosition( 0 ),
279 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
280 mIsSelectionHandleOneFlipped( false ),
281 mIsSelectionHandleTwoFlipped( false ),
282 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
283 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
284 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
285 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
286 mSelectionHandleOnePosition( 0 ),
287 mSelectionHandleTwoPosition( 0 ),
289 mPreEditStartPosition( 0 ),
290 mPreEditLength ( 0 ),
291 mNumberOfSurroundingCharactersDeleted( 0 ),
292 mTouchStartTime( 0 ),
294 mCurrentCopySelecton(),
297 mScrollDisplacement(),
298 mCurrentHandlePosition(),
299 mCurrentSelectionId(),
300 mCurrentSelectionHandlePosition(),
301 mRequestedSelection( 0, 0 ),
302 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
303 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
305 mMaterialColor( LIGHTBLUE ),
306 mPopupOffsetFromText ( Vector4( 0.0f, TOP_HANDLE_TOP_OFFSET, 0.0f, BOTTOM_HANDLE_BOTTOM_OFFSET ) ),
307 mOverrideAutomaticAlignment( false ),
308 mCursorRTLEnabled( false ),
309 mClosestCursorPositionEOL ( false ),
310 mCursorBlinkStatus( true ),
311 mCursorVisibility( false ),
312 mGrabHandleVisibility( false ),
313 mIsCursorInScrollArea( true ),
314 mIsGrabHandleInScrollArea( true ),
315 mEditModeActive( false ),
316 mEditOnTouch( true ),
317 mTextSelection( true ),
318 mExceedEnabled( true ),
319 mGrabHandleEnabled( true ),
320 mIsSelectionHandleFlipEnabled( true ),
321 mPreEditFlag( false ),
322 mIgnoreCommitFlag( false ),
323 mIgnoreFirstCommitFlag( false ),
324 mSelectingText( false ),
325 mPreserveCursorPosition( false ),
326 mSelectTextOnCommit( false ),
327 mUnderlinedPriorToPreEdit ( false ),
328 mCommitByKeyInput( false ),
329 mPlaceHolderSet( false ),
330 mMarkUpEnabled( false )
332 // Updates the line height accordingly with the input style.
336 TextInput::~TextInput()
338 StopCursorBlinkTimer();
343 std::string TextInput::GetText() const
347 // Return text-view's text only if the text-input's text is not empty
348 // in order to not to return the placeholder text.
349 if( !mStyledText.empty() )
351 text = mDisplayedTextView.GetText();
357 std::string TextInput::GetMarkupText() const
359 std::string markupString;
360 MarkupProcessor::GetMarkupString( mStyledText, markupString );
365 void TextInput::ShowPlaceholderText( const MarkupProcessor::StyledTextArray& stylePlaceHolderText )
367 mDisplayedTextView.SetText( stylePlaceHolderText );
368 mPlaceHolderSet = true;
369 mDisplayedTextView.SetScrollPosition( Vector2( 0.0f,0.0f ) );
372 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
374 // Get the placeholder styled text array from the markup string.
375 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
376 if( mStyledText.empty() )
378 ShowPlaceholderText( mStyledPlaceHolderText );
382 std::string TextInput::GetPlaceholderText()
384 // Traverses the styled placeholder array getting only the text.
385 // Note that for some languages a 'character' could be represented by more than one 'char'
387 std::string placeholderText;
388 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
390 placeholderText.append( (*it).mText.GetText() );
393 return placeholderText ;
396 void TextInput::SetInitialText(const std::string& initialText)
398 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
400 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
402 mPreEditFlag = false;
403 mIgnoreCommitFlag = true;
406 SetText( initialText );
407 PreEditReset( false ); // Reset keyboard as text changed
410 void TextInput::SetText(const std::string& initialText)
412 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
414 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
416 if( mStyledText.empty() )
418 ShowPlaceholderText( mStyledPlaceHolderText );
422 mDisplayedTextView.SetText( mStyledText );
423 mPlaceHolderSet = false;
428 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
430 ImfManager imfManager = ImfManager::Get();
433 imfManager.SetCursorPosition( mCursorPosition );
434 imfManager.SetSurroundingText( initialText );
435 imfManager.NotifyCursorPosition();
438 if( IsScrollEnabled() )
440 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
443 ShowGrabHandleAndSetVisibility( false );
452 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
454 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
456 mDisplayedTextView.SetText( styleText );
457 mPlaceHolderSet = false;
459 // If text alignment hasn't been manually set by application developer, then we
460 // automatically determine the alignment based on the content of the text i.e. what
461 // language the text begins with.
462 // TODO: This should determine different alignments for each line (broken by '\n') of text.
463 if(!mOverrideAutomaticAlignment)
465 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
466 bool leftToRight(true);
468 if( !styleText.empty() )
470 bool breakOut(false);
472 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
474 const Text& text = textIter->mText;
476 for( std::size_t i = 0; i < text.GetLength(); ++i )
478 Character character( text[i] );
479 if( character.GetCharacterDirection() != Character::Neutral )
481 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
489 // Based on this direction, either left or right align text if not manually set by application developer.
490 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
491 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
492 Toolkit::Alignment::VerticalTop ) );
493 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
499 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
501 mMaxStringLength = maxChars;
504 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
506 DALI_ASSERT_DEBUG( maxLines > 0 )
510 mNumberOflinesLimit = maxLines;
514 std::size_t TextInput::GetNumberOfLinesLimit() const
516 return mNumberOflinesLimit;
519 std::size_t TextInput::GetNumberOfCharacters() const
521 return mStyledText.size();
525 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
527 mMaterialColor = color;
528 if ( mCustomMaterial )
530 mCustomMaterial.SetDiffuseColor( mMaterialColor );
531 mMeshData.SetMaterial( mCustomMaterial );
535 const Vector4& TextInput::GetMaterialDiffuseColor() const
537 return mMaterialColor;
542 Toolkit::TextInput::InputSignalType& TextInput::InputStartedSignal()
544 return mInputStartedSignal;
547 Toolkit::TextInput::InputSignalType& TextInput::InputFinishedSignal()
549 return mInputFinishedSignal;
552 Toolkit::TextInput::InputSignalType& TextInput::CutAndPasteToolBarDisplayedSignal()
554 return mCutAndPasteToolBarDisplayed;
557 Toolkit::TextInput::StyleChangedSignalType& TextInput::StyleChangedSignal()
559 return mStyleChangedSignal;
562 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
564 return mTextModifiedSignal;
567 Toolkit::TextInput::MaxInputCharactersReachedSignalType& TextInput::MaxInputCharactersReachedSignal()
569 return mMaxInputCharactersReachedSignal;
572 Toolkit::TextInput::InputTextExceedBoundariesSignalType& TextInput::InputTextExceedBoundariesSignal()
574 return mInputTextExceedBoundariesSignal;
577 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
579 Dali::BaseHandle handle( object );
581 bool connected( true );
582 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
584 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
586 textInput.InputStartedSignal().Connect( tracker, functor );
588 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
590 textInput.InputFinishedSignal().Connect( tracker, functor );
592 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
594 textInput.StyleChangedSignal().Connect( tracker, functor );
596 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
598 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
600 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
602 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
606 // signalName does not match any signal
613 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
617 // update line height before calculate the actual position.
622 if( setCursorOnTouchPoint )
624 // Sets the cursor position for the given touch point.
625 ReturnClosestIndex( touchPoint, mCursorPosition );
627 // Creates the grab handle.
628 if( IsGrabHandleEnabled() )
630 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
634 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
635 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
636 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
637 ShowGrabHandleAndSetVisibility( true );
639 // Scrolls the text-view if needed.
640 if( IsScrollEnabled() )
642 ScrollTextViewToMakeCursorVisible( cursorPosition );
648 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
660 bool TextInput::IsEditable() const
662 return mEditModeActive;
665 void TextInput::SetEditOnTouch( bool editOnTouch )
667 mEditOnTouch = editOnTouch;
670 bool TextInput::IsEditOnTouch() const
675 void TextInput::SetTextSelectable( bool textSelectable )
677 mTextSelection = textSelectable;
680 bool TextInput::IsTextSelectable() const
682 return mTextSelection;
685 bool TextInput::IsTextSelected() const
687 return mHighlightMeshActor;
690 void TextInput::DeSelectText()
697 void TextInput::SetGrabHandleImage(Dali::Image image )
701 CreateGrabHandle(image);
705 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
707 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
711 mCursor.SetImage( image );
712 mCursor.SetNinePatchBorder( border );
716 Vector3 TextInput::GetSelectionHandleSize()
718 return DEFAULT_SELECTION_HANDLE_SIZE;
721 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
723 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
727 mCursorRTL.SetImage( image);
728 mCursorRTL.SetNinePatchBorder( border );
732 void TextInput::EnableGrabHandle(bool toggle)
734 // enables grab handle with will in turn de-activate magnifier
735 mGrabHandleEnabled = toggle;
738 bool TextInput::IsGrabHandleEnabled()
740 // if false then magnifier will be shown instead.
741 return mGrabHandleEnabled;
744 void TextInput::EnableSelectionHandleFlip( bool toggle )
746 // Deprecated function. To be removed.
747 mIsSelectionHandleFlipEnabled = toggle;
750 bool TextInput::IsSelectionHandleFlipEnabled()
752 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
756 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
758 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
759 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
760 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
762 mSelectionHandleFlipMargin = margin;
765 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
767 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
768 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
770 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
771 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
773 const Vector4 boundary( originX,
775 originX + boundingRectangle.width,
776 originY + boundingRectangle.height );
778 mBoundingRectangleWorldCoordinates = boundary;
781 const Rect<float> TextInput::GetBoundingRectangle() const
783 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
785 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
786 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
788 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
793 const Vector4& TextInput::GetSelectionHandleFlipMargin()
795 return mSelectionHandleFlipMargin;
798 void TextInput::SetTextColor( const Vector4& color )
800 mDisplayedTextView.SetColor( color );
803 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
805 if( style != mInputStyle )
808 bool emitSignal = false;
810 // mask: modify style according to mask, if different emit signal.
811 const TextStyle oldInputStyle( mInputStyle );
813 // Copy the new style.
814 mInputStyle.Copy( style, mask );
816 // if style has changed, emit signal.
817 if( oldInputStyle != mInputStyle )
822 // Updates the line height accordingly with the input style.
825 // Changing font point size will require the cursor to be re-sized
830 EmitStyleChangedSignal();
835 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
837 if ( IsTextSelected() )
839 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
840 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
842 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
844 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
847 // Keeps the old style to be compared with the new one.
848 const TextStyle oldInputStyle( mInputStyle );
850 // Copy only those parameters from the style which are set in the mask.
851 mInputStyle.Copy( style, mask );
853 if( mInputStyle != oldInputStyle )
855 // Updates the line height accordingly with the input style.
858 EmitStyleChangedSignal();
863 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
865 if( !mStyledText.empty() )
867 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
871 TextStyle TextInput::GetStyleAtCursor() const
875 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
877 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
878 style = mStyledText.at( mCursorPosition-1 ).mStyle;
884 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
886 Dali::Font defaultFont = Dali::Font::New();
887 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
894 TextStyle TextInput::GetStyleAt( std::size_t position ) const
896 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
898 if( position >= mStyledText.size() )
900 position = mStyledText.size() - 1;
903 return mStyledText.at( position ).mStyle;
906 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
908 mDisplayedTextView.SetTextAlignment( align );
909 mOverrideAutomaticAlignment = true;
912 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
914 mDisplayedTextView.SetLineJustification( justification );
915 mOverrideAutomaticAlignment = true;
918 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
920 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
923 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
925 return mDisplayedTextView.GetFadeBoundary();
928 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
930 return mDisplayedTextView.GetTextAlignment();
933 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
935 mDisplayedTextView.SetMultilinePolicy( policy );
938 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
940 return mDisplayedTextView.GetMultilinePolicy();
943 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
945 mDisplayedTextView.SetWidthExceedPolicy( policy );
948 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
950 return mDisplayedTextView.GetWidthExceedPolicy();
953 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
955 mDisplayedTextView.SetHeightExceedPolicy( policy );
958 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
960 return mDisplayedTextView.GetHeightExceedPolicy();
963 void TextInput::SetExceedEnabled( bool enable )
965 mExceedEnabled = enable;
968 bool TextInput::GetExceedEnabled() const
970 return mExceedEnabled;
973 void TextInput::SetBackground(Dali::Image image )
975 // TODO Should add this function and add public api to match.
978 bool TextInput::OnTouchEvent(const TouchEvent& event)
983 bool TextInput::OnKeyEvent(const KeyEvent& event)
985 switch( event.state )
989 return OnKeyDownEvent(event);
995 return OnKeyUpEvent(event);
1007 void TextInput::OnKeyInputFocusGained()
1009 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1011 mEditModeActive = true;
1013 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1015 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1017 // Updates the line height accordingly with the input style.
1020 // Connect the signals to use in text input.
1021 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1022 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1024 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1027 GetTextLayoutInfo();
1030 SetCursorVisibility( true );
1031 StartCursorBlinkTimer();
1033 Toolkit::TextInput handle( GetOwner() );
1034 mInputStartedSignal.Emit( handle );
1036 ImfManager imfManager = ImfManager::Get();
1040 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1042 // Notify that the text editing start.
1043 imfManager.Activate();
1045 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1046 imfManager.SetRestoreAfterFocusLost( true );
1048 imfManager.SetCursorPosition( mCursorPosition );
1049 imfManager.NotifyCursorPosition();
1052 mClipboard = Clipboard::Get(); // Store handle to clipboard
1054 // Now in edit mode we can accept string to paste from clipboard
1055 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1058 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1062 void TextInput::OnKeyInputFocusLost()
1064 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1068 // If key input focus is lost, it removes the
1069 // underline from the last pre-edit text.
1070 RemovePreEditStyle();
1071 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1072 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1076 ImfManager imfManager = ImfManager::Get();
1079 // The text editing is finished. Therefore the imf manager don't have restore activation.
1080 imfManager.SetRestoreAfterFocusLost( false );
1082 // Notify that the text editing finish.
1083 imfManager.Deactivate();
1085 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1087 // Disconnect signal used the text input.
1088 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1090 Toolkit::TextInput handle( GetOwner() );
1091 mInputFinishedSignal.Emit( handle );
1092 mEditModeActive = false;
1093 mPreEditFlag = false;
1095 SetCursorVisibility( false );
1096 StopCursorBlinkTimer();
1098 ShowGrabHandleAndSetVisibility( false );
1101 // No longer in edit mode so do not want to receive string from clipboard
1102 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1105 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1108 Clipboard clipboard = Clipboard::Get();
1111 clipboard.HideClipboard();
1115 void TextInput::OnControlStageConnection()
1117 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1119 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1121 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1125 void TextInput::CreateActiveLayer()
1127 Actor self = Self();
1128 mActiveLayer = Layer::New();
1129 mActiveLayer.SetName ( "ActiveLayerActor" );
1131 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1132 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1133 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1135 self.Add( mActiveLayer );
1136 mActiveLayer.RaiseToTop();
1139 void TextInput::OnInitialize()
1141 CreateTextViewActor();
1145 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1146 // different positions depending on language)
1147 mCursor = CreateCursor(DEFAULT_CURSOR_COLOR);
1148 mCursorRTL = CreateCursor(DEFAULT_CURSOR_COLOR);
1150 Actor self = Self();
1151 self.Add( mCursor );
1152 self.Add( mCursorRTL );
1154 mCursorVisibility = false;
1156 CreateActiveLayer(); // todo move this so layer only created when needed.
1158 // Assign names to image actors
1159 mCursor.SetName("mainCursor");
1160 mCursorRTL.SetName("rtlCursor");
1163 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1165 mDisplayedTextView.SetSize( targetSize );
1166 GetTextLayoutInfo();
1167 mActiveLayer.SetSize(targetSize);
1170 void TextInput::OnRelayout( const Vector2& size, ActorSizeContainer& container )
1172 Relayout( mDisplayedTextView, size, container );
1173 Relayout( mPopupPanel.GetRootActor(), size, container );
1175 GetTextLayoutInfo();
1180 Vector3 TextInput::GetNaturalSize()
1182 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1184 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1186 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1187 naturalSize.height = mLineHeight;
1193 float TextInput::GetHeightForWidth( float width )
1195 float height = mDisplayedTextView.GetHeightForWidth( width );
1197 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1199 // If the height is zero, it means there is no text. Let's return the cursor height.
1200 height = mLineHeight;
1206 /*end of Virtual methods from parent*/
1208 // Private Internal methods
1210 void TextInput::OnHandlePan(Actor actor, const PanGesture& gesture)
1212 switch (gesture.state)
1214 case Gesture::Started:
1215 // fall through so code not duplicated
1216 case Gesture::Continuing:
1218 if (actor == mGrabArea)
1220 SetCursorVisibility( true );
1221 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1222 MoveGrabHandle( gesture.displacement );
1223 HidePopup(); // Do not show popup whilst handle is moving
1225 else if (actor == mHandleOneGrabArea)
1227 // the displacement in PanGesture is affected by the actor's rotation.
1228 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1229 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1231 MoveSelectionHandle( HandleOne, gesture.displacement );
1233 mState = StateDraggingHandle;
1236 else if (actor == mHandleTwoGrabArea)
1238 // the displacement in PanGesture is affected by the actor's rotation.
1239 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1240 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1242 MoveSelectionHandle( HandleTwo, gesture.displacement );
1244 mState = StateDraggingHandle;
1250 case Gesture::Finished:
1252 // Revert back to non-pressed selection handle images
1253 if (actor == mGrabArea)
1255 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1256 SetCursorVisibility( true );
1257 SetUpPopupSelection();
1260 if (actor == mHandleOneGrabArea)
1262 // the displacement in PanGesture is affected by the actor's rotation.
1263 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1264 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1266 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1268 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1270 ShowPopupCutCopyPaste();
1272 if (actor == mHandleTwoGrabArea)
1274 // the displacement in PanGesture is affected by the actor's rotation.
1275 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1276 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1278 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1280 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1282 ShowPopupCutCopyPaste();
1291 // Stop the flashing animation so easy to see when moved.
1292 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1294 if (touch.GetPoint(0).state == TouchPoint::Down)
1296 SetCursorVisibility( true );
1297 StopCursorBlinkTimer();
1299 else if (touch.GetPoint(0).state == TouchPoint::Up)
1301 SetCursorVisibility( true );
1302 StartCursorBlinkTimer();
1307 // selection handle one
1308 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1310 if (touch.GetPoint(0).state == TouchPoint::Down)
1312 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1314 else if (touch.GetPoint(0).state == TouchPoint::Up)
1316 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1321 // selection handle two
1322 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1324 if (touch.GetPoint(0).state == TouchPoint::Down)
1326 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1328 else if (touch.GetPoint(0).state == TouchPoint::Up)
1330 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1335 void TextInput::OnDoubleTap(Dali::Actor actor, const Dali::TapGesture& tap)
1337 // If text exists then select nearest word.
1338 if ( !mStyledText.empty())
1342 ShowGrabHandleAndSetVisibility( false );
1347 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1348 // converts the pre-edit word being displayed to a committed word.
1349 if ( !mUnderlinedPriorToPreEdit )
1352 style.SetUnderline( false );
1353 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1355 mPreEditFlag = false;
1356 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1357 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1358 PreEditReset( false );
1360 mCursorPosition = 0;
1362 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1363 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1365 std::size_t start = 0;
1366 std::size_t end = 0;
1367 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1369 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1371 ImfManager imfManager = ImfManager::Get();
1374 imfManager.SetCursorPosition ( mCursorPosition );
1375 imfManager.NotifyCursorPosition();
1378 if ( !mStyledText.at(end-1).mText[0].IsWhiteSpace() )
1380 SelectText( start, end );
1381 ShowPopupCutCopyPaste();
1385 RemoveHighlight( false ); // Remove highlight but do not auto hide popup
1386 HidePopup( false ); // Hide popup with setting to do auto show.
1387 SetUpPopupSelection( false ); // Set to false so if nearest word is whitespace it will not show cut button.
1391 else if ( mClipboard && mClipboard.NumberOfItems() )
1393 ShowPopupCutCopyPaste();
1396 // If no text and clipboard empty then do nothing
1399 // TODO: Change the function name to be more general.
1400 void TextInput::OnTextTap(Dali::Actor actor, const Dali::TapGesture& tap)
1402 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1403 , (mEditOnTouch)?"true":"false"
1404 , (mEditModeActive)?"true":"false");
1406 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1411 if( mGrabArea == actor )
1413 if( mPopupPanel.GetState() == TextInputPopup::StateHidden || mPopupPanel.GetState() == TextInputPopup::StateHiding )
1415 SetUpPopupSelection();
1425 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1427 // Initially don't create the grab handle.
1428 bool createGrabHandle = false;
1430 if ( !mEditModeActive )
1432 // update line height before calculate the actual position.
1435 // Only start edit mode if TextInput configured to edit on touch
1438 // Set the initial cursor position in the tap point.
1439 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1445 // Show the keyboard if it was hidden.
1446 if (!VirtualKeyboard::IsVisible())
1448 VirtualKeyboard::Show();
1451 // Reset keyboard as tap event has occurred.
1452 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1453 PreEditReset( true );
1455 GetTextLayoutInfo();
1457 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1459 // 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.
1461 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1463 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1465 // Notify keyboard so it can 're-capture' word for predictive text.
1466 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1467 ImfManager imfManager = ImfManager::Get();
1470 imfManager.SetCursorPosition ( mCursorPosition );
1471 imfManager.NotifyCursorPosition();
1473 const TextStyle oldInputStyle( mInputStyle );
1475 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1479 // Create the grab handle.
1480 // Grab handle is created later.
1481 createGrabHandle = true;
1483 if( oldInputStyle != mInputStyle )
1485 // Updates the line height accordingly with the input style.
1488 EmitStyleChangedSignal();
1493 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1494 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1495 // otherwise the Grab handle will be shown when selecting.
1496 if ( createGrabHandle && IsGrabHandleEnabled() )
1498 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
1499 bool altPositionValid; // Alternate cursor validity flag.
1500 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1501 Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
1503 if( altPositionValid )
1505 // Check which of the positions is the closest.
1506 if( fabsf( altPosition.x - tap.localPoint.x ) < fabsf( cursorPosition.x - tap.localPoint.x ) )
1508 cursorPosition = altPosition;
1514 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1515 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1516 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1517 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1522 void TextInput::OnLongPress(Dali::Actor actor, const Dali::LongPressGesture& longPress)
1524 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1526 // Ignore longpress if in selection mode already
1527 if( mHighlightMeshActor )
1532 if(longPress.state == Dali::Gesture::Started)
1534 // Start edit mode on long press
1535 if ( !mEditModeActive )
1540 // If text exists then select nearest word.
1541 if ( !mStyledText.empty())
1545 ShowGrabHandleAndSetVisibility( false );
1550 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1551 // converts the pre-edit word being displayed to a committed word.
1552 if ( !mUnderlinedPriorToPreEdit )
1555 style.SetUnderline( false );
1556 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1558 mPreEditFlag = false;
1559 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1560 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1561 PreEditReset( false );
1563 mCursorPosition = 0;
1565 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1566 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1568 std::size_t start = 0;
1569 std::size_t end = 0;
1570 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1572 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1574 ImfManager imfManager = ImfManager::Get();
1577 imfManager.SetCursorPosition ( mCursorPosition );
1578 imfManager.NotifyCursorPosition();
1581 SelectText( start, end );
1584 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1585 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1587 ShowPopupCutCopyPaste();
1592 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1594 const Text clipboardText( notifier.GetContent() );
1595 PasteText( clipboardText );
1597 SetCursorVisibility( true );
1598 StartCursorBlinkTimer();
1600 ShowGrabHandleAndSetVisibility( false );
1606 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1608 mPopupPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1610 const std::string& name = button.GetName();
1612 if(name == TextInputPopup::OPTION_SELECT_WORD)
1614 std::size_t start = 0;
1615 std::size_t end = 0;
1616 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1618 SelectText( start, end );
1620 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1622 SetCursorVisibility(false);
1623 StopCursorBlinkTimer();
1625 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1626 std::size_t start = 0;
1628 SelectText( start, end );
1630 else if(name == TextInputPopup::OPTION_CUT)
1632 bool ret = CopySelectedTextToClipboard();
1636 DeleteHighlightedText( true );
1640 SetCursorVisibility( true );
1641 StartCursorBlinkTimer();
1645 else if(name == TextInputPopup::OPTION_COPY)
1647 CopySelectedTextToClipboard();
1651 SetCursorVisibility( true );
1652 StartCursorBlinkTimer();
1656 else if(name == TextInputPopup::OPTION_PASTE)
1658 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1660 PasteText(retrievedString);
1662 SetCursorVisibility( true );
1663 StartCursorBlinkTimer();
1665 ShowGrabHandleAndSetVisibility( false );
1669 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1671 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1672 // Hence pass the false parameter for signalFinished.
1673 HidePopup( true, false );
1674 mClipboard.ShowClipboard();
1680 bool TextInput::OnCursorBlinkTimerTick()
1683 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1684 if ( mCursorRTLEnabled )
1686 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1688 mCursorBlinkStatus = !mCursorBlinkStatus;
1693 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1695 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1697 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1698 if(mHighlightMeshActor && mState == StateEdit)
1700 ShowPopupCutCopyPaste();
1704 //FIXME this routine needs to be re-written as it contains too many branches.
1705 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1707 std::string keyName = event.keyPressedName;
1708 std::string keyString = event.keyPressed;
1710 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1712 // Do not consume "Tab" and "Escape" keys.
1713 if(keyName == "Tab" || keyName == "Escape")
1715 // Escape key to end the edit mode
1721 HidePopup(); // If Pop-up shown then hides it as editing text.
1723 // Update Flag, indicates whether to update the text-input contents or not.
1724 // Any key stroke that results in a visual change of the text-input should
1725 // set this flag to true.
1728 // Whether to scroll text to cursor position.
1729 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1730 bool scroll = false;
1732 if (keyName == "Return")
1734 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1736 bool preEditFlagPreviouslySet( mPreEditFlag );
1738 // replaces highlighted text with new line
1739 DeleteHighlightedText( false );
1741 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1743 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1744 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1747 mCommitByKeyInput = true;
1750 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1751 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1753 mPreEditFlag = true;
1754 mIgnoreCommitFlag = false;
1764 else if ( keyName == "space" )
1766 if ( mHighlightMeshActor )
1768 // Some text is selected so erase it before adding space.
1769 DeleteHighlightedText( true );
1772 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1774 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1775 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1778 mCommitByKeyInput = true;
1783 else if (keyName == "BackSpace")
1785 if ( mHighlightMeshActor )
1787 // Some text is selected so erase it
1788 DeleteHighlightedText( true );
1793 if ( mCursorPosition > 0 )
1795 DeleteCharacter( mCursorPosition );
1801 else if (keyName == "Right")
1806 else if (keyName == "Left")
1808 AdvanceCursor(true);
1811 else // event is a character
1813 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1814 if ( !keyString.empty() )
1816 // replaces highlighted text with new character
1817 DeleteHighlightedText( false );
1819 // Received key String
1820 mCursorPosition += InsertAt( Text( keyString ), mCursorPosition, 0 );
1826 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1827 // as this is a costly operation.
1833 if(update || scroll)
1835 if( IsScrollEnabled() )
1837 // Calculates the new cursor position (in actor coordinates)
1838 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1840 ScrollTextViewToMakeCursorVisible( cursorPosition );
1847 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1849 std::string keyName = event.keyPressedName;
1850 std::string keyString = event.keyPressed;
1852 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1854 // The selected text become deselected when the key code is DALI_KEY_BACK.
1855 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1864 void TextInput::ChooseRtlSelectionHandlePosition( const Vector3& cursorPositionOne,
1865 const Vector3& cursorPositionTwo,
1866 bool altPositionValidOne,
1867 bool altPositionValidTwo,
1868 const Vector3& altPositionOne,
1869 const Vector3& altPositionTwo )
1871 // TODO VCC Valid for one line.
1872 // Try to place the selection handles. TODO think in something better. Probably need to know the direction of the paragraph.
1873 if( cursorPositionOne != cursorPositionTwo )
1875 if( cursorPositionOne.x < cursorPositionTwo.x )
1877 mSelectionHandleOneActualPosition = cursorPositionOne;
1878 mSelectionHandleTwoActualPosition = cursorPositionTwo;
1882 mSelectionHandleOneActualPosition = cursorPositionTwo;
1883 mSelectionHandleTwoActualPosition = cursorPositionOne;
1888 mSelectionHandleOneActualPosition = cursorPositionOne;
1889 if( altPositionValidOne )
1891 if( altPositionOne.x < mSelectionHandleOneActualPosition.x )
1893 mSelectionHandleOneActualPosition = altPositionOne;
1896 if( altPositionValidTwo )
1898 if( altPositionTwo.x < mSelectionHandleOneActualPosition.x )
1900 mSelectionHandleOneActualPosition = altPositionTwo;
1904 mSelectionHandleTwoActualPosition = cursorPositionTwo;
1905 if( altPositionValidTwo )
1907 if( altPositionTwo.x > mSelectionHandleTwoActualPosition.x )
1909 mSelectionHandleTwoActualPosition = altPositionTwo;
1912 if( altPositionValidOne )
1914 if( altPositionOne.x > mSelectionHandleTwoActualPosition.x )
1916 mSelectionHandleTwoActualPosition = altPositionOne;
1922 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1924 // Updates the stored scroll position.
1925 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1927 const Vector3& controlSize = GetControlSize();
1928 Size cursorSize( CURSOR_THICKNESS, 0.f );
1930 // Updates the cursor and grab handle position and visibility.
1931 if( mGrabHandle || mCursor )
1933 cursorSize.height = GetRowRectFromCharacterPosition( mCursorPosition ).height;
1935 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
1936 bool altPositionValid; // Alternate cursor validity flag.
1937 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1938 Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
1940 if( altPositionValid )
1942 // Check which of the positions is the closest.
1943 if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( cursorPosition.x - mActualGrabHandlePosition.x ) )
1945 cursorPosition = altPosition;
1949 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1951 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1955 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1956 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1961 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1962 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1966 // Updates the selection handles and highlighted text position and visibility.
1967 if( mSelectionHandleOne && mSelectionHandleTwo )
1969 Vector3 altPositionOne; // Alternate (i.e. opposite direction) cursor position.
1970 bool altPositionValidOne; // Alternate cursor validity flag.
1971 bool directionRTLOne; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1972 Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition, directionRTLOne, altPositionOne, altPositionValidOne );
1974 Vector3 altPositionTwo; // Alternate (i.e. opposite direction) cursor position.
1975 bool altPositionValidTwo; // Alternate cursor validity flag.
1976 bool directionRTLTwo; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1977 Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition, directionRTLTwo, altPositionTwo, altPositionValidTwo );
1979 // VCC TODO: This method is a hack for one line.
1980 ChooseRtlSelectionHandlePosition( cursorPositionOne,
1982 altPositionValidOne,
1983 altPositionValidTwo,
1987 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1988 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1989 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1990 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1992 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1993 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1994 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1995 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1997 if( mHighlightMeshActor )
1999 mHighlightMeshActor.SetVisible( true );
2005 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
2007 // Scroll the text to make the cursor visible.
2008 const Size cursorSize( CURSOR_THICKNESS,
2009 GetRowRectFromCharacterPosition( mCursorPosition ).height );
2011 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
2013 const Vector3& controlSize = GetControlSize();
2015 // Calculates the new scroll position.
2016 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
2017 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
2019 scrollOffset.x += cursorPosition.x;
2022 if( cursorPosition.y - cursorSize.height < 0.f || cursorPosition.y > controlSize.height )
2024 scrollOffset.y += cursorPosition.y;
2027 // Sets the new scroll position.
2028 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
2029 SetScrollPosition( scrollOffset );
2032 void TextInput::StartScrollTimer()
2036 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
2037 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
2040 if( !mScrollTimer.IsRunning() )
2042 mScrollTimer.Start();
2046 void TextInput::StopScrollTimer()
2050 mScrollTimer.Stop();
2054 bool TextInput::OnScrollTimerTick()
2056 // TODO: need to set the new style accordingly the new handle position.
2058 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
2060 // nothing to do if all handles are invisible or doesn't exist.
2066 // Choose between the grab handle or the selection handles.
2067 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
2068 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
2069 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
2071 std::size_t newCursorPosition = 0;
2072 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
2074 // Whether the handle's position is different of the previous one and in the case of the selection handle,
2075 // the new selection handle's position needs to be different of the other one.
2076 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
2077 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
2078 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
2080 if( differentSelectionHandles )
2082 handlePosition = newCursorPosition;
2084 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2086 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
2088 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
2089 scrollPosition += scrollDelta;
2090 SetScrollPosition( scrollPosition );
2092 if( mDisplayedTextView.IsScrollPositionTrimmed() )
2097 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
2100 actualHandlePosition.x += mScrollDisplacement.x;
2101 actualHandlePosition.y += mScrollDisplacement.y;
2106 // Public Internal Methods (public for testing purpose)
2108 void TextInput::SetUpTouchEvents()
2110 if ( !mTapDetector )
2112 mTapDetector = TapGestureDetector::New();
2113 // Attach the actors and connect the signal
2114 mTapDetector.Attach(Self());
2116 // As contains children which may register for tap the default control detector is not used.
2117 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2120 if ( !mDoubleTapDetector )
2122 mDoubleTapDetector = TapGestureDetector::New();
2123 mDoubleTapDetector.SetTapsRequired( 2 );
2124 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2126 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2127 // so that we do not, unnecessarily, have a double tap request all the time
2130 if ( !mPanGestureDetector )
2132 mPanGestureDetector = PanGestureDetector::New();
2133 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2136 if ( !mLongPressDetector )
2138 mLongPressDetector = LongPressGestureDetector::New();
2139 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2140 mLongPressDetector.Attach(Self());
2144 void TextInput::CreateTextViewActor()
2146 mDisplayedTextView = Toolkit::TextView::New();
2147 mDisplayedTextView.SetName( "DisplayedTextView ");
2148 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2149 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2150 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2151 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2152 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2153 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2154 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2155 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2156 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2157 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2159 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2161 Self().Add( mDisplayedTextView );
2164 // Start a timer to initiate, used by the cursor to blink.
2165 void TextInput::StartCursorBlinkTimer()
2167 if ( !mCursorBlinkTimer )
2169 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2170 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2173 if ( !mCursorBlinkTimer.IsRunning() )
2175 mCursorBlinkTimer.Start();
2179 // Start a timer to initiate, used by the cursor to blink.
2180 void TextInput::StopCursorBlinkTimer()
2182 if ( mCursorBlinkTimer )
2184 mCursorBlinkTimer.Stop();
2188 void TextInput::StartEditMode()
2190 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2192 if(!mEditModeActive)
2197 if ( mDoubleTapDetector )
2199 mDoubleTapDetector.Attach( Self() );
2203 void TextInput::EndEditMode()
2205 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2207 ClearKeyInputFocus();
2209 if ( mDoubleTapDetector )
2211 mDoubleTapDetector.Detach( Self() );
2215 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2217 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2219 mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2221 style.SetUnderline( true );
2222 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2226 void TextInput::RemovePreEditStyle()
2228 if ( !mUnderlinedPriorToPreEdit )
2231 style.SetUnderline( false );
2232 SetActiveStyle( style, TextStyle::UNDERLINE );
2236 // IMF related methods
2239 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2241 bool update( false );
2242 bool preeditResetRequired ( false );
2244 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2246 HidePopup(); // If Pop-up shown then hides it as editing text.
2249 switch ( imfEvent.eventName )
2251 case ImfManager::PREEDIT:
2253 mIgnoreFirstCommitFlag = false;
2255 // 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
2256 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2258 // replaces highlighted text with new character
2259 DeleteHighlightedText( false );
2262 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2264 if( IsScrollEnabled() )
2266 // Calculates the new cursor position (in actor coordinates)
2267 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2268 ScrollTextViewToMakeCursorVisible( cursorPosition );
2275 case ImfManager::COMMIT:
2277 if( mIgnoreFirstCommitFlag )
2279 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2280 mIgnoreFirstCommitFlag = false;
2284 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2286 // 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
2287 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2289 // replaces highlighted text with new character
2290 DeleteHighlightedText( false );
2293 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2294 // not needed, one such scenario is when the pre-edit word is too long to fit.
2295 if ( !mIgnoreCommitFlag )
2297 update = CommitReceived( imfEvent.predictiveString );
2301 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2307 if( IsScrollEnabled() )
2309 // Calculates the new cursor position (in actor coordinates)
2310 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2312 ScrollTextViewToMakeCursorVisible( cursorPosition );
2317 case ImfManager::DELETESURROUNDING:
2319 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2320 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2322 mPreEditFlag = false;
2324 std::size_t toDelete = 0;
2325 std::size_t numberOfCharacters = 0;
2327 if( mHighlightMeshActor )
2329 // delete highlighted text.
2330 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2331 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2335 if( static_cast<std::size_t>(std::abs( imfEvent.cursorOffset )) < mCursorPosition )
2337 toDelete = mCursorPosition + imfEvent.cursorOffset;
2339 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2341 numberOfCharacters = mStyledText.size() - toDelete;
2345 numberOfCharacters = imfEvent.numberOfChars;
2348 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2349 DeleteRange( toDelete, numberOfCharacters );
2351 mCursorPosition = toDelete;
2352 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2356 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2359 case ImfManager::GETSURROUNDING:
2361 // 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
2362 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2363 if (! ( mHighlightMeshActor || mSelectingText ) )
2365 std::string text( GetText() );
2366 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2368 imfManager.SetCursorPosition( mCursorPosition );
2369 imfManager.SetSurroundingText( text );
2372 if( 0 != mNumberOfSurroundingCharactersDeleted )
2374 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2375 mNumberOfSurroundingCharactersDeleted = 0;
2377 if( mStyledText.empty() )
2379 ShowPlaceholderText( mStyledPlaceHolderText );
2384 case ImfManager::VOID:
2386 DALI_ASSERT_DEBUG( false );
2390 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2392 return callbackData;
2395 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2397 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2399 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2400 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2402 bool preeditResetRequest ( false );
2404 if( mPreEditFlag ) // Already in pre-edit state.
2406 if( mStyledText.size() >= mMaxStringLength )
2408 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2409 // Cannot fit these characters into field, clear pre-edit.
2410 if ( !mUnderlinedPriorToPreEdit )
2413 style.SetUnderline( false );
2414 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2416 mIgnoreCommitFlag = true;
2417 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2418 mPreEditFlag = false;
2419 EmitMaxInputCharactersReachedSignal();
2423 // delete existing pre-edit string
2424 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2426 // Store new pre-edit string
2427 mPreEditString.SetText( keyString );
2429 if ( keyString.empty() )
2431 mPreEditFlag = false;
2432 mCursorPosition = mPreEditStartPosition;
2434 if( mStyledText.empty() )
2436 ShowPlaceholderText( mStyledPlaceHolderText );
2440 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2443 GetTextLayoutInfo();
2448 // Insert new pre-edit string. InsertAt updates the size and position table.
2449 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2450 // 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.
2451 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2452 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2453 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2456 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2460 else // mPreEditFlag not set
2462 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2464 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2465 // new pre-edit so move into pre-edit state by setting flag
2466 mPreEditFlag = true;
2467 mPreEditString.SetText( keyString ); // store new pre-edit string
2468 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2469 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2470 // 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.
2471 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2472 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2473 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2474 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2480 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2484 return preeditResetRequest;
2487 bool TextInput::CommitReceived(const std::string& keyString )
2489 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2490 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2492 bool update( false );
2494 RemovePreEditStyle();
2496 const std::size_t styledTextSize( mStyledText.size() );
2497 if( styledTextSize >= mMaxStringLength )
2499 // Cannot fit these characters into field, clear pre-edit.
2502 mIgnoreCommitFlag = true;
2503 mPreEditFlag = false;
2505 EmitMaxInputCharactersReachedSignal();
2511 // delete existing pre-edit string
2512 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2513 mPreEditFlag = false;
2515 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2516 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2518 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2520 // No need to update cursor position as Cursor location given by touch.
2521 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2522 mPreserveCursorPosition = false;
2526 // Cursor not set by touch so needs to be re-positioned to input more text
2527 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2529 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2530 if ( mCommitByKeyInput )
2532 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2533 mCommitByKeyInput = false;
2539 if ( mSelectTextOnCommit )
2541 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2546 else // mPreEditFlag not set
2548 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2550 if( mStyledText.empty() && mPlaceHolderSet )
2552 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2553 mDisplayedTextView.SetText( "" );
2554 mNumberOfSurroundingCharactersDeleted = 0;
2555 mPlaceHolderSet = false;
2557 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2559 mNumberOfSurroundingCharactersDeleted = 0;
2564 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2569 mSelectTextOnCommit = false;
2571 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2572 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2577 // End of IMF related methods
2579 std::size_t TextInput::DeletePreEdit()
2581 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2583 DALI_ASSERT_DEBUG( mPreEditFlag );
2585 const std::size_t preEditStringLength = mPreEditString.GetLength();
2586 const std::size_t styledTextSize = mStyledText.size();
2588 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2590 // Prevents erase items outside mStyledText bounds.
2591 if( mPreEditStartPosition > styledTextSize )
2593 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2594 mPreEditStartPosition = styledTextSize;
2597 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2599 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2600 endPosition = styledTextSize;
2603 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2605 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2606 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2608 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2610 return preEditStringLength;
2613 void TextInput::PreEditReset( bool preserveCursorPosition )
2615 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2616 preserveCursorPosition, mCursorPosition);
2618 // 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.
2619 mPreserveCursorPosition = preserveCursorPosition;
2621 // Reset incase we are in a pre-edit state.
2622 ImfManager imfManager = ImfManager::Get();
2625 imfManager.Reset(); // Will trigger a commit message
2629 void TextInput::CursorUpdate()
2633 ImfManager imfManager = ImfManager::Get();
2636 std::string text( GetText() );
2637 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2638 imfManager.SetCursorPosition ( mCursorPosition );
2639 imfManager.NotifyCursorPosition();
2643 /* Delete highlighted characters redisplay*/
2644 void TextInput::DeleteHighlightedText( bool inheritStyle )
2646 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2648 if( mHighlightMeshActor )
2650 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2652 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2653 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2655 // Get the styled text of the characters to be deleted as it may be needed if
2656 // the "exceed the text-input's boundaries" option is disabled.
2657 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2659 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2661 mStyledText.erase( start, end ); // erase range of characters
2663 // Remove text from TextView and update place holder text if required
2665 // Set the placeholder text only if the styled text is empty.
2666 if( mStyledText.empty() )
2668 ShowPlaceholderText( mStyledPlaceHolderText );
2672 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2674 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2676 // It may happen than after removing a white space or a new line character,
2677 // two words merge, this new word could be big enough to not fit in its
2678 // current line, so moved to the next one, and make some part of the text to
2679 // exceed the text-input's boundary.
2680 if( !mExceedEnabled )
2682 // Get the new text layout after removing some characters.
2683 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2685 // Get text-input's size.
2686 const Vector3& size = GetControlSize();
2688 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2689 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2691 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2693 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2694 styledCharactersToDelete.begin(),
2695 styledCharactersToDelete.end() );
2699 GetTextLayoutInfo();
2707 const TextStyle oldInputStyle( mInputStyle );
2709 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2711 if( oldInputStyle != mInputStyle )
2713 // Updates the line height accordingly with the input style.
2716 EmitStyleChangedSignal();
2722 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2724 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2725 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2727 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2730 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2732 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2733 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2735 mStyledText.erase(itStart, itEnd);
2737 // update the selection handles if they are visible.
2738 if( mHighlightMeshActor )
2740 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2741 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2743 if( minHandle >= start + ncharacters )
2745 minHandle -= ncharacters;
2747 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2752 if( maxHandle >= start + ncharacters )
2754 maxHandle -= ncharacters;
2756 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2762 // 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.
2765 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2767 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2768 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2769 // Mean we do not re-draw the text more than we have too.
2772 /* Delete character at current cursor position and redisplay*/
2773 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2775 // Ensure positionToDelete is not out of bounds.
2776 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2777 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2778 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2780 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2783 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2785 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2787 // Get the styled text of the character to be deleted as it may be needed if
2788 // the "exceed the text-input's boundaries" option is disabled.
2789 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2791 mStyledText.erase(it); // erase the character left of positionToDelete
2793 if( mStyledText.empty() )
2795 ShowPlaceholderText( mStyledPlaceHolderText );
2799 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2801 const Character characterToDelete = styledCharacterToDelete.mText[0];
2803 // It may happen than after removing a white space or a new line character,
2804 // two words merge, this new word could be big enough to not fit in its
2805 // current line, so moved to the next one, and make some part of the text to
2806 // exceed the text-input's boundary.
2807 if( !mExceedEnabled )
2809 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2811 // Get the new text layout after removing one character.
2812 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2814 // Get text-input's size.
2815 const Vector3& size = GetControlSize();
2817 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2818 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2820 MarkupProcessor::StyledTextArray array;
2821 array.push_back( styledCharacterToDelete );
2822 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2824 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2829 GetTextLayoutInfo();
2831 ShowGrabHandleAndSetVisibility( false );
2833 mCursorPosition = positionToDelete -1;
2835 const TextStyle oldInputStyle( mInputStyle );
2837 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2839 if( oldInputStyle != mInputStyle )
2841 // Updates the line height accordingly with the input style.
2844 EmitStyleChangedSignal();
2849 /*Insert new character into the string and (optionally) redisplay text-input*/
2850 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2852 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2854 // Ensure insertionPosition is not out of bounds.
2855 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2857 bool textExceedsMaximunNumberOfCharacters = false;
2858 bool textExceedsBoundary = false;
2859 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2861 ShowGrabHandleAndSetVisibility( false );
2863 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2867 mIgnoreCommitFlag = true;
2868 mPreEditFlag = false;
2869 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2870 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2873 if( textExceedsMaximunNumberOfCharacters )
2875 EmitMaxInputCharactersReachedSignal();
2878 if( textExceedsBoundary )
2880 EmitInputTextExceedsBoundariesSignal();
2881 PreEditReset( false );
2885 return insertedStringLength;
2888 ImageActor TextInput::CreateCursor( const Vector4& color)
2891 cursor = CreateSolidColorActor(color);
2892 cursor.SetName( "Cursor" );
2894 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2895 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_LEFT);
2896 cursor.SetVisible(false);
2901 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2903 // As cursor is not moving due to grab handle, handle should be hidden.
2904 ShowGrabHandleAndSetVisibility( false );
2906 bool cursorPositionChanged = false;
2909 if ( mCursorPosition >= places )
2911 mCursorPosition = mCursorPosition - places;
2912 cursorPositionChanged = true;
2917 if ((mCursorPosition + places) <= mStyledText.size())
2919 mCursorPosition = mCursorPosition + places;
2920 cursorPositionChanged = true;
2924 if( cursorPositionChanged )
2926 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2928 const TextStyle oldInputStyle( mInputStyle );
2929 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2933 if( oldInputStyle != mInputStyle )
2935 // Updates the line height accordingly with the input style.
2938 EmitStyleChangedSignal();
2941 ImfManager imfManager = ImfManager::Get();
2944 imfManager.SetCursorPosition ( mCursorPosition );
2945 imfManager.NotifyCursorPosition();
2950 void TextInput::DrawCursor()
2952 const Size rowRect = GetRowRectFromCharacterPosition( mCursorPosition );
2954 // Get height of cursor and set its size
2955 Size size( CURSOR_THICKNESS, 0.0f );
2956 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
2958 size.height = rowRect.height;
2962 // Measure Font so know how big text will be if no initial text to measure.
2963 size.height = mLineHeight;
2966 mCursor.SetSize(size);
2968 // If the character is italic then the cursor also tilts.
2969 mCursor.SetRotation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2971 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2973 if( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2975 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2976 bool altPositionValid; // Alternate cursor validity flag.
2977 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2978 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2980 SetAltCursorEnabled( altPositionValid );
2982 if( !altPositionValid )
2984 mCursor.SetPosition( position + UI_OFFSET );
2988 size.height *= 0.5f;
2989 mCursor.SetSize(size);
2990 mCursor.SetPosition( position + UI_OFFSET - Vector3( 0.0f, directionRTL ? 0.0f : size.height, 0.0f ) );
2992 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2993 size.height = rowRect.height * 0.5f;
2994 mCursorRTL.SetSize(size);
2995 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3( 0.0f, directionRTL ? size.height : 0.0f, 0.0f ) );
2998 if( IsScrollEnabled() )
3000 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
3001 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
3006 void TextInput::SetAltCursorEnabled( bool enabled )
3008 mCursorRTLEnabled = enabled;
3009 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
3012 void TextInput::SetCursorVisibility( bool visible )
3014 mCursorVisibility = visible;
3015 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
3016 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
3019 void TextInput::CreateGrabHandle( Dali::Image image )
3025 mGrabHandleImage = ResourceImage::New(DEFAULT_GRAB_HANDLE);
3029 mGrabHandleImage = image;
3032 mGrabHandle = ImageActor::New(mGrabHandleImage);
3033 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
3034 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
3036 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
3038 ShowGrabHandleAndSetVisibility( false );
3040 CreateGrabArea( mGrabHandle );
3042 mActiveLayer.Add(mGrabHandle);
3046 void TextInput::CreateGrabArea( Actor& parent )
3048 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3049 mGrabArea.SetName( "GrabArea" );
3050 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3051 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3052 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
3053 mTapDetector.Attach( mGrabArea );
3054 mPanGestureDetector.Attach( mGrabArea );
3055 mLongPressDetector.Attach( mGrabArea );
3057 parent.Add(mGrabArea);
3060 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
3062 Vector3 actualHandlePosition;
3066 mActualGrabHandlePosition.x += displacement.x;
3067 mActualGrabHandlePosition.y += displacement.y;
3069 // Grab handle should jump to the nearest character and take cursor with it
3070 std::size_t newCursorPosition = 0;
3071 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
3073 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
3074 bool altPositionValid; // Alternate cursor validity flag.
3075 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3076 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition, directionRTL, altPosition, altPositionValid );
3078 if( altPositionValid )
3080 // Check which of the positions is the closest.
3081 if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( actualHandlePosition.x - mActualGrabHandlePosition.x ) )
3083 actualHandlePosition = altPosition;
3087 bool handleVisible = true;
3089 if( IsScrollEnabled() )
3091 const Vector3 controlSize = GetControlSize();
3092 const Size cursorSize = GetRowRectFromCharacterPosition( newCursorPosition );
3093 // Scrolls the text if the handle is not in a visible position
3094 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3101 mCurrentHandlePosition = actualHandlePosition;
3102 mScrollDisplacement = Vector2::ZERO;
3106 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3108 mScrollDisplacement.x = -SCROLL_SPEED;
3110 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3112 mScrollDisplacement.x = SCROLL_SPEED;
3114 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3116 mScrollDisplacement.y = -SCROLL_SPEED;
3118 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3120 mScrollDisplacement.y = SCROLL_SPEED;
3126 if( handleVisible && // Only redraw cursor and do updates if position changed
3127 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3129 mCursorPosition = newCursorPosition;
3131 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3133 const TextStyle oldInputStyle( mInputStyle );
3135 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3137 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3139 if( oldInputStyle != mInputStyle )
3141 // Updates the line height accordingly with the input style.
3144 EmitStyleChangedSignal();
3149 return actualHandlePosition;
3152 void TextInput::ShowGrabHandle( bool visible )
3154 if ( IsGrabHandleEnabled() )
3158 mGrabHandle.SetVisible( mGrabHandleVisibility );
3160 StartMonitoringStageForTouch();
3164 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3166 mGrabHandleVisibility = visible;
3167 ShowGrabHandle( visible );
3170 // Callbacks connected to be Property notifications for Boundary checking.
3172 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3174 mIsSelectionHandleOneFlipped = true;
3175 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3176 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3179 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3181 mIsSelectionHandleOneFlipped = false;
3182 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3183 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3186 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3188 mIsSelectionHandleTwoFlipped = true;
3189 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3190 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3193 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3195 mIsSelectionHandleTwoFlipped = false;
3196 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3197 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3200 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3201 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3203 mSelectionHandleOne.SetOpacity(0.0f);
3206 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3208 mSelectionHandleOne.SetOpacity(1.0f);
3211 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3213 mSelectionHandleTwo.SetOpacity(0.0f);
3216 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3218 mSelectionHandleTwo.SetOpacity(1.0f);
3221 // End of Callbacks connected to be Property notifications for Boundary checking.
3223 void TextInput::SetUpHandlePropertyNotifications()
3225 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3227 Vector3 handlesize = GetSelectionHandleSize();
3229 // Exceeding horizontal boundary
3230 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3231 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3233 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3234 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3236 // Within horizontal boundary
3237 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3238 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3240 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3241 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3243 // Exceeding vertical boundary
3244 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3245 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3246 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3247 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3249 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3250 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3251 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3252 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3254 // Within vertical boundary
3255 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3256 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3257 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3258 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3260 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3261 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3262 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3263 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3266 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3268 mSelectionHandleOnePosition = start;
3269 mSelectionHandleTwoPosition = end;
3271 if ( !mSelectionHandleOne )
3273 // create normal and pressed images
3274 mSelectionHandleOneImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE );
3275 mSelectionHandleOneImagePressed = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3277 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3278 mSelectionHandleOne.SetName("SelectionHandleOne");
3279 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3280 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3281 mIsSelectionHandleOneFlipped = false;
3282 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3284 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3285 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3287 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3288 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3290 mTapDetector.Attach( mHandleOneGrabArea );
3291 mPanGestureDetector.Attach( mHandleOneGrabArea );
3293 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3295 mSelectionHandleOne.Add( mHandleOneGrabArea );
3296 mActiveLayer.Add( mSelectionHandleOne );
3299 if ( !mSelectionHandleTwo )
3301 // create normal and pressed images
3302 mSelectionHandleTwoImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO );
3303 mSelectionHandleTwoImagePressed = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3305 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3306 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3307 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3308 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3309 mIsSelectionHandleTwoFlipped = false;
3310 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3312 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3313 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3314 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3315 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3317 mTapDetector.Attach( mHandleTwoGrabArea );
3318 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3320 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3322 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3324 mActiveLayer.Add( mSelectionHandleTwo );
3327 SetUpHandlePropertyNotifications();
3329 // update table as text may have changed.
3330 GetTextLayoutInfo();
3332 Vector3 altPositionOne; // Alternate (i.e. opposite direction) cursor position.
3333 bool altPositionValidOne; // Alternate cursor validity flag.
3334 bool directionRTLOne; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3335 Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition, directionRTLOne, altPositionOne, altPositionValidOne );
3337 Vector3 altPositionTwo; // Alternate (i.e. opposite direction) cursor position.
3338 bool altPositionValidTwo; // Alternate cursor validity flag.
3339 bool directionRTLTwo; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3340 Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition, directionRTLTwo, altPositionTwo, altPositionValidTwo );
3342 // VCC TODO: This method is a hack for one line.
3343 ChooseRtlSelectionHandlePosition( cursorPositionOne,
3345 altPositionValidOne,
3346 altPositionValidTwo,
3350 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3351 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3353 // Calculates and set the visibility if the scroll mode is enabled.
3354 bool isSelectionHandleOneVisible = true;
3355 bool isSelectionHandleTwoVisible = true;
3356 if( IsScrollEnabled() )
3358 const Vector3& controlSize( GetControlSize() );
3359 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3360 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3361 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3362 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3365 CreateHighlight(); // function will only create highlight if not already created.
3368 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3370 Vector3 actualHandlePosition;
3372 if ( mSelectionHandleOne && mSelectionHandleTwo )
3374 const Vector3& controlSize = GetControlSize();
3376 Size cursorSize( CURSOR_THICKNESS, 0.f );
3378 // Get a reference of the wanted selection handle (handle one or two).
3379 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3381 // Get a reference for the current position of the handle and a copy of its pair
3382 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3383 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3385 // Get a handle of the selection handle actor
3386 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3388 // Selection handles should jump to the nearest character
3389 std::size_t newHandlePosition = 0;
3390 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3392 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
3393 bool altPositionValid; // Alternate cursor validity flag.
3394 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3395 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition, directionRTL, altPosition, altPositionValid );
3396 if( altPositionValid )
3398 // Check which of the positions is the closest.
3399 if( fabsf( altPosition.x - actualSelectionHandlePosition.x ) < fabsf( actualHandlePosition.x - actualSelectionHandlePosition.x ) )
3401 actualHandlePosition = altPosition;
3405 bool handleVisible = true;
3407 if( IsScrollEnabled() )
3409 mCurrentSelectionId = handleId;
3411 cursorSize.height = GetRowRectFromCharacterPosition( newHandlePosition ).height;
3412 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3413 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3420 mCurrentSelectionHandlePosition = actualHandlePosition;
3421 mScrollDisplacement = Vector2::ZERO;
3425 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3427 mScrollDisplacement.x = -SCROLL_SPEED;
3429 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3431 mScrollDisplacement.x = SCROLL_SPEED;
3433 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3435 mScrollDisplacement.y = -SCROLL_SPEED;
3437 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3439 mScrollDisplacement.y = SCROLL_SPEED;
3445 if ( handleVisible && // Ensure the handle is visible.
3446 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3447 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3449 currentSelectionHandlePosition = newHandlePosition;
3451 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3452 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3456 if ( handleId == HandleOne )
3458 const TextStyle oldInputStyle( mInputStyle );
3460 // Set Active Style to that of first character in selection
3461 if( mSelectionHandleOnePosition < mStyledText.size() )
3463 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3466 if( oldInputStyle != mInputStyle )
3468 // Updates the line height accordingly with the input style.
3471 EmitStyleChangedSignal();
3477 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3480 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3482 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3483 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3485 if ( selectionHandleActor )
3487 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3488 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3489 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3491 if( IsScrollEnabled() )
3493 const Size cursorSize( CURSOR_THICKNESS,
3494 GetRowRectFromCharacterPosition( selectionHandlePosition ).height );
3495 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3497 GetControlSize() ) );
3502 void TextInput::GetVisualTextSelection( std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection )