2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali/dali.h>
20 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
21 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
22 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
23 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
24 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
26 #include <dali/integration-api/debug.h>
39 #if defined(DEBUG_ENABLED)
40 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
43 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
44 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
45 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
46 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
47 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
48 const Vector4 LIGHTBLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // Used for Selection highlight
50 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
51 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
52 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
53 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
54 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
56 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
57 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
58 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.1f ); ///< 1. Highlight rendered (z-offset).
59 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.2f ); ///< 2. Text rendered (z-offset).
60 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
62 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
63 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
64 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
65 const float TOP_HANDLE_TOP_OFFSET( 34.0f); ///< Offset between top handle and cutCopyPaste pop-up
66 const float BOTTOM_HANDLE_BOTTOM_OFFSET(34.0f); ///< Offset between bottom handle and cutCopyPaste pop-up
67 const float CURSOR_THICKNESS(4.0f);
68 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
69 const Vector4 DEFAULT_CURSOR_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
71 const std::string NEWLINE( "\n" );
73 const TextStyle DEFAULT_TEXT_STYLE;
75 const unsigned int SCROLL_TICK_INTERVAL = 50u;
76 const float SCROLL_THRESHOLD = 10.f;
77 const float SCROLL_SPEED = 15.f;
80 * Selection state enumeration (FSM)
84 SelectionNone, ///< Currently not encountered selected section.
85 SelectionStarted, ///< Encountered selected section
86 SelectionFinished ///< Finished selected section
89 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
91 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
95 if( ( *it ).mIsVisible )
97 return --cursorPosition;
106 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
108 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
110 if( ( *it ).mIsVisible )
112 return cursorPosition;
118 return cursorPosition;
122 * Whether the given position plus the cursor size offset is inside the given boundary.
124 * @param[in] position The given position.
125 * @param[in] cursorSize The cursor size.
126 * @param[in] controlSize The given boundary.
128 * @return whether the given position is inside the given boundary.
130 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
132 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
133 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
134 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
135 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
139 * Splits a text in two halves.
141 * If the text's number of characters is odd, firstHalf has one more character.
143 * @param[in] text The text to be split.
144 * @param[out] firstHalf The first half of the text.
145 * @param[out] secondHalf The second half of the text.
147 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
148 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
149 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
154 const std::size_t textLength = text.size();
155 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
157 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
158 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
161 } // end of namespace
169 const Property::Index TextInput::HIGHLIGHT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX;
170 const Property::Index TextInput::CUT_AND_PASTE_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+1;
171 const Property::Index TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+2;
172 const Property::Index TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+3;
173 const Property::Index TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+4;
174 const Property::Index TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+5;
175 const Property::Index TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+6;
176 const Property::Index TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+7;
177 const Property::Index TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+8;
178 const Property::Index TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+9;
179 const Property::Index TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+10;
180 const Property::Index TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+11;
181 const Property::Index TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+12;
182 const Property::Index TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+13;
183 const Property::Index TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+14;
184 const Property::Index TextInput::CURSOR_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+15;
195 return Toolkit::TextInput::New();
198 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
200 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
201 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
202 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
203 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
204 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
205 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
209 PropertyRegistration property1( typeRegistration, "highlight-color", Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
210 PropertyRegistration property2( typeRegistration, "cut-and-paste-bg-color", Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
211 PropertyRegistration property3( typeRegistration, "cut-and-paste-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
212 PropertyRegistration property4( typeRegistration, "cut-and-paste-icon-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
213 PropertyRegistration property5( typeRegistration, "cut-and-paste-icon-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
214 PropertyRegistration property6( typeRegistration, "cut-and-paste-text-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
215 PropertyRegistration property7( typeRegistration, "cut-and-paste-text-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
216 PropertyRegistration property8( typeRegistration, "cut-and-paste-border-color", Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
217 PropertyRegistration property9( typeRegistration, "cut-button-position-priority", Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
218 PropertyRegistration property10( typeRegistration, "copy-button-position-priority", Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
219 PropertyRegistration property11( typeRegistration, "paste-button-position-priority", Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
220 PropertyRegistration property12( typeRegistration, "select-button-position-priority", Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
221 PropertyRegistration property13( typeRegistration, "select-all-button-position-priority", Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
222 PropertyRegistration property14( typeRegistration, "clipboard-button-position-priority", Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
223 PropertyRegistration property15( typeRegistration, "popup-offset-from-text", Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
224 PropertyRegistration property16( typeRegistration, "cursor-color", Toolkit::TextInput::CURSOR_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
227 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
229 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
231 QuadCoordinates quad(x1, y1, x2, y2);
232 mQuadList.push_back( quad );
235 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
237 for(std::size_t i = 0;i < mQuadList.size(); i++)
239 QuadCoordinates& quad = mQuadList[i];
241 quad.min.Clamp(min, max);
242 quad.max.Clamp(min, max);
246 // [TextInput] ////////////////////////////////////////////////////////////////
248 Dali::Toolkit::TextInput TextInput::New()
250 // Create the implementation
251 TextInputPtr textInput(new TextInput());
252 // Pass ownership to CustomActor via derived handle
253 Dali::Toolkit::TextInput handle(*textInput);
254 handle.SetName( "TextInput");
256 textInput->Initialize();
260 TextInput::TextInput()
261 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
266 mDisplayedTextView(),
267 mStyledPlaceHolderText(),
268 mMaxStringLength( DEFAULT_MAX_SIZE ),
269 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
270 mCursorPosition( 0 ),
271 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
272 mIsSelectionHandleOneFlipped( false ),
273 mIsSelectionHandleTwoFlipped( false ),
274 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
275 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
276 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
277 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
278 mSelectionHandleOnePosition( 0 ),
279 mSelectionHandleTwoPosition( 0 ),
281 mPreEditStartPosition( 0 ),
282 mPreEditLength ( 0 ),
283 mNumberOfSurroundingCharactersDeleted( 0 ),
284 mTouchStartTime( 0 ),
286 mCurrentCopySelecton(),
289 mScrollDisplacement(),
290 mCurrentHandlePosition(),
291 mCurrentSelectionId(),
292 mCurrentSelectionHandlePosition(),
293 mRequestedSelection( 0, 0 ),
294 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
295 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
297 mMaterialColor( LIGHTBLUE ),
298 mPopupOffsetFromText ( Vector4( 0.0f, TOP_HANDLE_TOP_OFFSET, 0.0f, BOTTOM_HANDLE_BOTTOM_OFFSET ) ),
299 mOverrideAutomaticAlignment( false ),
300 mCursorRTLEnabled( false ),
301 mClosestCursorPositionEOL ( false ),
302 mCursorBlinkStatus( true ),
303 mCursorVisibility( false ),
304 mGrabHandleVisibility( false ),
305 mIsCursorInScrollArea( true ),
306 mIsGrabHandleInScrollArea( true ),
307 mEditModeActive( false ),
308 mEditOnTouch( true ),
309 mTextSelection( true ),
310 mExceedEnabled( true ),
311 mGrabHandleEnabled( true ),
312 mIsSelectionHandleFlipEnabled( true ),
313 mPreEditFlag( false ),
314 mIgnoreCommitFlag( false ),
315 mIgnoreFirstCommitFlag( false ),
316 mSelectingText( false ),
317 mPreserveCursorPosition( false ),
318 mSelectTextOnCommit( false ),
319 mUnderlinedPriorToPreEdit ( false ),
320 mCommitByKeyInput( false ),
321 mPlaceHolderSet( false ),
322 mMarkUpEnabled( false )
324 // Updates the line height accordingly with the input style.
328 TextInput::~TextInput()
330 StopCursorBlinkTimer();
335 std::string TextInput::GetText() const
339 // Return text-view's text only if the text-input's text is not empty
340 // in order to not to return the placeholder text.
341 if( !mStyledText.empty() )
343 text = mDisplayedTextView.GetText();
349 std::string TextInput::GetMarkupText() const
351 std::string markupString;
352 MarkupProcessor::GetMarkupString( mStyledText, markupString );
357 void TextInput::ShowPlaceholderText( const MarkupProcessor::StyledTextArray& stylePlaceHolderText )
359 mDisplayedTextView.SetText( stylePlaceHolderText );
360 mPlaceHolderSet = true;
361 mDisplayedTextView.SetScrollPosition( Vector2( 0.0f,0.0f ) );
364 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
366 // Get the placeholder styled text array from the markup string.
367 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
368 if( mStyledText.empty() )
370 ShowPlaceholderText( mStyledPlaceHolderText );
374 std::string TextInput::GetPlaceholderText()
376 // Traverses the styled placeholder array getting only the text.
377 // Note that for some languages a 'character' could be represented by more than one 'char'
379 std::string placeholderText;
380 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
382 placeholderText.append( (*it).mText.GetText() );
385 return placeholderText ;
388 void TextInput::SetInitialText(const std::string& initialText)
390 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
392 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
394 mPreEditFlag = false;
395 mIgnoreCommitFlag = true;
398 SetText( initialText );
399 PreEditReset( false ); // Reset keyboard as text changed
402 void TextInput::SetText(const std::string& initialText)
404 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
406 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
408 if( mStyledText.empty() )
410 ShowPlaceholderText( mStyledPlaceHolderText );
414 mDisplayedTextView.SetText( mStyledText );
415 mPlaceHolderSet = false;
420 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
422 ImfManager imfManager = ImfManager::Get();
425 imfManager.SetCursorPosition( mCursorPosition );
426 imfManager.SetSurroundingText( initialText );
427 imfManager.NotifyCursorPosition();
430 if( IsScrollEnabled() )
432 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
435 ShowGrabHandleAndSetVisibility( false );
444 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
446 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
448 mDisplayedTextView.SetText( styleText );
449 mPlaceHolderSet = false;
451 // If text alignment hasn't been manually set by application developer, then we
452 // automatically determine the alignment based on the content of the text i.e. what
453 // language the text begins with.
454 // TODO: This should determine different alignments for each line (broken by '\n') of text.
455 if(!mOverrideAutomaticAlignment)
457 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
458 bool leftToRight(true);
460 if( !styleText.empty() )
462 bool breakOut(false);
464 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
466 const Text& text = textIter->mText;
468 for( std::size_t i = 0; i < text.GetLength(); ++i )
470 Character character( text[i] );
471 if( character.GetCharacterDirection() != Character::Neutral )
473 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
481 // Based on this direction, either left or right align text if not manually set by application developer.
482 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
483 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
484 Toolkit::Alignment::VerticalTop ) );
485 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
491 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
493 mMaxStringLength = maxChars;
496 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
498 DALI_ASSERT_DEBUG( maxLines > 0 )
502 mNumberOflinesLimit = maxLines;
506 std::size_t TextInput::GetNumberOfLinesLimit() const
508 return mNumberOflinesLimit;
511 std::size_t TextInput::GetNumberOfCharacters() const
513 return mStyledText.size();
517 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
519 mMaterialColor = color;
520 if ( mCustomMaterial )
522 mCustomMaterial.SetDiffuseColor( mMaterialColor );
523 mMeshData.SetMaterial( mCustomMaterial );
527 const Vector4& TextInput::GetMaterialDiffuseColor() const
529 return mMaterialColor;
534 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
536 return mInputStartedSignalV2;
539 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
541 return mInputFinishedSignalV2;
544 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
546 return mCutAndPasteToolBarDisplayedV2;
549 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
551 return mStyleChangedSignalV2;
554 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
556 return mTextModifiedSignal;
559 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
561 return mMaxInputCharactersReachedSignalV2;
564 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
566 return mInputTextExceedBoundariesSignalV2;
569 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
571 Dali::BaseHandle handle( object );
573 bool connected( true );
574 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
576 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
578 textInput.InputStartedSignal().Connect( tracker, functor );
580 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
582 textInput.InputFinishedSignal().Connect( tracker, functor );
584 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
586 textInput.StyleChangedSignal().Connect( tracker, functor );
588 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
590 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
592 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
594 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
598 // signalName does not match any signal
605 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
609 // update line height before calculate the actual position.
614 if( setCursorOnTouchPoint )
616 // Sets the cursor position for the given touch point.
617 ReturnClosestIndex( touchPoint, mCursorPosition );
619 // Creates the grab handle.
620 if( IsGrabHandleEnabled() )
622 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
626 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
627 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
628 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
629 ShowGrabHandleAndSetVisibility( true );
631 // Scrolls the text-view if needed.
632 if( IsScrollEnabled() )
634 ScrollTextViewToMakeCursorVisible( cursorPosition );
640 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
652 bool TextInput::IsEditable() const
654 return mEditModeActive;
657 void TextInput::SetEditOnTouch( bool editOnTouch )
659 mEditOnTouch = editOnTouch;
662 bool TextInput::IsEditOnTouch() const
667 void TextInput::SetTextSelectable( bool textSelectable )
669 mTextSelection = textSelectable;
672 bool TextInput::IsTextSelectable() const
674 return mTextSelection;
677 bool TextInput::IsTextSelected() const
679 return mHighlightMeshActor;
682 void TextInput::DeSelectText()
689 void TextInput::SetGrabHandleImage(Dali::Image image )
693 CreateGrabHandle(image);
697 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
699 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
703 mCursor.SetImage( image );
704 mCursor.SetNinePatchBorder( border );
708 Vector3 TextInput::GetSelectionHandleSize()
710 return DEFAULT_SELECTION_HANDLE_SIZE;
713 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
715 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
719 mCursorRTL.SetImage( image);
720 mCursorRTL.SetNinePatchBorder( border );
724 void TextInput::EnableGrabHandle(bool toggle)
726 // enables grab handle with will in turn de-activate magnifier
727 mGrabHandleEnabled = toggle;
730 bool TextInput::IsGrabHandleEnabled()
732 // if false then magnifier will be shown instead.
733 return mGrabHandleEnabled;
736 void TextInput::EnableSelectionHandleFlip( bool toggle )
738 // Deprecated function. To be removed.
739 mIsSelectionHandleFlipEnabled = toggle;
742 bool TextInput::IsSelectionHandleFlipEnabled()
744 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
748 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
750 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
751 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
752 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
754 mSelectionHandleFlipMargin = margin;
757 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
759 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
760 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
762 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
763 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
765 const Vector4 boundary( originX,
767 originX + boundingRectangle.width,
768 originY + boundingRectangle.height );
770 mBoundingRectangleWorldCoordinates = boundary;
773 const Rect<float> TextInput::GetBoundingRectangle() const
775 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
777 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
778 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
780 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
785 const Vector4& TextInput::GetSelectionHandleFlipMargin()
787 return mSelectionHandleFlipMargin;
790 void TextInput::SetTextColor( const Vector4& color )
792 mDisplayedTextView.SetColor( color );
795 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
797 if( style != mInputStyle )
800 bool emitSignal = false;
802 // mask: modify style according to mask, if different emit signal.
803 const TextStyle oldInputStyle( mInputStyle );
805 // Copy the new style.
806 mInputStyle.Copy( style, mask );
808 // if style has changed, emit signal.
809 if( oldInputStyle != mInputStyle )
814 // Updates the line height accordingly with the input style.
817 // Changing font point size will require the cursor to be re-sized
822 EmitStyleChangedSignal();
827 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
829 if ( IsTextSelected() )
831 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
832 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
834 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
836 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
839 // Keeps the old style to be compared with the new one.
840 const TextStyle oldInputStyle( mInputStyle );
842 // Copy only those parameters from the style which are set in the mask.
843 mInputStyle.Copy( style, mask );
845 if( mInputStyle != oldInputStyle )
847 // Updates the line height accordingly with the input style.
850 EmitStyleChangedSignal();
855 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
857 if( !mStyledText.empty() )
859 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
863 TextStyle TextInput::GetStyleAtCursor() const
867 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
869 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
870 style = mStyledText.at( mCursorPosition-1 ).mStyle;
876 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
878 Dali::Font defaultFont = Dali::Font::New();
879 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
886 TextStyle TextInput::GetStyleAt( std::size_t position ) const
888 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
890 if( position >= mStyledText.size() )
892 position = mStyledText.size() - 1;
895 return mStyledText.at( position ).mStyle;
898 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
900 mDisplayedTextView.SetTextAlignment( align );
901 mOverrideAutomaticAlignment = true;
904 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
906 mDisplayedTextView.SetLineJustification( justification );
907 mOverrideAutomaticAlignment = true;
910 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
912 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
915 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
917 return mDisplayedTextView.GetFadeBoundary();
920 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
922 return mDisplayedTextView.GetTextAlignment();
925 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
927 mDisplayedTextView.SetMultilinePolicy( policy );
930 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
932 return mDisplayedTextView.GetMultilinePolicy();
935 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
937 mDisplayedTextView.SetWidthExceedPolicy( policy );
940 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
942 return mDisplayedTextView.GetWidthExceedPolicy();
945 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
947 mDisplayedTextView.SetHeightExceedPolicy( policy );
950 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
952 return mDisplayedTextView.GetHeightExceedPolicy();
955 void TextInput::SetExceedEnabled( bool enable )
957 mExceedEnabled = enable;
960 bool TextInput::GetExceedEnabled() const
962 return mExceedEnabled;
965 void TextInput::SetBackground(Dali::Image image )
967 // TODO Should add this function and add public api to match.
970 bool TextInput::OnTouchEvent(const TouchEvent& event)
975 bool TextInput::OnKeyEvent(const KeyEvent& event)
977 switch( event.state )
981 return OnKeyDownEvent(event);
987 return OnKeyUpEvent(event);
999 void TextInput::OnKeyInputFocusGained()
1001 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1003 mEditModeActive = true;
1005 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1007 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1009 // Updates the line height accordingly with the input style.
1012 // Connect the signals to use in text input.
1013 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1014 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1016 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1019 GetTextLayoutInfo();
1022 SetCursorVisibility( true );
1023 StartCursorBlinkTimer();
1025 Toolkit::TextInput handle( GetOwner() );
1026 mInputStartedSignalV2.Emit( handle );
1028 ImfManager imfManager = ImfManager::Get();
1032 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1034 // Notify that the text editing start.
1035 imfManager.Activate();
1037 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1038 imfManager.SetRestoreAferFocusLost( true );
1040 imfManager.SetCursorPosition( mCursorPosition );
1041 imfManager.NotifyCursorPosition();
1044 mClipboard = Clipboard::Get(); // Store handle to clipboard
1046 // Now in edit mode we can accept string to paste from clipboard
1047 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1050 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1054 void TextInput::OnKeyInputFocusLost()
1056 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1060 // If key input focus is lost, it removes the
1061 // underline from the last pre-edit text.
1062 RemovePreEditStyle();
1063 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1064 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1068 ImfManager imfManager = ImfManager::Get();
1071 // The text editing is finished. Therefore the imf manager don't have restore activation.
1072 imfManager.SetRestoreAferFocusLost( false );
1074 // Notify that the text editing finish.
1075 imfManager.Deactivate();
1077 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1079 // Disconnect signal used the text input.
1080 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1082 Toolkit::TextInput handle( GetOwner() );
1083 mInputFinishedSignalV2.Emit( handle );
1084 mEditModeActive = false;
1085 mPreEditFlag = false;
1087 SetCursorVisibility( false );
1088 StopCursorBlinkTimer();
1090 ShowGrabHandleAndSetVisibility( false );
1093 // No longer in edit mode so do not want to receive string from clipboard
1094 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1097 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1100 Clipboard clipboard = Clipboard::Get();
1103 clipboard.HideClipboard();
1107 void TextInput::OnControlStageConnection()
1109 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1111 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1113 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1117 void TextInput::CreateActiveLayer()
1119 Actor self = Self();
1120 mActiveLayer = Layer::New();
1121 mActiveLayer.SetName ( "ActiveLayerActor" );
1123 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1124 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1125 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1127 self.Add( mActiveLayer );
1128 mActiveLayer.RaiseToTop();
1131 void TextInput::OnInitialize()
1133 CreateTextViewActor();
1137 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1138 // different positions depending on language)
1139 mCursor = CreateCursor(DEFAULT_CURSOR_COLOR);
1140 mCursorRTL = CreateCursor(DEFAULT_CURSOR_COLOR);
1142 Actor self = Self();
1143 self.Add( mCursor );
1144 self.Add( mCursorRTL );
1146 mCursorVisibility = false;
1148 CreateActiveLayer(); // todo move this so layer only created when needed.
1150 // Assign names to image actors
1151 mCursor.SetName("mainCursor");
1152 mCursorRTL.SetName("rtlCursor");
1155 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1157 mDisplayedTextView.SetSize( targetSize );
1158 GetTextLayoutInfo();
1159 mActiveLayer.SetSize(targetSize);
1162 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1164 Relayout( mDisplayedTextView, size, container );
1165 Relayout( mPopupPanel.GetRootActor(), size, container );
1167 GetTextLayoutInfo();
1172 Vector3 TextInput::GetNaturalSize()
1174 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1176 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1178 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1179 naturalSize.height = mLineHeight;
1185 float TextInput::GetHeightForWidth( float width )
1187 float height = mDisplayedTextView.GetHeightForWidth( width );
1189 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1191 // If the height is zero, it means there is no text. Let's return the cursor height.
1192 height = mLineHeight;
1198 /*end of Virtual methods from parent*/
1200 // Private Internal methods
1202 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1204 switch (gesture.state)
1206 case Gesture::Started:
1207 // fall through so code not duplicated
1208 case Gesture::Continuing:
1210 if (actor == mGrabArea)
1212 SetCursorVisibility( true );
1213 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1214 MoveGrabHandle( gesture.displacement );
1215 HidePopup(); // Do not show popup whilst handle is moving
1217 else if (actor == mHandleOneGrabArea)
1219 // the displacement in PanGesture is affected by the actor's rotation.
1220 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1221 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1223 MoveSelectionHandle( HandleOne, gesture.displacement );
1225 mState = StateDraggingHandle;
1228 else if (actor == mHandleTwoGrabArea)
1230 // the displacement in PanGesture is affected by the actor's rotation.
1231 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1232 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1234 MoveSelectionHandle( HandleTwo, gesture.displacement );
1236 mState = StateDraggingHandle;
1242 case Gesture::Finished:
1244 // Revert back to non-pressed selection handle images
1245 if (actor == mGrabArea)
1247 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1248 SetCursorVisibility( true );
1249 SetUpPopupSelection();
1252 if (actor == mHandleOneGrabArea)
1254 // the displacement in PanGesture is affected by the actor's rotation.
1255 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1256 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1258 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1260 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1262 ShowPopupCutCopyPaste();
1264 if (actor == mHandleTwoGrabArea)
1266 // the displacement in PanGesture is affected by the actor's rotation.
1267 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1268 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1270 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1272 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1274 ShowPopupCutCopyPaste();
1283 // Stop the flashing animation so easy to see when moved.
1284 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1286 if (touch.GetPoint(0).state == TouchPoint::Down)
1288 SetCursorVisibility( true );
1289 StopCursorBlinkTimer();
1291 else if (touch.GetPoint(0).state == TouchPoint::Up)
1293 SetCursorVisibility( true );
1294 StartCursorBlinkTimer();
1299 // selection handle one
1300 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1302 if (touch.GetPoint(0).state == TouchPoint::Down)
1304 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1306 else if (touch.GetPoint(0).state == TouchPoint::Up)
1308 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1313 // selection handle two
1314 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1316 if (touch.GetPoint(0).state == TouchPoint::Down)
1318 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1320 else if (touch.GetPoint(0).state == TouchPoint::Up)
1322 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1327 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1329 // If text exists then select nearest word.
1330 if ( !mStyledText.empty())
1334 ShowGrabHandleAndSetVisibility( false );
1339 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1340 // converts the pre-edit word being displayed to a committed word.
1341 if ( !mUnderlinedPriorToPreEdit )
1344 style.SetUnderline( false );
1345 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1347 mPreEditFlag = false;
1348 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1349 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1350 PreEditReset( false );
1352 mCursorPosition = 0;
1354 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1355 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1357 std::size_t start = 0;
1358 std::size_t end = 0;
1359 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1361 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1363 ImfManager imfManager = ImfManager::Get();
1366 imfManager.SetCursorPosition ( mCursorPosition );
1367 imfManager.NotifyCursorPosition();
1370 if ( !mStyledText.at(end-1).mText[0].IsWhiteSpace() )
1372 SelectText( start, end );
1373 ShowPopupCutCopyPaste();
1377 RemoveHighlight( false ); // Remove highlight but do not auto hide popup
1378 HidePopup( false ); // Hide popup with setting to do auto show.
1379 SetUpPopupSelection( false ); // Set to false so if nearest word is whitespace it will not show cut button.
1383 else if ( mClipboard && mClipboard.NumberOfItems() )
1385 ShowPopupCutCopyPaste();
1388 // If no text and clipboard empty then do nothing
1391 // TODO: Change the function name to be more general.
1392 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1394 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1395 , (mEditOnTouch)?"true":"false"
1396 , (mEditModeActive)?"true":"false");
1398 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1403 if( mGrabArea == actor )
1405 if( mPopupPanel.GetState() == TextInputPopup::StateHidden || mPopupPanel.GetState() == TextInputPopup::StateHiding )
1407 SetUpPopupSelection();
1417 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1419 // Initially don't create the grab handle.
1420 bool createGrabHandle = false;
1422 if ( !mEditModeActive )
1424 // update line height before calculate the actual position.
1427 // Only start edit mode if TextInput configured to edit on touch
1430 // Set the initial cursor position in the tap point.
1431 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1437 // Show the keyboard if it was hidden.
1438 if (!VirtualKeyboard::IsVisible())
1440 VirtualKeyboard::Show();
1443 // Reset keyboard as tap event has occurred.
1444 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1445 PreEditReset( true );
1447 GetTextLayoutInfo();
1449 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1451 // 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.
1453 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1455 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1457 // Notify keyboard so it can 're-capture' word for predictive text.
1458 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1459 ImfManager imfManager = ImfManager::Get();
1462 imfManager.SetCursorPosition ( mCursorPosition );
1463 imfManager.NotifyCursorPosition();
1465 const TextStyle oldInputStyle( mInputStyle );
1467 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1471 // Create the grab handle.
1472 // Grab handle is created later.
1473 createGrabHandle = true;
1475 if( oldInputStyle != mInputStyle )
1477 // Updates the line height accordingly with the input style.
1480 EmitStyleChangedSignal();
1485 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1486 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1487 // otherwise the Grab handle will be shown when selecting.
1488 if ( createGrabHandle && IsGrabHandleEnabled() )
1490 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1494 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1495 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1496 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1497 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1502 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1504 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1506 // Ignore longpress if in selection mode already
1507 if( mHighlightMeshActor )
1512 if(longPress.state == Dali::Gesture::Started)
1514 // Start edit mode on long press
1515 if ( !mEditModeActive )
1520 // If text exists then select nearest word.
1521 if ( !mStyledText.empty())
1525 ShowGrabHandleAndSetVisibility( false );
1530 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1531 // converts the pre-edit word being displayed to a committed word.
1532 if ( !mUnderlinedPriorToPreEdit )
1535 style.SetUnderline( false );
1536 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1538 mPreEditFlag = false;
1539 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1540 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1541 PreEditReset( false );
1543 mCursorPosition = 0;
1545 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1546 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1548 std::size_t start = 0;
1549 std::size_t end = 0;
1550 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1552 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1554 ImfManager imfManager = ImfManager::Get();
1557 imfManager.SetCursorPosition ( mCursorPosition );
1558 imfManager.NotifyCursorPosition();
1561 SelectText( start, end );
1564 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1565 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1567 ShowPopupCutCopyPaste();
1572 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1574 const Text clipboardText( notifier.GetContent() );
1575 PasteText( clipboardText );
1577 SetCursorVisibility( true );
1578 StartCursorBlinkTimer();
1580 ShowGrabHandleAndSetVisibility( false );
1586 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1588 mPopupPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1590 const std::string& name = button.GetName();
1592 if(name == TextInputPopup::OPTION_SELECT_WORD)
1594 std::size_t start = 0;
1595 std::size_t end = 0;
1596 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1598 SelectText( start, end );
1600 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1602 SetCursorVisibility(false);
1603 StopCursorBlinkTimer();
1605 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1606 std::size_t start = 0;
1608 SelectText( start, end );
1610 else if(name == TextInputPopup::OPTION_CUT)
1612 bool ret = CopySelectedTextToClipboard();
1616 DeleteHighlightedText( true );
1620 SetCursorVisibility( true );
1621 StartCursorBlinkTimer();
1625 else if(name == TextInputPopup::OPTION_COPY)
1627 CopySelectedTextToClipboard();
1631 SetCursorVisibility( true );
1632 StartCursorBlinkTimer();
1636 else if(name == TextInputPopup::OPTION_PASTE)
1638 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1640 PasteText(retrievedString);
1642 SetCursorVisibility( true );
1643 StartCursorBlinkTimer();
1645 ShowGrabHandleAndSetVisibility( false );
1649 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1651 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1652 // Hence pass the false parameter for signalFinished.
1653 HidePopup( true, false );
1654 mClipboard.ShowClipboard();
1660 bool TextInput::OnCursorBlinkTimerTick()
1663 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1664 if ( mCursorRTLEnabled )
1666 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1668 mCursorBlinkStatus = !mCursorBlinkStatus;
1673 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1675 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1677 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1678 if(mHighlightMeshActor && mState == StateEdit)
1680 ShowPopupCutCopyPaste();
1684 //FIXME this routine needs to be re-written as it contains too many branches.
1685 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1687 std::string keyName = event.keyPressedName;
1688 std::string keyString = event.keyPressed;
1690 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1692 // Do not consume "Tab" and "Escape" keys.
1693 if(keyName == "Tab" || keyName == "Escape")
1695 // Escape key to end the edit mode
1701 HidePopup(); // If Pop-up shown then hides it as editing text.
1703 // Update Flag, indicates whether to update the text-input contents or not.
1704 // Any key stroke that results in a visual change of the text-input should
1705 // set this flag to true.
1708 // Whether to scroll text to cursor position.
1709 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1710 bool scroll = false;
1712 if (keyName == "Return")
1714 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1716 bool preEditFlagPreviouslySet( mPreEditFlag );
1718 // replaces highlighted text with new line
1719 DeleteHighlightedText( false );
1721 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1723 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1724 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1727 mCommitByKeyInput = true;
1730 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1731 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1733 mPreEditFlag = true;
1734 mIgnoreCommitFlag = false;
1744 else if ( keyName == "space" )
1746 if ( mHighlightMeshActor )
1748 // Some text is selected so erase it before adding space.
1749 DeleteHighlightedText( true );
1753 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1755 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1756 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1759 mCommitByKeyInput = true;
1764 else if (keyName == "BackSpace")
1766 if ( mHighlightMeshActor )
1768 // Some text is selected so erase it
1769 DeleteHighlightedText( true );
1774 if ( mCursorPosition > 0 )
1776 DeleteCharacter( mCursorPosition );
1782 else if (keyName == "Right")
1787 else if (keyName == "Left")
1789 AdvanceCursor(true);
1792 else // event is a character
1794 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1795 if ( !keyString.empty() )
1797 // replaces highlighted text with new character
1798 DeleteHighlightedText( false );
1800 // Received key String
1801 mCursorPosition += InsertAt( Text( keyString ), mCursorPosition, 0 );
1807 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1808 // as this is a costly operation.
1814 if(update || scroll)
1816 if( IsScrollEnabled() )
1818 // Calculates the new cursor position (in actor coordinates)
1819 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1821 ScrollTextViewToMakeCursorVisible( cursorPosition );
1828 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1830 std::string keyName = event.keyPressedName;
1831 std::string keyString = event.keyPressed;
1833 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1835 // The selected text become deselected when the key code is DALI_KEY_BACK.
1836 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1845 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1847 // Updates the stored scroll position.
1848 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1850 const Vector3& controlSize = GetControlSize();
1851 Size cursorSize( CURSOR_THICKNESS, 0.f );
1853 // Updates the cursor and grab handle position and visibility.
1854 if( mGrabHandle || mCursor )
1856 cursorSize.height = GetRowRectFromCharacterPosition( mCursorPosition ).height;
1857 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1859 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1861 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1865 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1866 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1871 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1872 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1876 // Updates the selection handles and highlighted text position and visibility.
1877 if( mSelectionHandleOne && mSelectionHandleTwo )
1879 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1880 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1881 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1882 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1883 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1884 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1886 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1887 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1889 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1890 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1891 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1892 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1894 if( mHighlightMeshActor )
1896 mHighlightMeshActor.SetVisible( true );
1902 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1904 // Scroll the text to make the cursor visible.
1905 const Size cursorSize( CURSOR_THICKNESS,
1906 GetRowRectFromCharacterPosition( mCursorPosition ).height );
1908 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1910 const Vector3& controlSize = GetControlSize();
1912 // Calculates the new scroll position.
1913 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1914 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1916 scrollOffset.x += cursorPosition.x;
1919 if( cursorPosition.y - cursorSize.height < 0.f || cursorPosition.y > controlSize.height )
1921 scrollOffset.y += cursorPosition.y;
1924 // Sets the new scroll position.
1925 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1926 SetScrollPosition( scrollOffset );
1929 void TextInput::StartScrollTimer()
1933 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1934 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1937 if( !mScrollTimer.IsRunning() )
1939 mScrollTimer.Start();
1943 void TextInput::StopScrollTimer()
1947 mScrollTimer.Stop();
1951 bool TextInput::OnScrollTimerTick()
1953 // TODO: need to set the new style accordingly the new handle position.
1955 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1957 // nothing to do if all handles are invisible or doesn't exist.
1963 // Choose between the grab handle or the selection handles.
1964 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1965 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1966 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1968 std::size_t newCursorPosition = 0;
1969 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1971 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1972 // the new selection handle's position needs to be different of the other one.
1973 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1974 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1975 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1977 if( differentSelectionHandles )
1979 handlePosition = newCursorPosition;
1981 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1983 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1985 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1986 scrollPosition += scrollDelta;
1987 SetScrollPosition( scrollPosition );
1989 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1994 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1997 actualHandlePosition.x += mScrollDisplacement.x;
1998 actualHandlePosition.y += mScrollDisplacement.y;
2003 // Public Internal Methods (public for testing purpose)
2005 void TextInput::SetUpTouchEvents()
2007 if ( !mTapDetector )
2009 mTapDetector = TapGestureDetector::New();
2010 // Attach the actors and connect the signal
2011 mTapDetector.Attach(Self());
2013 // As contains children which may register for tap the default control detector is not used.
2014 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2017 if ( !mDoubleTapDetector )
2019 mDoubleTapDetector = TapGestureDetector::New();
2020 mDoubleTapDetector.SetTapsRequired( 2 );
2021 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2023 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2024 // so that we do not, unnecessarily, have a double tap request all the time
2027 if ( !mPanGestureDetector )
2029 mPanGestureDetector = PanGestureDetector::New();
2030 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2033 if ( !mLongPressDetector )
2035 mLongPressDetector = LongPressGestureDetector::New();
2036 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2037 mLongPressDetector.Attach(Self());
2041 void TextInput::CreateTextViewActor()
2043 mDisplayedTextView = Toolkit::TextView::New();
2044 mDisplayedTextView.SetName( "DisplayedTextView ");
2045 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2046 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2047 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2048 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2049 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2050 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2051 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2052 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2053 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2054 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2056 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2058 Self().Add( mDisplayedTextView );
2061 // Start a timer to initiate, used by the cursor to blink.
2062 void TextInput::StartCursorBlinkTimer()
2064 if ( !mCursorBlinkTimer )
2066 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2067 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2070 if ( !mCursorBlinkTimer.IsRunning() )
2072 mCursorBlinkTimer.Start();
2076 // Start a timer to initiate, used by the cursor to blink.
2077 void TextInput::StopCursorBlinkTimer()
2079 if ( mCursorBlinkTimer )
2081 mCursorBlinkTimer.Stop();
2085 void TextInput::StartEditMode()
2087 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2089 if(!mEditModeActive)
2094 if ( mDoubleTapDetector )
2096 mDoubleTapDetector.Attach( Self() );
2100 void TextInput::EndEditMode()
2102 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2104 ClearKeyInputFocus();
2106 if ( mDoubleTapDetector )
2108 mDoubleTapDetector.Detach( Self() );
2112 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2114 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2116 mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2118 style.SetUnderline( true );
2119 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2123 void TextInput::RemovePreEditStyle()
2125 if ( !mUnderlinedPriorToPreEdit )
2128 style.SetUnderline( false );
2129 SetActiveStyle( style, TextStyle::UNDERLINE );
2133 // IMF related methods
2136 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2138 bool update( false );
2139 bool preeditResetRequired ( false );
2141 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2143 HidePopup(); // If Pop-up shown then hides it as editing text.
2146 switch ( imfEvent.eventName )
2148 case ImfManager::PREEDIT:
2150 mIgnoreFirstCommitFlag = false;
2152 // 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
2153 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2155 // replaces highlighted text with new character
2156 DeleteHighlightedText( false );
2159 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2161 if( IsScrollEnabled() )
2163 // Calculates the new cursor position (in actor coordinates)
2164 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2165 ScrollTextViewToMakeCursorVisible( cursorPosition );
2172 case ImfManager::COMMIT:
2174 if( mIgnoreFirstCommitFlag )
2176 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2177 mIgnoreFirstCommitFlag = false;
2181 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2183 // 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
2184 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2186 // replaces highlighted text with new character
2187 DeleteHighlightedText( false );
2190 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2191 // not needed, one such scenario is when the pre-edit word is too long to fit.
2192 if ( !mIgnoreCommitFlag )
2194 update = CommitReceived( imfEvent.predictiveString );
2198 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2204 if( IsScrollEnabled() )
2206 // Calculates the new cursor position (in actor coordinates)
2207 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2209 ScrollTextViewToMakeCursorVisible( cursorPosition );
2214 case ImfManager::DELETESURROUNDING:
2216 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2217 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2219 mPreEditFlag = false;
2221 std::size_t toDelete = 0;
2222 std::size_t numberOfCharacters = 0;
2224 if( mHighlightMeshActor )
2226 // delete highlighted text.
2227 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2228 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2232 if( static_cast<std::size_t>(std::abs( imfEvent.cursorOffset )) < mCursorPosition )
2234 toDelete = mCursorPosition + imfEvent.cursorOffset;
2236 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2238 numberOfCharacters = mStyledText.size() - toDelete;
2242 numberOfCharacters = imfEvent.numberOfChars;
2245 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2246 DeleteRange( toDelete, numberOfCharacters );
2248 mCursorPosition = toDelete;
2249 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2253 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2256 case ImfManager::GETSURROUNDING:
2258 // 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
2259 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2260 if (! ( mHighlightMeshActor || mSelectingText ) )
2262 std::string text( GetText() );
2263 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2265 imfManager.SetCursorPosition( mCursorPosition );
2266 imfManager.SetSurroundingText( text );
2269 if( 0 != mNumberOfSurroundingCharactersDeleted )
2271 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2272 mNumberOfSurroundingCharactersDeleted = 0;
2274 if( mStyledText.empty() )
2276 ShowPlaceholderText( mStyledPlaceHolderText );
2281 case ImfManager::VOID:
2283 DALI_ASSERT_DEBUG( false );
2287 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2289 return callbackData;
2292 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2294 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2296 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2297 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2299 bool preeditResetRequest ( false );
2301 if( mPreEditFlag ) // Already in pre-edit state.
2303 if( mStyledText.size() >= mMaxStringLength )
2305 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2306 // Cannot fit these characters into field, clear pre-edit.
2307 if ( !mUnderlinedPriorToPreEdit )
2310 style.SetUnderline( false );
2311 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2313 mIgnoreCommitFlag = true;
2314 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2315 mPreEditFlag = false;
2316 EmitMaxInputCharactersReachedSignal();
2320 // delete existing pre-edit string
2321 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2323 // Store new pre-edit string
2324 mPreEditString.SetText( keyString );
2326 if ( keyString.empty() )
2328 mPreEditFlag = false;
2329 mCursorPosition = mPreEditStartPosition;
2331 if( mStyledText.empty() )
2333 ShowPlaceholderText( mStyledPlaceHolderText );
2337 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2340 GetTextLayoutInfo();
2345 // Insert new pre-edit string. InsertAt updates the size and position table.
2346 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2347 // 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.
2348 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2349 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2350 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2353 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2357 else // mPreEditFlag not set
2359 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2361 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2362 // new pre-edit so move into pre-edit state by setting flag
2363 mPreEditFlag = true;
2364 mPreEditString.SetText( keyString ); // store new pre-edit string
2365 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2366 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2367 // 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.
2368 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2369 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2370 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2371 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2377 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2381 return preeditResetRequest;
2384 bool TextInput::CommitReceived(const std::string& keyString )
2386 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2387 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2389 bool update( false );
2391 RemovePreEditStyle();
2393 const std::size_t styledTextSize( mStyledText.size() );
2394 if( styledTextSize >= mMaxStringLength )
2396 // Cannot fit these characters into field, clear pre-edit.
2399 mIgnoreCommitFlag = true;
2400 mPreEditFlag = false;
2402 EmitMaxInputCharactersReachedSignal();
2408 // delete existing pre-edit string
2409 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2410 mPreEditFlag = false;
2412 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2413 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2415 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2417 // No need to update cursor position as Cursor location given by touch.
2418 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2419 mPreserveCursorPosition = false;
2423 // Cursor not set by touch so needs to be re-positioned to input more text
2424 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2426 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2427 if ( mCommitByKeyInput )
2429 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2430 mCommitByKeyInput = false;
2436 if ( mSelectTextOnCommit )
2438 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2443 else // mPreEditFlag not set
2445 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2447 if( mStyledText.empty() && mPlaceHolderSet )
2449 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2450 mDisplayedTextView.SetText( "" );
2451 mNumberOfSurroundingCharactersDeleted = 0;
2452 mPlaceHolderSet = false;
2454 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2456 mNumberOfSurroundingCharactersDeleted = 0;
2461 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2466 mSelectTextOnCommit = false;
2468 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2469 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2474 // End of IMF related methods
2476 std::size_t TextInput::DeletePreEdit()
2478 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2480 DALI_ASSERT_DEBUG( mPreEditFlag );
2482 const std::size_t preEditStringLength = mPreEditString.GetLength();
2483 const std::size_t styledTextSize = mStyledText.size();
2485 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2487 // Prevents erase items outside mStyledText bounds.
2488 if( mPreEditStartPosition > styledTextSize )
2490 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2491 mPreEditStartPosition = styledTextSize;
2494 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2496 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2497 endPosition = styledTextSize;
2500 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2502 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2503 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2505 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2507 return preEditStringLength;
2510 void TextInput::PreEditReset( bool preserveCursorPosition )
2512 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2513 preserveCursorPosition, mCursorPosition);
2515 // 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.
2516 mPreserveCursorPosition = preserveCursorPosition;
2518 // Reset incase we are in a pre-edit state.
2519 ImfManager imfManager = ImfManager::Get();
2522 imfManager.Reset(); // Will trigger a commit message
2526 void TextInput::CursorUpdate()
2530 ImfManager imfManager = ImfManager::Get();
2533 std::string text( GetText() );
2534 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2535 imfManager.SetCursorPosition ( mCursorPosition );
2536 imfManager.NotifyCursorPosition();
2540 /* Delete highlighted characters redisplay*/
2541 void TextInput::DeleteHighlightedText( bool inheritStyle )
2543 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2545 if( mHighlightMeshActor )
2547 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2549 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2550 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2552 // Get the styled text of the characters to be deleted as it may be needed if
2553 // the "exceed the text-input's boundaries" option is disabled.
2554 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2556 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2558 mStyledText.erase( start, end ); // erase range of characters
2560 // Remove text from TextView and update place holder text if required
2562 // Set the placeholder text only if the styled text is empty.
2563 if( mStyledText.empty() )
2565 ShowPlaceholderText( mStyledPlaceHolderText );
2569 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2571 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2573 // It may happen than after removing a white space or a new line character,
2574 // two words merge, this new word could be big enough to not fit in its
2575 // current line, so moved to the next one, and make some part of the text to
2576 // exceed the text-input's boundary.
2577 if( !mExceedEnabled )
2579 // Get the new text layout after removing some characters.
2580 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2582 // Get text-input's size.
2583 const Vector3& size = GetControlSize();
2585 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2586 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2588 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2590 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2591 styledCharactersToDelete.begin(),
2592 styledCharactersToDelete.end() );
2596 GetTextLayoutInfo();
2604 const TextStyle oldInputStyle( mInputStyle );
2606 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2608 if( oldInputStyle != mInputStyle )
2610 // Updates the line height accordingly with the input style.
2613 EmitStyleChangedSignal();
2619 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2621 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2622 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2624 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2627 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2629 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2630 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2632 mStyledText.erase(itStart, itEnd);
2634 // update the selection handles if they are visible.
2635 if( mHighlightMeshActor )
2637 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2638 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2640 if( minHandle >= start + ncharacters )
2642 minHandle -= ncharacters;
2644 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2649 if( maxHandle >= start + ncharacters )
2651 maxHandle -= ncharacters;
2653 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2659 // 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.
2662 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2664 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2665 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2666 // Mean we do not re-draw the text more than we have too.
2669 /* Delete character at current cursor position and redisplay*/
2670 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2672 // Ensure positionToDelete is not out of bounds.
2673 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2674 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2675 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2677 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2680 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2682 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2684 // Get the styled text of the character to be deleted as it may be needed if
2685 // the "exceed the text-input's boundaries" option is disabled.
2686 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2688 mStyledText.erase(it); // erase the character left of positionToDelete
2690 if( mStyledText.empty() )
2692 ShowPlaceholderText( mStyledPlaceHolderText );
2696 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2698 const Character characterToDelete = styledCharacterToDelete.mText[0];
2700 // It may happen than after removing a white space or a new line character,
2701 // two words merge, this new word could be big enough to not fit in its
2702 // current line, so moved to the next one, and make some part of the text to
2703 // exceed the text-input's boundary.
2704 if( !mExceedEnabled )
2706 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2708 // Get the new text layout after removing one character.
2709 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2711 // Get text-input's size.
2712 const Vector3& size = GetControlSize();
2714 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2715 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2717 MarkupProcessor::StyledTextArray array;
2718 array.push_back( styledCharacterToDelete );
2719 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2721 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2726 GetTextLayoutInfo();
2728 ShowGrabHandleAndSetVisibility( false );
2730 mCursorPosition = positionToDelete -1;
2732 const TextStyle oldInputStyle( mInputStyle );
2734 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2736 if( oldInputStyle != mInputStyle )
2738 // Updates the line height accordingly with the input style.
2741 EmitStyleChangedSignal();
2746 /*Insert new character into the string and (optionally) redisplay text-input*/
2747 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2749 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2751 // Ensure insertionPosition is not out of bounds.
2752 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2754 bool textExceedsMaximunNumberOfCharacters = false;
2755 bool textExceedsBoundary = false;
2756 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2758 ShowGrabHandleAndSetVisibility( false );
2760 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2764 mIgnoreCommitFlag = true;
2765 mPreEditFlag = false;
2766 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2767 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2770 if( textExceedsMaximunNumberOfCharacters )
2772 EmitMaxInputCharactersReachedSignal();
2775 if( textExceedsBoundary )
2777 EmitInputTextExceedsBoundariesSignal();
2778 PreEditReset( false );
2782 return insertedStringLength;
2785 ImageActor TextInput::CreateCursor( const Vector4& color)
2788 cursor = CreateSolidColorActor(color);
2789 cursor.SetName( "Cursor" );
2791 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2792 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_LEFT);
2793 cursor.SetVisible(false);
2798 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2800 // As cursor is not moving due to grab handle, handle should be hidden.
2801 ShowGrabHandleAndSetVisibility( false );
2803 bool cursorPositionChanged = false;
2806 if ( mCursorPosition >= places )
2808 mCursorPosition = mCursorPosition - places;
2809 cursorPositionChanged = true;
2814 if ((mCursorPosition + places) <= mStyledText.size())
2816 mCursorPosition = mCursorPosition + places;
2817 cursorPositionChanged = true;
2821 if( cursorPositionChanged )
2823 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2825 const TextStyle oldInputStyle( mInputStyle );
2826 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2830 if( oldInputStyle != mInputStyle )
2832 // Updates the line height accordingly with the input style.
2835 EmitStyleChangedSignal();
2838 ImfManager imfManager = ImfManager::Get();
2841 imfManager.SetCursorPosition ( mCursorPosition );
2842 imfManager.NotifyCursorPosition();
2847 void TextInput::DrawCursor()
2849 const Size rowRect = GetRowRectFromCharacterPosition( mCursorPosition );
2851 // Get height of cursor and set its size
2852 Size size( CURSOR_THICKNESS, 0.0f );
2853 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
2855 size.height = rowRect.height;
2859 // Measure Font so know how big text will be if no initial text to measure.
2860 size.height = mLineHeight;
2863 mCursor.SetSize(size);
2865 // If the character is italic then the cursor also tilts.
2866 mCursor.SetRotation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2868 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2870 if( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2872 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2873 bool altPositionValid; // Alternate cursor validity flag.
2874 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2875 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2877 SetAltCursorEnabled( altPositionValid );
2879 if( !altPositionValid )
2881 mCursor.SetPosition( position + UI_OFFSET );
2885 size.height *= 0.5f;
2886 mCursor.SetSize(size);
2887 mCursor.SetPosition( position + UI_OFFSET - Vector3( 0.0f, directionRTL ? 0.0f : size.height, 0.0f ) );
2889 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2890 size.height = rowRect.height * 0.5f;
2891 mCursorRTL.SetSize(size);
2892 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3( 0.0f, directionRTL ? size.height : 0.0f, 0.0f ) );
2895 if( IsScrollEnabled() )
2897 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2898 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2903 void TextInput::SetAltCursorEnabled( bool enabled )
2905 mCursorRTLEnabled = enabled;
2906 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2909 void TextInput::SetCursorVisibility( bool visible )
2911 mCursorVisibility = visible;
2912 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2913 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2916 void TextInput::CreateGrabHandle( Dali::Image image )
2922 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2926 mGrabHandleImage = image;
2929 mGrabHandle = ImageActor::New(mGrabHandleImage);
2930 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2931 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2933 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2935 ShowGrabHandleAndSetVisibility( false );
2937 CreateGrabArea( mGrabHandle );
2939 mActiveLayer.Add(mGrabHandle);
2943 void TextInput::CreateGrabArea( Actor& parent )
2945 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2946 mGrabArea.SetName( "GrabArea" );
2947 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2948 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2949 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2950 mTapDetector.Attach( mGrabArea );
2951 mPanGestureDetector.Attach( mGrabArea );
2952 mLongPressDetector.Attach( mGrabArea );
2954 parent.Add(mGrabArea);
2957 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2959 Vector3 actualHandlePosition;
2963 mActualGrabHandlePosition.x += displacement.x;
2964 mActualGrabHandlePosition.y += displacement.y;
2966 // Grab handle should jump to the nearest character and take cursor with it
2967 std::size_t newCursorPosition = 0;
2968 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2970 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2971 bool altPositionValid; // Alternate cursor validity flag.
2972 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2973 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition, directionRTL, altPosition, altPositionValid );
2975 if( altPositionValid )
2977 // Check which of the positions is the closest.
2978 if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( actualHandlePosition.x - mActualGrabHandlePosition.x ) )
2980 actualHandlePosition = altPosition;
2984 bool handleVisible = true;
2986 if( IsScrollEnabled() )
2988 const Vector3 controlSize = GetControlSize();
2989 const Size cursorSize = GetRowRectFromCharacterPosition( newCursorPosition );
2990 // Scrolls the text if the handle is not in a visible position
2991 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2998 mCurrentHandlePosition = actualHandlePosition;
2999 mScrollDisplacement = Vector2::ZERO;
3003 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3005 mScrollDisplacement.x = -SCROLL_SPEED;
3007 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3009 mScrollDisplacement.x = SCROLL_SPEED;
3011 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3013 mScrollDisplacement.y = -SCROLL_SPEED;
3015 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3017 mScrollDisplacement.y = SCROLL_SPEED;
3023 if( handleVisible && // Only redraw cursor and do updates if position changed
3024 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3026 mCursorPosition = newCursorPosition;
3028 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3030 const TextStyle oldInputStyle( mInputStyle );
3032 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3034 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3036 if( oldInputStyle != mInputStyle )
3038 // Updates the line height accordingly with the input style.
3041 EmitStyleChangedSignal();
3046 return actualHandlePosition;
3049 void TextInput::ShowGrabHandle( bool visible )
3051 if ( IsGrabHandleEnabled() )
3055 mGrabHandle.SetVisible( mGrabHandleVisibility );
3057 StartMonitoringStageForTouch();
3061 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3063 mGrabHandleVisibility = visible;
3064 ShowGrabHandle( visible );
3067 // Callbacks connected to be Property notifications for Boundary checking.
3069 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3071 mIsSelectionHandleOneFlipped = true;
3072 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3073 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3076 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3078 mIsSelectionHandleOneFlipped = false;
3079 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3080 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3083 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3085 mIsSelectionHandleTwoFlipped = true;
3086 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3087 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3090 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3092 mIsSelectionHandleTwoFlipped = false;
3093 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3094 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3097 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3098 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3100 mSelectionHandleOne.SetOpacity(0.0f);
3103 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3105 mSelectionHandleOne.SetOpacity(1.0f);
3108 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3110 mSelectionHandleTwo.SetOpacity(0.0f);
3113 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3115 mSelectionHandleTwo.SetOpacity(1.0f);
3118 // End of Callbacks connected to be Property notifications for Boundary checking.
3120 void TextInput::SetUpHandlePropertyNotifications()
3122 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3124 Vector3 handlesize = GetSelectionHandleSize();
3126 // Exceeding horizontal boundary
3127 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3128 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3130 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3131 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3133 // Within horizontal boundary
3134 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3135 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3137 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3138 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3140 // Exceeding vertical boundary
3141 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3142 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3143 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3144 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3146 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3147 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3148 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3149 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3151 // Within vertical boundary
3152 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3153 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3154 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3155 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3157 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3158 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3159 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3160 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3163 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3165 mSelectionHandleOnePosition = start;
3166 mSelectionHandleTwoPosition = end;
3168 if ( !mSelectionHandleOne )
3170 // create normal and pressed images
3171 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3172 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3174 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3175 mSelectionHandleOne.SetName("SelectionHandleOne");
3176 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3177 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3178 mIsSelectionHandleOneFlipped = false;
3179 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3181 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3182 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3184 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3185 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3187 mTapDetector.Attach( mHandleOneGrabArea );
3188 mPanGestureDetector.Attach( mHandleOneGrabArea );
3190 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3192 mSelectionHandleOne.Add( mHandleOneGrabArea );
3193 mActiveLayer.Add( mSelectionHandleOne );
3196 if ( !mSelectionHandleTwo )
3198 // create normal and pressed images
3199 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3200 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3202 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3203 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3204 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3205 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3206 mIsSelectionHandleTwoFlipped = false;
3207 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3209 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3210 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3211 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3212 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3214 mTapDetector.Attach( mHandleTwoGrabArea );
3215 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3217 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3219 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3221 mActiveLayer.Add( mSelectionHandleTwo );
3224 SetUpHandlePropertyNotifications();
3226 // update table as text may have changed.
3227 GetTextLayoutInfo();
3229 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3230 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3232 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3233 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3235 // Calculates and set the visibility if the scroll mode is enabled.
3236 bool isSelectionHandleOneVisible = true;
3237 bool isSelectionHandleTwoVisible = true;
3238 if( IsScrollEnabled() )
3240 const Vector3& controlSize( GetControlSize() );
3241 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3242 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3243 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3244 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3247 CreateHighlight(); // function will only create highlight if not already created.
3250 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3252 Vector3 actualHandlePosition;
3254 if ( mSelectionHandleOne && mSelectionHandleTwo )
3256 const Vector3& controlSize = GetControlSize();
3258 Size cursorSize( CURSOR_THICKNESS, 0.f );
3260 // Get a reference of the wanted selection handle (handle one or two).
3261 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3263 // Get a reference for the current position of the handle and a copy of its pair
3264 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3265 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3267 // Get a handle of the selection handle actor
3268 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3270 // Selection handles should jump to the nearest character
3271 std::size_t newHandlePosition = 0;
3272 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3274 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3276 bool handleVisible = true;
3278 if( IsScrollEnabled() )
3280 mCurrentSelectionId = handleId;
3282 cursorSize.height = GetRowRectFromCharacterPosition( newHandlePosition ).height;
3283 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3284 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3291 mCurrentSelectionHandlePosition = actualHandlePosition;
3292 mScrollDisplacement = Vector2::ZERO;
3296 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3298 mScrollDisplacement.x = -SCROLL_SPEED;
3300 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3302 mScrollDisplacement.x = SCROLL_SPEED;
3304 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3306 mScrollDisplacement.y = -SCROLL_SPEED;
3308 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3310 mScrollDisplacement.y = SCROLL_SPEED;
3316 if ( handleVisible && // Ensure the handle is visible.
3317 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3318 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3320 currentSelectionHandlePosition = newHandlePosition;
3322 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3323 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3327 if ( handleId == HandleOne )
3329 const TextStyle oldInputStyle( mInputStyle );
3331 // Set Active Style to that of first character in selection
3332 if( mSelectionHandleOnePosition < mStyledText.size() )
3334 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3337 if( oldInputStyle != mInputStyle )
3339 // Updates the line height accordingly with the input style.
3342 EmitStyleChangedSignal();
3348 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3351 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3353 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3354 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3356 if ( selectionHandleActor )
3358 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3359 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3360 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3362 if( IsScrollEnabled() )
3364 const Size cursorSize( CURSOR_THICKNESS,
3365 GetRowRectFromCharacterPosition( selectionHandlePosition ).height );
3366 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3368 GetControlSize() ) );
3373 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3375 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3376 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3377 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3378 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3380 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3382 // Deselect text prior to startSelectionIt
3383 for(;it!=startSelectionIt;++it)
3385 selectedVisualText[*it] = false;
3388 // Select text from startSelectionIt -> endSelectionIt
3389 for(;it!=endSelectionIt;++it)
3391 selectedVisualText[*it] = true;
3394 // Deselect text after endSelection
3397 selectedVisualText[*it] = false;
3400 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3403 // Calculate the dimensions of the quads they will make the highlight mesh
3404 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3406 // At the moment there is no public API to modify the block alignment option.
3407 const bool blockAlignEnabled = true;
3409 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3411 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3413 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3414 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3416 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3417 std::vector<bool> selectedVisualText;
3418 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3419 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3420 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3422 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3423 float rowLeft = 0.0f;
3424 float rowRight = 0.0f;
3425 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3426 float maxRowLeft = std::numeric_limits<float>::max();
3427 float maxRowRight = 0.0f;
3429 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3431 // Scan through entire text.
3434 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3436 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3437 bool charSelected( false );
3438 if( selectedIt != selectedEndIt )
3440 charSelected = *selectedIt++;
3443 if(selectionState == SelectionNone)
3447 selectionState = SelectionStarted;
3448 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3449 rowRight = rowLeft + charInfo.mSize.width;
3452 else if(selectionState == SelectionStarted)
3454 // break selection on:
3455 // 1. new line causing selection break. (\n or wordwrap)
3456 // 2. character not selected.
3457 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3460 // finished selection.
3461 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3462 // that it resides on. That way this enumeration is not necessary.
3464 if(lastIt->mIsNewParagraphChar)
3466 // If the last character is a new line, then to get the row rect, we need to scan from the character before the new line.
3467 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3469 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3470 maxRowLeft = std::min(maxRowLeft, min.x);
3471 maxRowRight = std::max(maxRowRight, max.x);
3472 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3473 float rowTop = rowBottom - rowSize.height;
3475 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3476 if(charSelected && blockAlignEnabled)
3478 rowRight = std::numeric_limits<float>::max();
3480 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3482 selectionState = SelectionNone;
3484 // Still selected? start a new selection
3487 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3488 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3489 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3490 selectionState = SelectionStarted;
3495 // build up highlight(s) with this selection data.
3496 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3497 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3504 // If reached end, and still on selection, then close selection.
3507 if(selectionState == SelectionStarted)
3509 // finished selection.
3511 if(lastIt->mIsNewParagraphChar)
3513 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3515 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3516 maxRowLeft = std::min(maxRowLeft, min.x);
3517 maxRowRight = std::max(maxRowRight, max.x);
3518 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3519 float rowTop = rowBottom - rowSize.height;
3520 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3524 // Get the top left and bottom right corners.
3525 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3526 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3527 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3529 // Clamp quads so they appear to clip to borders of the whole text.
3530 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3532 // For block-align align Further Clamp quads to max left and right extents
3533 if(blockAlignEnabled)
3535 // BlockAlign: Will adjust highlight to block:
3537 // H[ello] (top row right = max of all rows right)
3538 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3539 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3540 // [text] (bottom row left = min of all rows left)
3541 // (common in SMS messaging selection)
3543 // As opposed to the default which is tight text highlighting.
3548 // (common in regular text editors/web browser selection)
3550 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3553 // Finally clamp quads again so they don't exceed the boundry of the control.
3554 const Vector3& controlSize = GetControlSize();
3555 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3558 return mNewHighlightInfo;
3561 void TextInput::UpdateHighlight()
3563 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3565 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3567 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3568 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3569 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3570 // [BOTTOM] [ MIDDLE ]
3573 // Each quad is created as 2 triangles.
3574 // Middle is just 1 quad regardless of its size.
3588 if ( mHighlightMeshActor )
3590 // vertex and triangle buffers should always be present if MeshActor is alive.
3591 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3592 MeshData::VertexContainer vertices;
3593 Dali::MeshData::FaceIndices faceIndices;
3595 if( !newHighlightInfo.mQuadList.empty() )
3597 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3598 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3600 // vertex position defaults to (0 0 0)
3601 MeshData::Vertex vertex;
3602 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3605 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3607 // Add each quad geometry (a sub-selection) to the mesh data.
3617 QuadCoordinates& quad = *iter;
3619 vertex.x = quad.min.x;
3620 vertex.y = quad.min.y;
3621 vertices.push_back( vertex );
3624 vertex.x = quad.max.x;
3625 vertex.y = quad.min.y;
3626 vertices.push_back( vertex );
3628 // bottom-left (v+2)
3629 vertex.x = quad.min.x;
3630 vertex.y = quad.max.y;
3631 vertices.push_back( vertex );
3633 // bottom-right (v+3)
3634 vertex.x = quad.max.x;
3635 vertex.y = quad.max.y;
3636 vertices.push_back( vertex );
3638 // triangle A (3, 1, 0)
3639 faceIndices.push_back( v + 3 );
3640 faceIndices.push_back( v + 1 );
3641 faceIndices.push_back( v );
3643 // triangle B (0, 2, 3)
3644 faceIndices.push_back( v );
3645 faceIndices.push_back( v + 2 );
3646 faceIndices.push_back( v + 3 );
3648 mMeshData.SetFaceIndices( faceIndices );
3651 BoneContainer bones(0); // passed empty as bones not required
3652 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3653 mHighlightMesh.UpdateMeshData(mMeshData);
3658 void TextInput::ClearPopup()
3660 mPopupPanel.Clear();
3663 void TextInput::AddPopupOptions()
3665 mPopupPanel.AddPopupOptions();
3668 void TextInput::SetPopupPosition( const Vector3& position, const Vector2& alternativePosition )
3670 const Vector3& visiblePopUpSize = mPopupPanel.GetVisibileSize();
3672 Vector3 clampedPosition ( position );
3673 Vector3 tailOffsetPosition ( position );
3675 float xOffSet( 0.0f );
3677 Actor self = Self();
3678 const Vector3 textViewTopLeftWorldPosition = self.GetCurrentWorldPosition() - self.GetCurrentSize()*0.5f;
3680 const float popUpLeft = textViewTopLeftWorldPosition.x + position.x - visiblePopUpSize.width*0.5f;
3681 const float popUpTop = textViewTopLeftWorldPosition.y + position.y - visiblePopUpSize.height;
3683 // Clamp to left or right or of boundary
3684 if( popUpLeft < mBoundingRectangleWorldCoordinates.x )
3686 xOffSet = mBoundingRectangleWorldCoordinates.x - popUpLeft ;
3688 else if ( popUpLeft + visiblePopUpSize.width > mBoundingRectangleWorldCoordinates.z )
3690 xOffSet = mBoundingRectangleWorldCoordinates.z - ( popUpLeft + visiblePopUpSize.width );
3693 clampedPosition.x = position.x + xOffSet;
3694 tailOffsetPosition.x = -xOffSet;
3696 // Check if top left of PopUp outside of top bounding rectangle, if so then flip to lower position.
3697 bool flipTail( false );
3699 if ( popUpTop < mBoundingRectangleWorldCoordinates.y )
3701 clampedPosition.y = alternativePosition.y + visiblePopUpSize.height;
3705 mPopupPanel.GetRootActor().SetPosition( clampedPosition );
3706 mPopupPanel.SetTailPosition( tailOffsetPosition, flipTail );
3709 void TextInput::HidePopup(bool animate, bool signalFinished )
3711 if ( ( mPopupPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopupPanel.GetState() == TextInputPopup::StateShown ) )
3713 mPopupPanel.Hide( animate );
3715 if( animate && signalFinished )
3717 mPopupPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3722 void TextInput::ShowPopup( bool animate )
3725 Vector2 alternativePopupPosition;
3727 if(mHighlightMeshActor && mState == StateEdit)
3730 Vector3 bottomHandle; // referring to the bottom most point of the handle or the bottom line of selection.
3732 // When text is selected, show popup above top handle (and text), or below bottom handle.
3733 // topHandle: referring to the top most point of the handle or the top line of selection.
3734 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3736 topHandle = mSelectionHandleOneActualPosition;
3737 bottomHandle = mSelectionHandleTwoActualPosition;
3738 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3742 topHandle = mSelectionHandleTwoActualPosition;
3743 bottomHandle = mSelectionHandleOneActualPosition;
3744 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3746 topHandle.y += -mPopupOffsetFromText.y - rowSize.height;
3747 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3749 float xPosition = ( fabsf( topHandle.x - bottomHandle.x ) )*0.5f + std::min( mSelectionHandleOneActualPosition.x , mSelectionHandleTwoActualPosition.x );
3751 position.x = xPosition;
3753 // Alternative position if no upper space
3754 bottomHandle.y += GetSelectionHandleSize().y + mPopupOffsetFromText.w;
3755 alternativePopupPosition = Vector2 ( position.x, bottomHandle.y );
3759 // When no text is selected, show popup at world position of grab handle or cursor
3760 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3761 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3762 position.y -= ( mPopupOffsetFromText.y + rowSize.height );
3763 // if can't be positioned above, then position below row.
3764 alternativePopupPosition = Vector2( position.x, position.y ); // default if no grab handle
3767 // If grab handle enabled then position pop-up below the grab handle.
3768 alternativePopupPosition.y = rowSize.height + mGrabHandle.GetCurrentSize().height + mPopupOffsetFromText.w +50.0f;
3772 SetPopupPosition( position, alternativePopupPosition );
3775 mPopupPanel.Show( Self(), animate );
3776 StartMonitoringStageForTouch();
3778 mPopupPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3781 void TextInput::ShowPopupCutCopyPaste()
3785 mPopupPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3786 // Check the selected text is whole text or not.
3787 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3789 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3792 if ( !mStyledText.empty() && IsTextSelected() )
3794 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCopy, true );
3795 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCut, true );
3798 if( mClipboard && mClipboard.NumberOfItems() )
3800 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsPaste, true );
3801 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3806 mPopupPanel.Hide(false);
3810 void TextInput::SetUpPopupSelection( bool showCutButton )
3813 mPopupPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3814 // If no text exists then don't offer to select
3815 if ( !mStyledText.empty() )
3817 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3818 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelect, true );
3819 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCut, ( showCutButton && IsTextSelected() ) );
3821 // if clipboard has valid contents then offer paste option
3822 if( mClipboard && mClipboard.NumberOfItems() )
3824 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsPaste, true );
3825 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3830 mPopupPanel.Hide(false);
3833 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3838 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3839 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3840 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3841 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3843 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3845 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3847 float closestYdifference = std::numeric_limits<float>::max();
3848 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3849 std::size_t numberOfMatchedCharacters = 0;
3851 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3852 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3854 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3856 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3857 float baselinePosition = info.mPosition.y - info.mDescender;
3859 if( info.mIsVisible )
3861 // store difference between source y point and the y position of the current character
3862 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3864 if( currentYdifference < closestYdifference )
3866 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3867 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3868 closestYdifference = currentYdifference;
3869 matchedCharacters.clear();
3870 numberOfMatchedCharacters = 0; // reset count
3873 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3874 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3876 // ignore new line character.
3877 if( !info.mIsNewParagraphChar )
3879 matchedCharacters.push_back( info );
3880 numberOfMatchedCharacters++;
3884 } // End of loop checking each character's y position in the character layout table
3886 // Check if last character is a newline, if it is
3887 // then need pretend there is an imaginary line afterwards,
3888 // and check if user is touching below previous line.
3889 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3891 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewParagraphChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3893 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3897 // 2 Iterate through matching list of y positions and find closest matching X position.
3899 bool matched( false );
3901 // Traverse the characters in the visual order. VCC TODO: check for more than one line.
3902 std::size_t visualIndex = 0u;
3903 const std::size_t matchedCharactersSize = matchedCharacters.size();
3904 for( ; visualIndex < matchedCharactersSize; ++visualIndex )
3906 const Toolkit::TextView::CharacterLayoutInfo& info( *( matchedCharacters.begin() + mTextLayoutInfo.mCharacterVisualToLogicalMap[visualIndex] ) );
3908 if( info.mIsVisible )
3910 // stop when on left side of character's center.
3911 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3912 if( sourceScrollOffset.x < characterMidPointPosition )
3914 if(info.mIsRightToLeftCharacter)
3916 rightToLeftChar = true;
3918 glyphIntersection = info.mPosition.x;
3923 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3927 if( visualIndex == matchedCharactersSize )
3929 rightToLeftChar = lastRightToLeftChar;
3932 closestIndex = lineOffset + visualIndex;
3934 mClosestCursorPositionEOL = false; // reset
3935 if( ( visualIndex == matchedCharactersSize ) && !matched )
3937 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3940 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3941 if( rightToLeftChar && lastRightToLeftChar )
3943 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3948 // closestIndex is the visual index, need to convert it to the logical index
3949 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3951 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3953 // Checks for situations where user is touching between LTR and RTL
3954 // characters. To identify if the user means the end of a LTR string
3955 // or the beginning of an RTL string, and vice versa.
3956 if( closestIndex > 0 )
3958 if( rightToLeftChar && !lastRightToLeftChar )
3963 // A: In this touch range, the user is indicating that they wish to place
3964 // the cursor at the end of the LTR text.
3965 // B: In this touch range, the user is indicating that they wish to place
3966 // the cursor at the end of the RTL text.
3968 // Result of touching A area:
3969 // [.....LTR]|[RTL......]+
3971 // |: primary cursor (for typing LTR chars)
3972 // +: secondary cursor (for typing RTL chars)
3974 // Result of touching B area:
3975 // [.....LTR]+[RTL......]|
3977 // |: primary cursor (for typing RTL chars)
3978 // +: secondary cursor (for typing LTR chars)
3980 if( sourceScrollOffset.x < glyphIntersection )
3985 else if( !rightToLeftChar && lastRightToLeftChar )
3987 if( sourceScrollOffset.x < glyphIntersection )
3994 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3995 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3996 // one further ahead
3997 if( rightToLeftChar && !lastRightToLeftChar )
4002 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
4004 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
4006 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
4015 float TextInput::GetLineJustificationPosition() const
4017 const Vector3& size = mDisplayedTextView.GetCurrentSize();
4018 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4019 float alignmentOffset = 0.f;
4021 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4022 if( alignment & Toolkit::Alignment::HorizontalLeft )
4024 alignmentOffset = 0.f;
4026 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4028 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4030 else if( alignment & Toolkit::Alignment::HorizontalRight )
4032 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4035 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4036 float justificationOffset = 0.f;
4038 switch( justification )
4040 case Toolkit::TextView::Left:
4042 justificationOffset = 0.f;
4045 case Toolkit::TextView::Center:
4047 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4050 case Toolkit::TextView::Right:
4052 justificationOffset = mTextLayoutInfo.mTextSize.width;
4055 case Toolkit::TextView::Justified:
4057 justificationOffset = 0.f;
4062 DALI_ASSERT_ALWAYS( false );
4066 return alignmentOffset + justificationOffset;
4069 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4071 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4072 A newline character is not inserted in this case */
4074 Vector3 cursorPosition;
4076 Toolkit::TextView::CharacterLayoutInfo currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4080 if( characterPosition > 0u )
4082 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1u ];
4084 // If previous character on a different line then use current characters position
4085 if( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4087 // VCC TODO: PositionCursorAfterWordWrap currently doesn't work for multiline. Need to check this branch.
4088 if ( mClosestCursorPositionEOL )
4090 cursorPosition = Vector3( previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z ) ;
4094 cursorPosition = Vector3( currentCharInfo.mPosition );
4103 // If the character is left to right, the position is the character's position plus its width.
4104 const float ltrOffset = !currentCharInfo.mIsRightToLeftCharacter ? currentCharInfo.mSize.width : 0.f;
4106 cursorPosition.x = currentCharInfo.mPosition.x + ltrOffset;
4107 cursorPosition.y = currentCharInfo.mPosition.y;
4110 return cursorPosition;
4113 Vector3 TextInput::GetActualPositionFromCharacterPosition( std::size_t characterPosition ) const
4115 bool direction = false;
4116 Vector3 alternatePosition;
4117 bool alternatePositionValid = false;
4119 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4122 Vector3 TextInput::GetActualPositionFromCharacterPosition( std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4124 DALI_ASSERT_DEBUG( ( mTextLayoutInfo.mCharacterLayoutInfoTable.size() == mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ) &&
4125 ( mTextLayoutInfo.mCharacterLayoutInfoTable.size() == mTextLayoutInfo.mCharacterVisualToLogicalMap.size() ) &&
4126 "TextInput::GetActualPositionFromCharacterPosition. All layout tables must have the same size." );
4128 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4130 alternatePositionValid = false;
4131 directionRTL = false;
4133 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4135 if( characterPosition == 0u )
4137 // When the cursor position is at the beginning, it should be at the start of the current character.
4138 // If the current character is LTR, then the start is on the right side of the glyph.
4139 // If the current character is RTL, then the start is on the left side of the glyph.
4141 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() ) ).mIsVisible )
4143 characterPosition = FindVisibleCharacter( Right, 0u );
4146 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4147 const float rtlOffset = info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f;
4149 cursorPosition.x = info.mPosition.x + rtlOffset;
4150 cursorPosition.y = info.mPosition.y;
4151 directionRTL = info.mIsRightToLeftCharacter;
4153 else if( characterPosition > 0u )
4155 // Get the direction of the paragraph.
4156 const std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition );
4157 const bool isParagraphRightToLeft = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + startCharacterPosition ) ).mIsRightToLeftCharacter;
4159 // When cursor is not at beginning, consider possibility of
4160 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4162 // Cursor position should be the end of the last character.
4163 // If the last character is LTR, then the end is on the right side of the glyph.
4164 // If the last character is RTL, then the end is on the left side of the glyph.
4166 --characterPosition;
4168 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition ) ).mIsVisible )
4170 characterPosition = FindVisibleCharacter( Left, characterPosition );
4173 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4174 if( ( characterPosition > 0u ) && info.mIsNewParagraphChar && !IsScrollEnabled() )
4176 // VCC TODO : check for a new paragraph character.
4178 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4179 const Vector3& size = GetControlSize();
4181 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4183 --characterPosition;
4185 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4188 if( !info.mIsNewParagraphChar )
4190 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4194 // VCC TODO : check for a new paragraph character.
4196 // When cursor points to first character on new line, position cursor at the start of this glyph.
4197 if( characterPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4199 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4200 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4202 cursorPosition.x = infoNext.mPosition.x + start;
4203 cursorPosition.y = infoNext.mPosition.y;
4207 // If cursor points to the end of text, then can only position
4208 // cursor where the new line starts based on the line-justification position.
4209 cursorPosition.x = GetLineJustificationPosition();
4211 if( characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size() )
4213 // If this is after the last character, then we can assume that the new cursor
4214 // should be exactly one row below the current row.
4216 const Size rowRect = GetRowRectFromCharacterPosition( characterPosition - 1u );
4217 cursorPosition.y = info.mPosition.y + rowRect.height;
4221 // If this is not after last character, then we can use this row's height.
4222 // should be exactly one row below the current row.
4224 const Size rowRect = GetRowRectFromCharacterPosition( characterPosition );
4225 cursorPosition.y = info.mPosition.y + rowRect.height;
4230 directionRTL = info.mIsRightToLeftCharacter;
4232 if( 1u < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4234 // 1. When the cursor is neither at the beginning or the end,
4235 // we can show multiple cursors under situations when the cursor is
4236 // between RTL and LTR text...
4237 if( characterPosition + 1u < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4239 std::size_t characterAltPosition = characterPosition + 1u;
4241 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterAltPosition ];
4243 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4245 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4246 // Text: [...LTR...]|[...RTL...]
4248 // Alternate cursor pos: ^
4249 // In which case we need to display an alternate cursor for the RTL text.
4251 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4252 alternatePosition.y = infoAlt.mPosition.y;
4253 alternatePositionValid = true;
4255 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4257 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4258 // Text: |[...RTL...] [...LTR....]
4260 // Alternate cursor pos: ^
4261 // In which case we need to display an alternate cursor for the RTL text.
4263 alternatePosition.x = infoAlt.mPosition.x;
4264 alternatePosition.y = infoAlt.mPosition.y;
4265 alternatePositionValid = true;
4270 // 2. When the cursor is at the end of the text,
4271 // and we have multi-directional text,
4272 // we can also consider showing mulitple cursors.
4273 // The rule here is:
4274 // If first and last characters on row are different
4275 // Directions, then two cursors need to be displayed.
4277 if( info.mIsRightToLeftCharacter != isParagraphRightToLeft )
4279 // The last character's direction is differernt than the first one of current paragraph.
4282 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ GetFirstCharacterWithSameDirection( characterPosition ) ];
4284 if(info.mIsRightToLeftCharacter)
4286 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4287 // Text: [...LTR...]|[...RTL...]
4289 // Alternate cursor pos: ^
4290 // In which case we need to display an alternate cursor for the RTL text, this cursor
4291 // should be at the end of the given line.
4293 alternatePosition.x = infoStart.mPosition.x + infoStart.mSize.width;
4294 alternatePosition.y = infoStart.mPosition.y;
4295 alternatePositionValid = true;
4297 else if(!info.mIsRightToLeftCharacter) // starting RTL
4299 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4300 // Text: |[...RTL...] [...LTR....]
4302 // Alternate cursor pos: ^
4303 // In which case we need to display an alternate cursor for the RTL text.
4305 alternatePosition.x = infoStart.mPosition.x;
4306 alternatePosition.y = infoStart.mPosition.y;
4307 alternatePositionValid = true;
4312 } // characterPosition > 0
4316 // If the character table is void, place the cursor accordingly the text alignment.
4317 const Vector3& size = GetControlSize();
4319 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4320 float alignmentOffset = 0.f;
4322 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4323 if( alignment & Toolkit::Alignment::HorizontalLeft )
4325 alignmentOffset = 0.f;
4327 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4329 alignmentOffset = 0.5f * ( size.width );
4331 else if( alignment & Toolkit::Alignment::HorizontalRight )
4333 alignmentOffset = size.width;
4336 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4337 cursorPosition.x = alignmentOffset;
4339 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4340 if( alignment & Toolkit::Alignment::VerticalTop )
4342 cursorPosition.y = mLineHeight;
4344 else if( alignment & Toolkit::Alignment::VerticalCenter )
4346 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4348 else if( alignment & Toolkit::Alignment::VerticalBottom )
4350 cursorPosition.y = size.height;
4354 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4355 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4356 if( alternatePositionValid )
4358 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4359 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4362 return cursorPosition;
4365 std::size_t TextInput::GetRowStartFromCharacterPosition( std::size_t logicalPosition ) const
4367 // scan string from current position to beginning of current line to note direction of line
4368 while( logicalPosition )
4371 if( mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsNewParagraphChar )
4378 return logicalPosition;
4381 std::size_t TextInput::GetFirstCharacterWithSameDirection( std::size_t logicalPosition ) const
4383 const bool isRightToLeft = mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsRightToLeftCharacter;
4385 while( logicalPosition )
4388 if( isRightToLeft != mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsRightToLeftCharacter )
4395 return logicalPosition;
4398 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4402 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4405 Size TextInput::GetRowRectFromCharacterPosition( std::size_t characterPosition, Vector2& min, Vector2& max ) const
4407 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4408 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4410 min = Vector2::ZERO;
4411 max = Vector2(0.0f, mLineHeight);
4415 DALI_ASSERT_DEBUG( characterPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4417 // Initializes the min and max position.
4418 const std::size_t initialPosition = ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) ? characterPosition - 1u : characterPosition;
4419 min = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + initialPosition ) ).mPosition.GetVectorXY();
4423 // 1) Find the line where the character is laid-out.
4424 for( Toolkit::TextView::LineLayoutInfoContainer::const_iterator lineIt = mTextLayoutInfo.mLines.begin(), lineEndIt = mTextLayoutInfo.mLines.end();
4425 !found && ( lineIt != mTextLayoutInfo.mLines.end() );
4428 const Toolkit::TextView::LineLayoutInfo& lineInfo( *lineIt );
4430 // Index within the whole text to the last character of the current line.
4431 std::size_t lastCharacterOfLine = 0u;
4433 Toolkit::TextView::LineLayoutInfoContainer::const_iterator lineNextIt = lineIt + 1u;
4434 if( lineNextIt != lineEndIt )
4436 lastCharacterOfLine = (*lineNextIt).mCharacterGlobalIndex - 1u;
4440 lastCharacterOfLine = mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1u;
4443 // Check if the given chracter position is within the line.
4444 if( ( lineInfo.mCharacterGlobalIndex <= initialPosition ) && ( initialPosition <= lastCharacterOfLine ) )
4446 // 2) Get the row rect of all laid-out characters on the line.
4448 // Need to scan all characters of the line because they are in the logical position.
4449 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + lineInfo.mCharacterGlobalIndex,
4450 endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + lastCharacterOfLine + 1u;
4454 const Toolkit::TextView::CharacterLayoutInfo& characterInfo( *it );
4456 min.x = std::min( min.x, characterInfo.mPosition.x );
4457 min.y = std::min( min.y, characterInfo.mPosition.y );
4458 max.x = std::max( max.x, characterInfo.mPosition.x + characterInfo.mSize.width );
4459 max.y = std::max( max.y, characterInfo.mPosition.y + characterInfo.mSize.height );
4466 return Size( max.x - min.x, max.y - min.y );
4469 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4471 Actor popUpPanel = mPopupPanel.GetRootActor();
4473 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4479 Dali::Actor parent( touchedActor.GetParent() );
4483 return WasTouchedCheck( parent );
4490 void TextInput::StartMonitoringStageForTouch()
4492 Stage stage = Stage::GetCurrent();
4493 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4496 void TextInput::EndMonitoringStageForTouch()
4498 Stage stage = Stage::GetCurrent();
4499 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4502 void TextInput::OnStageTouched(const TouchEvent& event)
4504 if( event.GetPointCount() > 0 )
4506 if ( TouchPoint::Down == event.GetPoint(0).state )
4508 const Actor touchedActor(event.GetPoint(0).hitActor);
4510 bool popUpShown( false );
4512 if ( ( mPopupPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopupPanel.GetState() == TextInputPopup::StateShown ) )
4517 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4519 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4521 EndMonitoringStageForTouch();
4522 HidePopup( true, false );
4525 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4527 EndMonitoringStageForTouch();
4528 ShowGrabHandleAndSetVisibility( false );
4534 void TextInput::SelectText(std::size_t start, std::size_t end)
4536 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4537 IsGrabHandleEnabled()?"true":"false",
4538 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4539 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4540 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4542 StartMonitoringStageForTouch();
4544 if ( mEditModeActive ) // Only allow text selection when in edit mode
4546 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4547 mSelectingText = true;
4549 std::size_t selectionStartPosition = std::min( start, end );
4551 // Hide grab handle when selecting.
4552 ShowGrabHandleAndSetVisibility( false );
4554 if( start != end ) // something to select
4556 SetCursorVisibility( false );
4557 StopCursorBlinkTimer();
4559 CreateSelectionHandles(start, end);
4562 const TextStyle oldInputStyle( mInputStyle );
4563 mInputStyle = GetStyleAt( selectionStartPosition ); // Inherit style from selected position.
4565 if( oldInputStyle != mInputStyle )
4567 // Updates the line height accordingly with the input style.
4570 EmitStyleChangedSignal();
4576 mSelectingText = false;
4580 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4582 MarkupProcessor::StyledTextArray currentSelectedText;
4584 if ( IsTextSelected() )
4586 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4587 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4589 for(; it != end; ++it)
4591 MarkupProcessor::StyledText& styledText( *it );
4592 currentSelectedText.push_back( styledText );
4595 return currentSelectedText;
4598 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4600 const std::size_t beginIndex = std::min( begin, end );
4601 const std::size_t endIndex = std::max( begin, end );
4604 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4606 // Create a styled text array used to replace the text into the text-view.
4607 MarkupProcessor::StyledTextArray text;
4608 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4610 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4611 GetTextLayoutInfo();
4613 if( IsScrollEnabled() )
4615 // Need to set the scroll position as the text's size may have changed.
4616 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4619 ShowGrabHandleAndSetVisibility( false );
4625 // Set Handle positioning as the new style may have repositioned the characters.
4626 SetSelectionHandlePosition(HandleOne);
4627 SetSelectionHandlePosition(HandleTwo);
4630 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4632 // Just hide the grab handle when keyboard is hidden.
4633 if (!keyboardShown )
4635 ShowGrabHandleAndSetVisibility( false );
4637 // If the keyboard is not now being shown, then hide the popup panel
4638 mPopupPanel.Hide( true );
4642 // Removes highlight and resumes edit mode state
4643 void TextInput::RemoveHighlight( bool hidePopup )
4645 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4647 if ( mHighlightMeshActor )
4649 if ( mSelectionHandleOne )
4651 mActiveLayer.Remove( mSelectionHandleOne );
4652 mSelectionHandleOne.Reset();
4653 mSelectionHandleOneOffset.x = 0.0f;
4655 if ( mSelectionHandleTwo )
4657 mActiveLayer.Remove( mSelectionHandleTwo );
4658 mSelectionHandleTwo.Reset();
4659 mSelectionHandleTwoOffset.x = 0.0f;
4662 mNewHighlightInfo.mQuadList.clear();
4664 Self().Remove( mHighlightMeshActor );
4666 SetCursorVisibility( true );
4667 StartCursorBlinkTimer();
4669 mHighlightMeshActor.Reset();
4670 // NOTE: We cannot dereference mHighlightMesh, due
4671 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4679 mSelectionHandleOnePosition = 0;
4680 mSelectionHandleTwoPosition = 0;
4683 void TextInput::CreateHighlight()
4685 if ( !mHighlightMeshActor )
4687 mMeshData = MeshData( );
4688 mMeshData.SetHasNormals( true );
4690 mCustomMaterial = Material::New("CustomMaterial");
4691 mCustomMaterial.SetDiffuseColor( mMaterialColor );
4693 mMeshData.SetMaterial( mCustomMaterial );
4695 mHighlightMesh = Mesh::New( mMeshData );
4697 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4698 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4699 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4700 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4701 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4702 mHighlightMeshActor.SetAffectedByLighting(false);
4704 Self().Add(mHighlightMeshActor);
4709 bool TextInput::CopySelectedTextToClipboard()
4711 mCurrentCopySelecton.clear();
4713 mCurrentCopySelecton = GetSelectedText();
4715 std::string stringToStore;
4717 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4718 * a marked up string.
4720 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4721 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4722 bool success = mClipboard.SetItem( stringToStore );
4726 void TextInput::PasteText( const Text& text )
4728 // Update Flag, indicates whether to update the text-input contents or not.
4729 // Any key stroke that results in a visual change of the text-input should
4730 // set this flag to true.
4731 bool update = false;
4732 if( mHighlightMeshActor )
4734 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4735 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4737 ImfManager imfManager = ImfManager::Get();
4740 imfManager.SetCursorPosition( mCursorPosition );
4741 imfManager.NotifyCursorPosition();
4743 DeleteHighlightedText( true );
4747 bool textExceedsMaximunNumberOfCharacters = false;
4748 bool textExceedsBoundary = false;
4750 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4752 mCursorPosition += insertedStringLength;
4753 ImfManager imfManager = ImfManager::Get();
4756 imfManager.SetCursorPosition ( mCursorPosition );
4757 imfManager.NotifyCursorPosition();
4760 update = update || ( insertedStringLength > 0 );
4767 if( insertedStringLength < text.GetLength() )
4769 EmitMaxInputCharactersReachedSignal();
4772 if( textExceedsBoundary )
4774 EmitInputTextExceedsBoundariesSignal();
4778 void TextInput::SetTextDirection()
4780 // Put the cursor to the right if we are empty and an RTL language is being used.
4781 if ( mStyledText.empty() )
4783 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4785 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4786 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4788 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4789 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4791 int alignment( mDisplayedTextView.GetTextAlignment() &
4792 ( Toolkit::Alignment::VerticalTop |
4793 Toolkit::Alignment::VerticalCenter |
4794 Toolkit::Alignment::VerticalBottom |
4795 Toolkit::Alignment::HorizontalCenter ) );
4796 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4798 // If our alignment is in the center, then do not change.
4799 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4801 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4804 // If our justification is in the center, then do not change.
4805 if ( justification != Toolkit::TextView::Center )
4807 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4810 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4811 mDisplayedTextView.SetLineJustification( justification );
4815 void TextInput::UpdateLineHeight()
4817 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4818 mLineHeight = font.GetLineHeight();
4820 // If the height exceed policy is shrink or exceed the boundaries of the text-input is not allowed, then modify the line height is needed.
4822 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4824 if( !mExceedEnabled || shrink )
4826 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4830 std::size_t TextInput::FindVisibleCharacter( FindVisibleCharacterDirection direction , std::size_t cursorPosition ) const
4832 // VCC check if we need do this in the visual order ...
4833 std::size_t position = 0u;
4835 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4841 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4843 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1u : position ) ) ).mIsVisible )
4845 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4851 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4852 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1u : position ) ) ).mIsVisible )
4854 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4860 position = FindVisibleCharacterLeft( 0u, mTextLayoutInfo.mCharacterLayoutInfoTable );
4865 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4872 void TextInput::SetSortModifier( float depthOffset )
4874 if(mDisplayedTextView)
4876 mDisplayedTextView.SetSortModifier(depthOffset);
4880 void TextInput::SetSnapshotModeEnabled( bool enable )
4882 if(mDisplayedTextView)
4884 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4888 bool TextInput::IsSnapshotModeEnabled() const
4890 bool snapshotEnabled = false;
4892 if(mDisplayedTextView)
4894 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4897 return snapshotEnabled;
4900 void TextInput::SetMarkupProcessingEnabled( bool enable )
4902 mMarkUpEnabled = enable;
4905 bool TextInput::IsMarkupProcessingEnabled() const
4907 return mMarkUpEnabled;
4910 void TextInput::SetScrollEnabled( bool enable )
4912 if( mDisplayedTextView )
4914 mDisplayedTextView.SetScrollEnabled( enable );
4919 // Don't set cursor's and handle's visibility to false if they are outside the
4920 // boundaries of the text-input.
4921 mIsCursorInScrollArea = true;
4922 mIsGrabHandleInScrollArea = true;
4923 if( mSelectionHandleOne && mSelectionHandleTwo )
4925 mSelectionHandleOne.SetVisible( true );
4926 mSelectionHandleTwo.SetVisible( true );
4928 if( mHighlightMeshActor )
4930 mHighlightMeshActor.SetVisible( true );
4936 bool TextInput::IsScrollEnabled() const
4938 bool scrollEnabled = false;
4940 if( mDisplayedTextView )
4942 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4945 return scrollEnabled;
4948 void TextInput::SetScrollPosition( const Vector2& position )
4950 if( mDisplayedTextView )
4952 mDisplayedTextView.SetScrollPosition( position );
4956 Vector2 TextInput::GetScrollPosition() const
4958 Vector2 scrollPosition;
4960 if( mDisplayedTextView )
4962 scrollPosition = mDisplayedTextView.GetScrollPosition();
4965 return scrollPosition;
4968 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4970 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4971 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4972 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4974 // Add style to the new input text.
4975 MarkupProcessor::StyledTextArray textToInsert;
4976 for( std::size_t i = 0; i < insertedStringLength; ++i )
4978 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4979 textToInsert.push_back( newStyledCharacter );
4982 //Insert text to the TextView.
4983 const bool emptyTextView = mStyledText.empty();
4984 if( emptyTextView && mPlaceHolderSet )
4986 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4987 mDisplayedTextView.SetText( textToInsert );
4991 if( 0 == numberOfCharactersToReplace )
4993 mDisplayedTextView.InsertTextAt( position, textToInsert );
4997 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
5000 mPlaceHolderSet = false;
5002 if( textToInsert.empty() )
5004 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
5005 GetTextLayoutInfo();
5009 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
5010 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5013 textExceedsBoundary = false;
5015 if( !mExceedEnabled )
5017 const Vector3& size = GetControlSize();
5019 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5021 // If new text does not fit within TextView
5022 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
5023 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
5024 GetTextLayoutInfo();
5025 textExceedsBoundary = true;
5026 insertedStringLength = 0;
5029 if( textExceedsBoundary )
5031 // Add the part of the text which fits on the text-input.
5033 // Split the text which doesn't fit in two halves.
5034 MarkupProcessor::StyledTextArray firstHalf;
5035 MarkupProcessor::StyledTextArray secondHalf;
5036 SplitText( textToInsert, firstHalf, secondHalf );
5038 // Clear text. This text will be filled with the text inserted.
5039 textToInsert.clear();
5041 // Where to insert the text.
5042 std::size_t positionToInsert = position;
5044 bool end = text.GetLength() <= 1;
5047 // Insert text and check ...
5048 const std::size_t textLength = firstHalf.size();
5049 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5050 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5052 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5054 // Inserted text doesn't fit.
5056 // Remove inserted text
5057 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5058 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5060 // The iteration finishes when only one character doesn't fit.
5061 end = textLength <= 1;
5065 // Prepare next two halves for next iteration.
5066 MarkupProcessor::StyledTextArray copyText = firstHalf;
5067 SplitText( copyText, firstHalf, secondHalf );
5074 // store text to be inserted in mStyledText.
5075 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5077 // Increase the inserted characters counter.
5078 insertedStringLength += textLength;
5080 // Prepare next two halves for next iteration.
5081 MarkupProcessor::StyledTextArray copyText = secondHalf;
5082 SplitText( copyText, firstHalf, secondHalf );
5084 // Update where next text has to be inserted
5085 positionToInsert += textLength;
5091 if( textToInsert.empty() && emptyTextView )
5093 // No character has been added and the text-view was empty.
5094 // Show the placeholder text.
5095 ShowPlaceholderText( mStyledPlaceHolderText );
5099 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5100 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5101 mPlaceHolderSet = false;
5104 return insertedStringLength;
5107 void TextInput::GetTextLayoutInfo()
5109 if( mStyledText.empty() )
5111 // The text-input has no text, clear the text-view's layout info.
5112 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5116 if( mDisplayedTextView )
5118 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5122 // There is no text-view.
5123 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5128 void TextInput::SetOffsetFromText( const Vector4& offset )
5130 mPopupOffsetFromText = offset;
5133 const Vector4& TextInput::GetOffsetFromText() const
5135 return mPopupOffsetFromText;
5138 void TextInput::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value )
5140 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5144 TextInput& textInputImpl( GetImpl( textInput ) );
5146 switch ( propertyIndex )
5148 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5150 textInputImpl.SetMaterialDiffuseColor( value.Get< Vector4 >() );
5153 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5155 textInputImpl.mPopupPanel.SetCutPastePopupColor( value.Get< Vector4 >() );
5158 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5160 textInputImpl.mPopupPanel.SetCutPastePopupPressedColor( value.Get< Vector4 >() );
5163 case Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY:
5165 textInputImpl.mPopupPanel.SetCutPastePopupBorderColor( value.Get< Vector4 >() );
5168 case Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY:
5170 textInputImpl.mPopupPanel.SetCutPastePopupIconColor( value.Get< Vector4 >() );
5173 case Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY:
5175 textInputImpl.mPopupPanel.SetCutPastePopupIconPressedColor( value.Get< Vector4 >() );
5178 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY:
5180 textInputImpl.mPopupPanel.SetCutPastePopupTextColor( value.Get< Vector4 >() );
5183 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY:
5185 textInputImpl.mPopupPanel.SetCutPastePopupTextPressedColor( value.Get< Vector4 >() );
5188 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5190 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCut, value.Get<unsigned int>() );
5193 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5195 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCopy, value.Get<unsigned int>() );
5198 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5200 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsPaste, value.Get<unsigned int>() );
5203 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5205 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelect, value.Get<unsigned int>() );
5208 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5210 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll, value.Get<unsigned int>() );
5213 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5215 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsClipboard, value.Get<unsigned int>() );
5218 case Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY:
5220 textInputImpl.SetOffsetFromText( value.Get< Vector4 >() );
5223 case Toolkit::TextInput::CURSOR_COLOR_PROPERTY:
5225 textInputImpl.mCursor.SetColor( value.Get< Vector4 >() );
5231 Property::Value TextInput::GetProperty( BaseObject* object, Property::Index propertyIndex )
5233 Property::Value value;
5235 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5239 TextInput& textInputImpl( GetImpl( textInput ) );
5241 switch ( propertyIndex )
5243 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5245 value = textInputImpl.GetMaterialDiffuseColor();
5248 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5250 value = textInputImpl.mPopupPanel.GetCutPastePopupColor();
5253 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5255 value = textInputImpl.mPopupPanel.GetCutPastePopupPressedColor();
5258 case Toolkit::TextInput::CUT_AND_PASTE_BORDER_COLOR_PROPERTY :
5260 value = textInputImpl.mPopupPanel.GetCutPastePopupBorderColor();
5263 case Toolkit::TextInput::CUT_AND_PASTE_ICON_COLOR_PROPERTY:
5265 value = textInputImpl.mPopupPanel.GetCutPastePopupIconColor();
5268 case Toolkit::TextInput::CUT_AND_PASTE_ICON_PRESSED_COLOR_PROPERTY:
5270 value = textInputImpl.mPopupPanel.GetCutPastePopupIconPressedColor();
5273 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_COLOR_PROPERTY:
5275 value = textInputImpl.mPopupPanel.GetCutPastePopupTextColor();
5278 case Toolkit::TextInput::CUT_AND_PASTE_TEXT_PRESSED_COLOR_PROPERTY:
5280 value = textInputImpl.mPopupPanel.GetCutPastePopupTextPressedColor();
5283 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5285 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCut );
5288 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5290 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCopy );
5293 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5295 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsPaste );
5298 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5300 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelect );
5303 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5305 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll );
5308 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5310 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsClipboard );
5313 case Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY:
5315 value = textInputImpl.GetOffsetFromText();
5318 case Toolkit::TextInput::CURSOR_COLOR_PROPERTY:
5320 value = textInputImpl.mCursor.GetCurrentColor();
5327 void TextInput::EmitStyleChangedSignal()
5329 // emit signal if input style changes.
5330 Toolkit::TextInput handle( GetOwner() );
5331 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5334 void TextInput::EmitTextModified()
5336 // emit signal when text changes.
5337 Toolkit::TextInput handle( GetOwner() );
5338 mTextModifiedSignal.Emit( handle );
5342 void TextInput::EmitMaxInputCharactersReachedSignal()
5344 // emit signal if max characters is reached during text input.
5345 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5347 Toolkit::TextInput handle( GetOwner() );
5348 mMaxInputCharactersReachedSignalV2.Emit( handle );
5351 void TextInput::EmitInputTextExceedsBoundariesSignal()
5353 // Emit a signal when the input text exceeds the boundaries of the text input.
5355 Toolkit::TextInput handle( GetOwner() );
5356 mInputTextExceedBoundariesSignalV2.Emit( handle );
5359 } // namespace Internal
5361 } // namespace Toolkit