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>
25 #include <dali/integration-api/debug.h>
32 #define GET_LOCALE_TEXT(string) dgettext("sys_string", string)
41 #if defined(DEBUG_ENABLED)
42 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
45 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
46 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
47 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
48 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
49 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
50 const Vector4 LIGHTBLUE( 10.0f/255.0f, 140.0f/255.0f, 210.0f/255.0f, 1.0f ); // Used for Selection highlight
52 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
53 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
54 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
55 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
56 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
57 const char* DEFAULT_CURSOR( DALI_IMAGE_DIR "cursor.png" );
59 const char* DEFAULT_ICON_CLIPBOARD( DALI_IMAGE_DIR "copy_paste_icon_clipboard.png" );
60 const char* DEFAULT_ICON_COPY( DALI_IMAGE_DIR "copy_paste_icon_copy.png" );
61 const char* DEFAULT_ICON_CUT( DALI_IMAGE_DIR "copy_paste_icon_cut.png" );
62 const char* DEFAULT_ICON_PASTE( DALI_IMAGE_DIR "copy_paste_icon_paste.png" );
63 const char* DEFAULT_ICON_SELECT( DALI_IMAGE_DIR "copy_paste_icon_select.png" );
64 const char* DEFAULT_ICON_SELECT_ALL( DALI_IMAGE_DIR "copy_paste_icon_select_all.png" );
66 const Vector4 DEFAULT_CURSOR_IMAGE_9_BORDER( 2.0f, 2.0f, 2.0f, 2.0f );
68 const std::string OPTION_SELECT_WORD("select_word"); ///< "Select Word" popup option.
69 const std::string OPTION_SELECT_ALL("select_all"); ///< "Select All" popup option.
70 const std::string OPTION_CUT("cut"); ///< "Cut" popup option.
71 const std::string OPTION_COPY("copy"); ///< "Copy" popup option.
72 const std::string OPTION_PASTE("paste"); ///< "Paste" popup option.
73 const std::string OPTION_CLIPBOARD("clipboard"); ///< "Clipboard" popup option.
75 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
76 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
77 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.0f ); ///< 1. Highlight rendered (z-offset).
78 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.1f ); ///< 2. Text rendered (z-offset).
79 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
81 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
82 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
83 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
84 const float TOP_HANDLE_TOP_OFFSET(-1.5f); ///< Offset between top handle and cutCopyPaste pop-up
85 const float BOTTOM_HANDLE_BOTTOM_OFFSET(1.5f); ///< Offset between bottom handle and cutCopyPaste pop-up
86 const float CURSOR_THICKNESS(6.0f);
87 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
89 const std::string NEWLINE( "\n" );
91 const TextStyle DEFAULT_TEXT_STYLE;
93 const unsigned int SCROLL_TICK_INTERVAL = 50u;
94 const float SCROLL_THRESHOLD = 10.f;
95 const float SCROLL_SPEED = 15.f;
98 * Whether the given style is the default style or not.
99 * @param[in] style The given style.
100 * @return \e true if the given style is the default. Otherwise it returns \e false.
102 bool IsDefaultStyle( const TextStyle& style )
104 return DEFAULT_TEXT_STYLE == style;
108 * Whether the given styled text is using the default style or not.
109 * @param[in] textArray The given text.
110 * @return \e true if the given styled text is using the default style. Otherwise it returns \e false.
112 bool IsTextDefaultStyle( const Toolkit::MarkupProcessor::StyledTextArray& textArray )
114 for( Toolkit::MarkupProcessor::StyledTextArray::const_iterator it = textArray.begin(), endIt = textArray.end(); it != endIt; ++it )
116 const TextStyle& style( (*it).mStyle );
118 if( !IsDefaultStyle( style ) )
128 * Selection state enumeration (FSM)
132 SelectionNone, ///< Currently not encountered selected section.
133 SelectionStarted, ///< Encountered selected section
134 SelectionFinished ///< Finished selected section
137 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
139 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
143 if( ( *it ).mIsVisible )
145 return --cursorPosition;
154 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
156 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
158 if( ( *it ).mIsVisible )
160 return cursorPosition;
166 return cursorPosition;
170 * Whether the given position plus the cursor size offset is inside the given boundary.
172 * @param[in] position The given position.
173 * @param[in] cursorSize The cursor size.
174 * @param[in] controlSize The given boundary.
176 * @return whether the given position is inside the given boundary.
178 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
180 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
181 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
182 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
183 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
187 * Splits a text in two halves.
189 * If the text's number of characters is odd, firstHalf has one more character.
191 * @param[in] text The text to be split.
192 * @param[out] firstHalf The first half of the text.
193 * @param[out] secondHalf The second half of the text.
195 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
196 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
197 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
202 const std::size_t textLength = text.size();
203 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
205 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
206 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
209 } // end of namespace
225 return Toolkit::TextInput::New();
228 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
230 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
231 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
232 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
233 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
234 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
235 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
239 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
241 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
243 QuadCoordinates quad(x1, y1, x2, y2);
244 mQuadList.push_back( quad );
247 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
249 for(std::size_t i = 0;i < mQuadList.size(); i++)
251 QuadCoordinates& quad = mQuadList[i];
253 quad.min.Clamp(min, max);
254 quad.max.Clamp(min, max);
258 // [TextInput] ////////////////////////////////////////////////////////////////
260 Dali::Toolkit::TextInput TextInput::New()
262 // Create the implementation
263 TextInputPtr textInput(new TextInput());
264 // Pass ownership to CustomActor via derived handle
265 Dali::Toolkit::TextInput handle(*textInput);
267 textInput->Initialize();
272 TextInput::TextInput()
273 :ControlImpl( true ),
278 mDisplayedTextView(),
279 mStyledPlaceHolderText(),
280 mMaxStringLength( DEFAULT_MAX_SIZE ),
281 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
282 mCursorPosition( 0 ),
283 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
284 mIsSelectionHandleOneFlipped( false ),
285 mIsSelectionHandleTwoFlipped( false ),
286 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
287 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
288 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
289 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
290 mSelectionHandleOnePosition( 0 ),
291 mSelectionHandleTwoPosition( 0 ),
293 mPreEditStartPosition( 0 ),
294 mPreEditLength ( 0 ),
295 mNumberOfSurroundingCharactersDeleted( 0 ),
296 mTouchStartTime( 0 ),
298 mCurrentCopySelecton(),
300 mScrollDisplacement(),
301 mCurrentHandlePosition(),
302 mCurrentSelectionId(),
303 mCurrentSelectionHandlePosition(),
304 mRequestedSelection( 0, 0 ),
305 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
306 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
308 mOverrideAutomaticAlignment( false ),
309 mCursorRTLEnabled( false ),
310 mClosestCursorPositionEOL ( false ),
311 mCursorBlinkStatus( true ),
312 mCursorVisibility( false ),
313 mGrabHandleVisibility( false ),
314 mIsCursorInScrollArea( true ),
315 mIsGrabHandleInScrollArea( true ),
316 mEditModeActive( false ),
317 mEditOnTouch( true ),
318 mTextSelection( true ),
319 mExceedEnabled( true ),
320 mGrabHandleEnabled( true ),
321 mIsSelectionHandleFlipEnabled( true ),
322 mPreEditFlag( false ),
323 mIgnoreCommitFlag( false ),
324 mIgnoreFirstCommitFlag( false ),
325 mSelectingText( false ),
326 mPreserveCursorPosition( false ),
327 mSelectTextOnCommit( false ),
328 mUnderlinedPriorToPreEdit ( false ),
329 mCommitByKeyInput( false ),
330 mPlaceHolderSet( false ),
331 mMarkUpEnabled( true )
333 // Updates the line height accordingly with the input style.
337 TextInput::~TextInput()
339 StopCursorBlinkTimer();
344 std::string TextInput::GetText() const
348 // Return text-view's text only if the text-input's text is not empty
349 // in order to not to return the placeholder text.
350 if( !mStyledText.empty() )
352 text = mDisplayedTextView.GetText();
358 std::string TextInput::GetMarkupText() const
360 std::string markupString;
361 MarkupProcessor::GetMarkupString( mStyledText, markupString );
366 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
368 // Get the placeholder styled text array from the markup string.
369 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
371 if( mStyledText.empty() )
373 // Set the placeholder text only if the styled text is empty.
374 mDisplayedTextView.SetText( mStyledPlaceHolderText );
375 mPlaceHolderSet = true;
379 std::string TextInput::GetPlaceholderText()
381 // Traverses the styled placeholder array getting only the text.
382 // Note that for some languages a 'character' could be represented by more than one 'char'
384 std::string placeholderText;
385 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
387 placeholderText.append( (*it).mText.GetText() );
390 return placeholderText ;
393 void TextInput::SetInitialText(const std::string& initialText)
395 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
397 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
399 mPreEditFlag = false;
400 mIgnoreCommitFlag = true;
403 SetText( initialText );
404 PreEditReset( false ); // Reset keyboard as text changed
407 void TextInput::SetText(const std::string& initialText)
409 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
411 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
413 if( mStyledText.empty() )
415 // If the initial text is empty, set the placeholder text.
416 mDisplayedTextView.SetText( mStyledPlaceHolderText );
417 mPlaceHolderSet = true;
421 mDisplayedTextView.SetText( mStyledText );
422 mPlaceHolderSet = false;
427 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
429 ImfManager imfManager = ImfManager::Get();
432 imfManager.SetCursorPosition( mCursorPosition );
433 imfManager.SetSurroundingText( initialText );
434 imfManager.NotifyCursorPosition();
437 if( IsScrollEnabled() )
439 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
442 ShowGrabHandleAndSetVisibility( false );
449 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
451 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
453 mDisplayedTextView.SetText( styleText );
454 mPlaceHolderSet = false;
456 // If text alignment hasn't been manually set by application developer, then we
457 // automatically determine the alignment based on the content of the text i.e. what
458 // language the text begins with.
459 // TODO: This should determine different alignments for each line (broken by '\n') of text.
460 if(!mOverrideAutomaticAlignment)
462 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
463 bool leftToRight(true);
465 if( !styleText.empty() )
467 bool breakOut(false);
469 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
471 const Text& text = textIter->mText;
473 for( std::size_t i = 0; i < text.GetLength(); ++i )
475 Character character( text[i] );
476 if( character.GetCharacterDirection() != Character::Neutral )
478 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
486 // Based on this direction, either left or right align text if not manually set by application developer.
487 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
488 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
489 Toolkit::Alignment::VerticalTop ) );
490 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
494 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
496 mMaxStringLength = maxChars;
499 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
501 DALI_ASSERT_DEBUG( maxLines > 0 )
505 mNumberOflinesLimit = maxLines;
509 std::size_t TextInput::GetNumberOfLinesLimit() const
511 return mNumberOflinesLimit;
514 std::size_t TextInput::GetNumberOfCharacters() const
516 return mStyledText.size();
519 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
521 return mInputStartedSignalV2;
524 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
526 return mInputFinishedSignalV2;
529 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
531 return mCutAndPasteToolBarDisplayedV2;
534 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
536 return mStyleChangedSignalV2;
539 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
541 return mMaxInputCharactersReachedSignalV2;
544 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
546 return mInputTextExceedBoundariesSignalV2;
549 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
551 Dali::BaseHandle handle( object );
553 bool connected( true );
554 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
556 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
558 textInput.InputStartedSignal().Connect( tracker, functor );
560 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
562 textInput.InputFinishedSignal().Connect( tracker, functor );
564 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
566 textInput.StyleChangedSignal().Connect( tracker, functor );
568 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
570 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
572 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
574 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
578 // signalName does not match any signal
585 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
589 // update line height before calculate the actual position.
594 if( setCursorOnTouchPoint )
596 // Sets the cursor position for the given touch point.
597 ReturnClosestIndex( touchPoint, mCursorPosition );
599 // Creates the grab handle.
600 if( IsGrabHandleEnabled() )
602 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
606 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
607 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
608 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
609 ShowGrabHandleAndSetVisibility( true );
611 // Scrolls the text-view if needed.
612 if( IsScrollEnabled() )
614 ScrollTextViewToMakeCursorVisible( cursorPosition );
620 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
632 bool TextInput::IsEditable() const
634 return mEditModeActive;
637 void TextInput::SetEditOnTouch( bool editOnTouch )
639 mEditOnTouch = editOnTouch;
642 bool TextInput::IsEditOnTouch() const
647 void TextInput::SetTextSelectable( bool textSelectable )
649 mTextSelection = textSelectable;
652 bool TextInput::IsTextSelectable() const
654 return mTextSelection;
657 bool TextInput::IsTextSelected() const
659 return mHighlightMeshActor;
662 void TextInput::DeSelectText()
669 void TextInput::SetGrabHandleImage(Dali::Image image )
673 CreateGrabHandle(image);
677 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
679 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
683 mCursor.SetImage( image );
684 mCursor.SetNinePatchBorder( border );
688 Vector3 TextInput::GetSelectionHandleSize()
690 return DEFAULT_SELECTION_HANDLE_SIZE;
693 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
695 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
699 mCursorRTL.SetImage( image);
700 mCursorRTL.SetNinePatchBorder( border );
704 void TextInput::EnableGrabHandle(bool toggle)
706 // enables grab handle with will in turn de-activate magnifier
707 mGrabHandleEnabled = toggle;
710 bool TextInput::IsGrabHandleEnabled()
712 // if false then magnifier will be shown instead.
713 return mGrabHandleEnabled;
716 void TextInput::EnableSelectionHandleFlip( bool toggle )
718 // Deprecated function. To be removed.
719 mIsSelectionHandleFlipEnabled = toggle;
722 bool TextInput::IsSelectionHandleFlipEnabled()
724 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
728 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
730 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
731 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
732 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
734 mSelectionHandleFlipMargin = margin;
737 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
739 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
740 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
742 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
743 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
745 const Vector4 boundary( originX,
747 originX + boundingRectangle.width,
748 originY + boundingRectangle.height );
750 mBoundingRectangleWorldCoordinates = boundary;
753 const Rect<float> TextInput::GetBoundingRectangle() const
755 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
757 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
758 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
760 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
765 const Vector4& TextInput::GetSelectionHandleFlipMargin()
767 return mSelectionHandleFlipMargin;
770 void TextInput::SetTextColor( const Vector4& color )
772 mDisplayedTextView.SetColor( color );
775 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
777 if( style != mInputStyle )
780 bool emitSignal = false;
782 // mask: modify style according to mask, if different emit signal.
783 const TextStyle oldInputStyle( mInputStyle );
785 // Copy the new style.
786 mInputStyle.Copy( style, mask );
788 // if style has changed, emit signal.
789 if( oldInputStyle != mInputStyle )
794 // Updates the line height accordingly with the input style.
797 // Changing font point size will require the cursor to be re-sized
802 EmitStyleChangedSignal();
807 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
809 if ( IsTextSelected() )
811 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
812 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
814 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
816 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
819 // Keeps the old style to be compared with the new one.
820 const TextStyle oldInputStyle( mInputStyle );
822 // Copy only those parameters from the style which are set in the mask.
823 mInputStyle.Copy( style, mask );
825 if( mInputStyle != oldInputStyle )
827 // Updates the line height accordingly with the input style.
830 EmitStyleChangedSignal();
835 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
837 if( !mStyledText.empty() )
839 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
843 TextStyle TextInput::GetStyleAtCursor() const
847 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
849 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
850 style = mStyledText.at( mCursorPosition-1 ).mStyle;
856 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
858 Dali::Font defaultFont = Dali::Font::New();
859 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
866 TextStyle TextInput::GetStyleAt( std::size_t position ) const
868 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
870 if( position >= mStyledText.size() )
872 position = mStyledText.size() - 1;
875 return mStyledText.at( position ).mStyle;
878 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
880 mDisplayedTextView.SetTextAlignment( align );
881 mOverrideAutomaticAlignment = true;
884 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
886 mDisplayedTextView.SetLineJustification( justification );
887 mOverrideAutomaticAlignment = true;
890 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
892 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
895 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
897 return mDisplayedTextView.GetFadeBoundary();
900 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
902 return mDisplayedTextView.GetTextAlignment();
905 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
907 mDisplayedTextView.SetMultilinePolicy( policy );
910 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
912 return mDisplayedTextView.GetMultilinePolicy();
915 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
917 mDisplayedTextView.SetWidthExceedPolicy( policy );
920 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
922 return mDisplayedTextView.GetWidthExceedPolicy();
925 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
927 mDisplayedTextView.SetHeightExceedPolicy( policy );
930 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
932 return mDisplayedTextView.GetHeightExceedPolicy();
935 void TextInput::SetExceedEnabled( bool enable )
937 mExceedEnabled = enable;
940 bool TextInput::GetExceedEnabled() const
942 return mExceedEnabled;
945 void TextInput::SetBackground(Dali::Image image )
947 // TODO Should add this function and add public api to match.
950 bool TextInput::OnTouchEvent(const TouchEvent& event)
955 bool TextInput::OnKeyEvent(const KeyEvent& event)
957 switch( event.state )
961 return OnKeyDownEvent(event);
967 return OnKeyUpEvent(event);
979 void TextInput::OnKeyInputFocusGained()
981 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
983 mEditModeActive = true;
985 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
987 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
989 // Updates the line height accordingly with the input style.
992 // Connect the signals to use in text input.
993 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
994 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
996 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1002 SetCursorVisibility( true );
1003 StartCursorBlinkTimer();
1005 Toolkit::TextInput handle( GetOwner() );
1006 mInputStartedSignalV2.Emit( handle );
1008 ImfManager imfManager = ImfManager::Get();
1012 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1014 // Notify that the text editing start.
1015 imfManager.Activate();
1017 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1018 imfManager.SetRestoreAferFocusLost( true );
1020 imfManager.SetCursorPosition( mCursorPosition );
1021 imfManager.NotifyCursorPosition();
1024 mClipboard = Clipboard::Get(); // Store handle to clipboard
1026 // Now in edit mode we can accept string to paste from clipboard
1027 if( Adaptor::IsAvailable() )
1029 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1032 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1037 void TextInput::OnKeyInputFocusLost()
1039 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1043 // If key input focus is lost, it removes the
1044 // underline from the last pre-edit text.
1045 RemovePreEditStyle();
1046 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1047 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1050 ImfManager imfManager = ImfManager::Get();
1053 // The text editing is finished. Therefore the imf manager don't have restore activation.
1054 imfManager.SetRestoreAferFocusLost( false );
1056 // Notify that the text editing finish.
1057 imfManager.Deactivate();
1059 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1061 // Disconnect signal used the text input.
1062 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1064 Toolkit::TextInput handle( GetOwner() );
1065 mInputFinishedSignalV2.Emit( handle );
1066 mEditModeActive = false;
1067 mPreEditFlag = false;
1069 SetCursorVisibility( false );
1070 StopCursorBlinkTimer();
1072 ShowGrabHandleAndSetVisibility( false );
1075 // No longer in edit mode so do not want to receive string from clipboard
1076 if( Adaptor::IsAvailable() )
1078 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1081 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1083 Clipboard clipboard = Clipboard::Get();
1087 clipboard.HideClipboard();
1092 void TextInput::OnControlStageConnection()
1094 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1096 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1098 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1102 void TextInput::CreateActiveLayer()
1104 Actor self = Self();
1105 mActiveLayer = Layer::New();
1107 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1108 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1109 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1111 self.Add( mActiveLayer );
1112 mActiveLayer.RaiseToTop();
1115 void TextInput::OnInitialize()
1117 CreateTextViewActor();
1121 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1122 // different positions depending on language)
1123 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1124 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1125 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1127 Actor self = Self();
1128 self.Add( mCursor );
1129 self.Add( mCursorRTL );
1131 mCursorVisibility = false;
1133 CreateActiveLayer(); // todo move this so layer only created when needed.
1135 // Assign names to image actors
1136 mCursor.SetName("mainCursor");
1137 mCursorRTL.SetName("rtlCursor");
1140 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1142 mDisplayedTextView.SetSize( targetSize );
1143 GetTextLayoutInfo();
1144 mActiveLayer.SetSize(targetSize);
1147 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1149 Relayout( mDisplayedTextView, size, container );
1150 GetTextLayoutInfo();
1155 Vector3 TextInput::GetNaturalSize()
1157 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1159 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1161 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1162 naturalSize.height = mLineHeight;
1168 float TextInput::GetHeightForWidth( float width )
1170 float height = mDisplayedTextView.GetHeightForWidth( width );
1172 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1174 // If the height is zero, it means there is no text. Let's return the cursor height.
1175 height = mLineHeight;
1181 /*end of Virtual methods from parent*/
1183 // Private Internal methods
1185 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1187 switch (gesture.state)
1189 case Gesture::Started:
1190 // fall through so code not duplicated
1191 case Gesture::Continuing:
1193 if (actor == mGrabArea)
1195 SetCursorVisibility( true );
1196 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1197 MoveGrabHandle( gesture.displacement );
1198 HidePopup(); // Do not show popup whilst handle is moving
1200 else if (actor == mHandleOneGrabArea)
1202 // the displacement in PanGesture is affected by the actor's rotation.
1203 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1204 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1206 MoveSelectionHandle( HandleOne, gesture.displacement );
1208 mState = StateDraggingHandle;
1211 else if (actor == mHandleTwoGrabArea)
1213 // the displacement in PanGesture is affected by the actor's rotation.
1214 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1215 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1217 MoveSelectionHandle( HandleTwo, gesture.displacement );
1219 mState = StateDraggingHandle;
1225 case Gesture::Finished:
1227 // Revert back to non-pressed selection handle images
1228 if (actor == mGrabArea)
1230 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1231 SetCursorVisibility( true );
1232 SetUpPopUpSelection();
1235 if (actor == mHandleOneGrabArea)
1237 // the displacement in PanGesture is affected by the actor's rotation.
1238 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1239 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1241 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1243 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1245 ShowPopupCutCopyPaste();
1247 if (actor == mHandleTwoGrabArea)
1249 // the displacement in PanGesture is affected by the actor's rotation.
1250 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1251 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1253 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1255 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1257 ShowPopupCutCopyPaste();
1266 // Stop the flashing animation so easy to see when moved.
1267 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1269 if (touch.GetPoint(0).state == TouchPoint::Down)
1271 SetCursorVisibility( true );
1272 StopCursorBlinkTimer();
1274 else if (touch.GetPoint(0).state == TouchPoint::Up)
1276 SetCursorVisibility( true );
1277 StartCursorBlinkTimer();
1282 // selection handle one
1283 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1285 if (touch.GetPoint(0).state == TouchPoint::Down)
1287 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1289 else if (touch.GetPoint(0).state == TouchPoint::Up)
1291 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1296 // selection handle two
1297 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1299 if (touch.GetPoint(0).state == TouchPoint::Down)
1301 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1303 else if (touch.GetPoint(0).state == TouchPoint::Up)
1305 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1310 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1312 // If text exists then select nearest word.
1313 if ( !mStyledText.empty())
1317 ShowGrabHandleAndSetVisibility( false );
1322 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1323 // converts the pre-edit word being displayed to a committed word.
1324 if ( !mUnderlinedPriorToPreEdit )
1327 style.SetUnderline( false );
1328 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1330 mPreEditFlag = false;
1331 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1332 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1333 PreEditReset( false );
1335 mCursorPosition = 0;
1337 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1338 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1340 ImfManager imfManager = ImfManager::Get();
1343 imfManager.SetCursorPosition ( mCursorPosition );
1344 imfManager.NotifyCursorPosition();
1347 std::size_t start = 0;
1348 std::size_t end = 0;
1349 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1351 SelectText( start, end );
1353 // if no text but clipboard has content then show paste option
1354 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1356 ShowPopupCutCopyPaste();
1359 // If no text and clipboard empty then do nothing
1362 // TODO: Change the function name to be more general.
1363 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1365 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1366 , (mEditOnTouch)?"true":"false"
1367 , (mEditModeActive)?"true":"false");
1369 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1374 if( mGrabArea == actor )
1376 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1378 SetUpPopUpSelection();
1388 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1390 // Initially don't create the grab handle.
1391 bool createGrabHandle = false;
1393 if ( !mEditModeActive )
1395 // update line height before calculate the actual position.
1398 // Only start edit mode if TextInput configured to edit on touch
1401 // Set the initial cursor position in the tap point.
1402 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1404 // Create the grab handle.
1405 // TODO Make this a re-usable function.
1406 if ( IsGrabHandleEnabled() )
1408 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1412 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1413 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1414 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1415 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1419 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1420 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1421 // otherwise the Grab handle will be shown when selecting.
1428 // Show the keyboard if it was hidden.
1429 if (!VirtualKeyboard::IsVisible())
1431 VirtualKeyboard::Show();
1434 // Reset keyboard as tap event has occurred.
1435 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1436 PreEditReset( true );
1438 GetTextLayoutInfo();
1440 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1442 // 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.
1444 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1446 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1448 // Notify keyboard so it can 're-capture' word for predictive text.
1449 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1450 ImfManager imfManager = ImfManager::Get();
1453 imfManager.SetCursorPosition ( mCursorPosition );
1454 imfManager.NotifyCursorPosition();
1456 const TextStyle oldInputStyle( mInputStyle );
1458 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1462 // Create the grab handle.
1463 // Grab handle is created later.
1464 createGrabHandle = true;
1466 if( oldInputStyle != mInputStyle )
1468 // Updates the line height accordingly with the input style.
1471 EmitStyleChangedSignal();
1476 if ( createGrabHandle && IsGrabHandleEnabled() )
1478 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1482 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1483 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1484 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1485 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1490 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1492 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1494 if(longPress.state == Dali::Gesture::Started)
1496 // Start edit mode on long press
1497 if ( !mEditModeActive )
1502 // If text exists then select nearest word.
1503 if ( !mStyledText.empty())
1507 ShowGrabHandleAndSetVisibility( false );
1512 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1513 // converts the pre-edit word being displayed to a committed word.
1514 if ( !mUnderlinedPriorToPreEdit )
1517 style.SetUnderline( false );
1518 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1520 mPreEditFlag = false;
1521 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1522 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1523 PreEditReset( false );
1525 mCursorPosition = 0;
1527 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1528 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1530 ImfManager imfManager = ImfManager::Get();
1533 imfManager.SetCursorPosition ( mCursorPosition );
1534 imfManager.NotifyCursorPosition();
1536 std::size_t start = 0;
1537 std::size_t end = 0;
1538 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1540 SelectText( start, end );
1543 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1544 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1546 ShowPopupCutCopyPaste();
1551 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1553 const Text clipboardText( notifier.GetContent() );
1554 PasteText( clipboardText );
1556 SetCursorVisibility( true );
1557 StartCursorBlinkTimer();
1559 ShowGrabHandleAndSetVisibility( false );
1565 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1567 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1569 const std::string& name = button.GetName();
1571 if(name == OPTION_SELECT_WORD)
1573 std::size_t start = 0;
1574 std::size_t end = 0;
1575 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1577 SelectText( start, end );
1579 else if(name == OPTION_SELECT_ALL)
1581 SetCursorVisibility(false);
1582 StopCursorBlinkTimer();
1584 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1585 std::size_t start = 0;
1587 SelectText( start, end );
1589 else if(name == OPTION_CUT)
1591 bool ret = CopySelectedTextToClipboard();
1595 DeleteHighlightedText( true );
1599 SetCursorVisibility( true );
1600 StartCursorBlinkTimer();
1604 else if(name == OPTION_COPY)
1606 CopySelectedTextToClipboard();
1610 SetCursorVisibility( true );
1611 StartCursorBlinkTimer();
1615 else if(name == OPTION_PASTE)
1617 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1619 PasteText(retrievedString);
1621 SetCursorVisibility( true );
1622 StartCursorBlinkTimer();
1624 ShowGrabHandleAndSetVisibility( false );
1629 else if(name == OPTION_CLIPBOARD)
1631 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1632 // Hence pass the false parameter for signalFinished.
1633 HidePopup( true, false );
1634 mClipboard.ShowClipboard();
1640 bool TextInput::OnCursorBlinkTimerTick()
1643 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1644 if ( mCursorRTLEnabled )
1646 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1648 mCursorBlinkStatus = !mCursorBlinkStatus;
1653 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1655 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1657 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1658 if(mHighlightMeshActor && mState == StateEdit)
1660 ShowPopupCutCopyPaste();
1664 //FIXME this routine needs to be re-written as it contains too many branches.
1665 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1667 std::string keyName = event.keyPressedName;
1668 std::string keyString = event.keyPressed;
1670 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1672 // Do not consume "Tab" and "Escape" keys.
1673 if(keyName == "Tab" || keyName == "Escape")
1675 // Escape key to end the edit mode
1681 HidePopup(); // If Pop-up shown then hides it as editing text.
1683 // Update Flag, indicates whether to update the text-input contents or not.
1684 // Any key stroke that results in a visual change of the text-input should
1685 // set this flag to true.
1688 // Whether to scroll text to cursor position.
1689 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1690 bool scroll = false;
1692 if (keyName == "Return")
1694 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1696 bool preEditFlagPreviouslySet( mPreEditFlag );
1698 if (mHighlightMeshActor)
1700 // replaces highlighted text with new line
1701 DeleteHighlightedText( false );
1703 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1705 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1706 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1709 mCommitByKeyInput = true;
1712 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1713 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1715 mPreEditFlag = true;
1716 mIgnoreCommitFlag = false;
1726 else if ( keyName == "space" )
1728 if ( mHighlightMeshActor )
1730 // Some text is selected so erase it before adding space.
1731 DeleteHighlightedText( true );
1735 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1737 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1738 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1741 mCommitByKeyInput = true;
1746 else if (keyName == "BackSpace")
1748 if ( mHighlightMeshActor )
1750 // Some text is selected so erase it
1751 DeleteHighlightedText( true );
1756 if ( mCursorPosition > 0 )
1758 DeleteCharacter( mCursorPosition );
1763 else if (keyName == "Right")
1768 else if (keyName == "Left")
1770 AdvanceCursor(true);
1773 else // event is a character
1775 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1776 if ( !keyString.empty() )
1778 if ( mHighlightMeshActor )
1780 // replaces highlighted text with new character
1781 DeleteHighlightedText( false );
1785 // Received key String
1786 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1791 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1792 // as this is a costly operation.
1798 if(update || scroll)
1800 if( IsScrollEnabled() )
1802 // Calculates the new cursor position (in actor coordinates)
1803 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1805 ScrollTextViewToMakeCursorVisible( cursorPosition );
1812 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1814 std::string keyName = event.keyPressedName;
1815 std::string keyString = event.keyPressed;
1817 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1819 // The selected text become deselected when the key code is DALI_KEY_BACK.
1820 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1829 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1831 // Updates the stored scroll position.
1832 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1834 const Vector3& controlSize = GetControlSize();
1835 Size cursorSize( CURSOR_THICKNESS, 0.f );
1837 // Updates the cursor and grab handle position and visibility.
1838 if( mGrabHandle || mCursor )
1840 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1841 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1843 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1845 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1849 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1850 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1855 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1856 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1860 // Updates the selection handles and highlighted text position and visibility.
1861 if( mSelectionHandleOne && mSelectionHandleTwo )
1863 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1864 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1865 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1866 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1867 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1868 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1870 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1871 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1873 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1874 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1875 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1876 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1878 if( mHighlightMeshActor )
1880 mHighlightMeshActor.SetVisible( true );
1886 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1888 // Scroll the text to make the cursor visible.
1889 const Size cursorSize( CURSOR_THICKNESS,
1890 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1892 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1894 const Vector3& controlSize = GetControlSize();
1896 // Calculates the new scroll position.
1897 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1898 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1900 scrollOffset.x += cursorPosition.x;
1903 if( cursorPosition.y - cursorSize.height < 0.f )
1905 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1907 else if( cursorPosition.y > controlSize.height )
1909 scrollOffset.y += cursorPosition.y;
1912 // Sets the new scroll position.
1913 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1914 SetScrollPosition( scrollOffset );
1917 void TextInput::StartScrollTimer()
1921 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1922 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1925 if( !mScrollTimer.IsRunning() )
1927 mScrollTimer.Start();
1931 void TextInput::StopScrollTimer()
1935 mScrollTimer.Stop();
1939 bool TextInput::OnScrollTimerTick()
1941 // TODO: need to set the new style accordingly the new handle position.
1943 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1945 // nothing to do if all handles are invisible or doesn't exist.
1951 // Choose between the grab handle or the selection handles.
1952 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1953 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1954 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1956 std::size_t newCursorPosition = 0;
1957 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1959 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1960 // the new selection handle's position needs to be different of the other one.
1961 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1962 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1963 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1965 if( differentSelectionHandles )
1967 handlePosition = newCursorPosition;
1969 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1971 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1973 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1974 scrollPosition += scrollDelta;
1975 SetScrollPosition( scrollPosition );
1977 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1982 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1985 actualHandlePosition.x += mScrollDisplacement.x;
1986 actualHandlePosition.y += mScrollDisplacement.y;
1991 // Public Internal Methods (public for testing purpose)
1993 void TextInput::SetUpTouchEvents()
1995 if ( !mTapDetector )
1997 mTapDetector = TapGestureDetector::New();
1998 // Attach the actors and connect the signal
1999 mTapDetector.Attach(Self());
2001 // As contains children which may register for tap the default control detector is not used.
2002 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2005 if ( !mDoubleTapDetector )
2007 mDoubleTapDetector = TapGestureDetector::New();
2008 mDoubleTapDetector.SetTapsRequired( 2 );
2009 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2011 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2012 // so that we do not, unnecessarily, have a double tap request all the time
2015 if ( !mPanGestureDetector )
2017 mPanGestureDetector = PanGestureDetector::New();
2018 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2021 if ( !mLongPressDetector )
2023 mLongPressDetector = LongPressGestureDetector::New();
2024 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2025 mLongPressDetector.Attach(Self());
2029 void TextInput::CreateTextViewActor()
2031 mDisplayedTextView = Toolkit::TextView::New();
2032 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2033 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2034 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2035 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2036 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2037 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2038 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2039 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2040 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2041 mDisplayedTextView.SetSizePolicy( Control::Fixed, Control::Fixed );
2043 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2045 Self().Add( mDisplayedTextView );
2048 // Start a timer to initiate, used by the cursor to blink.
2049 void TextInput::StartCursorBlinkTimer()
2051 if ( !mCursorBlinkTimer )
2053 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2054 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2057 if ( !mCursorBlinkTimer.IsRunning() )
2059 mCursorBlinkTimer.Start();
2063 // Start a timer to initiate, used by the cursor to blink.
2064 void TextInput::StopCursorBlinkTimer()
2066 if ( mCursorBlinkTimer )
2068 mCursorBlinkTimer.Stop();
2072 void TextInput::StartEditMode()
2074 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2076 if(!mEditModeActive)
2081 if ( mDoubleTapDetector )
2083 mDoubleTapDetector.Attach( Self() );
2087 void TextInput::EndEditMode()
2089 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2091 ClearKeyInputFocus();
2093 if ( mDoubleTapDetector )
2095 mDoubleTapDetector.Detach( Self() );
2099 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2101 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2103 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2105 style.SetUnderline( true );
2106 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2110 void TextInput::RemovePreEditStyle()
2112 if ( !mUnderlinedPriorToPreEdit )
2115 style.SetUnderline( false );
2116 SetActiveStyle( style, TextStyle::UNDERLINE );
2120 // IMF related methods
2123 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2125 bool update( false );
2126 bool preeditResetRequired ( false );
2128 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2130 HidePopup(); // If Pop-up shown then hides it as editing text.
2133 switch ( imfEvent.eventName )
2135 case ImfManager::PREEDIT:
2137 mIgnoreFirstCommitFlag = false;
2139 // 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
2140 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2142 // replaces highlighted text with new character
2143 DeleteHighlightedText( false );
2146 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2148 if( IsScrollEnabled() )
2150 // Calculates the new cursor position (in actor coordinates)
2151 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2152 ScrollTextViewToMakeCursorVisible( cursorPosition );
2159 case ImfManager::COMMIT:
2161 if( mIgnoreFirstCommitFlag )
2163 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2164 mIgnoreFirstCommitFlag = false;
2168 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2170 // 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
2171 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2173 // replaces highlighted text with new character
2174 DeleteHighlightedText( false );
2177 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2178 // not needed, one such scenario is when the pre-edit word is too long to fit.
2179 if ( !mIgnoreCommitFlag )
2181 update = CommitReceived( imfEvent.predictiveString );
2185 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2191 if( IsScrollEnabled() )
2193 // Calculates the new cursor position (in actor coordinates)
2194 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2196 ScrollTextViewToMakeCursorVisible( cursorPosition );
2201 case ImfManager::DELETESURROUNDING:
2203 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2204 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2206 mPreEditFlag = false;
2208 std::size_t toDelete = 0;
2209 std::size_t numberOfCharacters = 0;
2211 if( mHighlightMeshActor )
2213 // delete highlighted text.
2214 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2215 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2219 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2221 toDelete = mCursorPosition + imfEvent.cursorOffset;
2223 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2225 numberOfCharacters = mStyledText.size() - toDelete;
2229 numberOfCharacters = imfEvent.numberOfChars;
2232 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2233 DeleteRange( toDelete, numberOfCharacters );
2235 mCursorPosition = toDelete;
2236 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2238 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2241 case ImfManager::GETSURROUNDING:
2243 // 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
2244 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2245 if (! ( mHighlightMeshActor || mSelectingText ) )
2247 std::string text( GetText() );
2248 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2250 imfManager.SetCursorPosition( mCursorPosition );
2251 imfManager.SetSurroundingText( text );
2254 if( 0 != mNumberOfSurroundingCharactersDeleted )
2256 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2257 mNumberOfSurroundingCharactersDeleted = 0;
2259 if( mStyledText.empty() )
2261 // Styled text is empty, so set the placeholder text.
2262 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2263 mPlaceHolderSet = true;
2268 case ImfManager::VOID:
2270 DALI_ASSERT_DEBUG( false );
2274 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2276 return callbackData;
2279 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2281 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2283 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2284 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2286 bool preeditResetRequest ( false );
2288 if( mPreEditFlag ) // Already in pre-edit state.
2290 if( mStyledText.size() >= mMaxStringLength )
2292 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2293 // Cannot fit these characters into field, clear pre-edit.
2294 if ( !mUnderlinedPriorToPreEdit )
2297 style.SetUnderline( false );
2298 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2300 mIgnoreCommitFlag = true;
2301 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2302 mPreEditFlag = false;
2303 EmitMaxInputCharactersReachedSignal();
2307 // delete existing pre-edit string
2308 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2310 // Store new pre-edit string
2311 mPreEditString.SetText( keyString );
2313 if ( keyString.empty() )
2315 mPreEditFlag = false;
2316 mCursorPosition = mPreEditStartPosition;
2318 if( mStyledText.empty() )
2320 // Styled text is empty, so set the placeholder text.
2321 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2322 mPlaceHolderSet = true;
2326 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2328 GetTextLayoutInfo();
2332 // Insert new pre-edit string. InsertAt updates the size and position table.
2333 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2334 // 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.
2335 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2336 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2337 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2339 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2343 else // mPreEditFlag not set
2345 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2347 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2348 // new pre-edit so move into pre-edit state by setting flag
2349 mPreEditFlag = true;
2350 mPreEditString.SetText( keyString ); // store new pre-edit string
2351 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2352 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2353 // 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.
2354 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2355 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2356 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2358 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2363 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2367 return preeditResetRequest;
2370 bool TextInput::CommitReceived(const std::string& keyString )
2372 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2373 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2375 bool update( false );
2377 RemovePreEditStyle();
2379 const std::size_t styledTextSize( mStyledText.size() );
2380 if( styledTextSize >= mMaxStringLength )
2382 // Cannot fit these characters into field, clear pre-edit.
2385 mIgnoreCommitFlag = true;
2386 mPreEditFlag = false;
2388 EmitMaxInputCharactersReachedSignal();
2394 // delete existing pre-edit string
2395 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2396 mPreEditFlag = false;
2398 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2399 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2401 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2403 // No need to update cursor position as Cursor location given by touch.
2404 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2405 mPreserveCursorPosition = false;
2409 // Cursor not set by touch so needs to be re-positioned to input more text
2410 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2412 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2413 if ( mCommitByKeyInput )
2415 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2416 mCommitByKeyInput = false;
2420 if ( mSelectTextOnCommit )
2422 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2427 else // mPreEditFlag not set
2429 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2431 if( mStyledText.empty() && mPlaceHolderSet )
2433 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2434 mDisplayedTextView.SetText( "" );
2435 mNumberOfSurroundingCharactersDeleted = 0;
2436 mPlaceHolderSet = false;
2438 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2440 mNumberOfSurroundingCharactersDeleted = 0;
2444 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2449 mSelectTextOnCommit = false;
2451 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2452 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2457 // End of IMF related methods
2459 std::size_t TextInput::DeletePreEdit()
2461 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2463 DALI_ASSERT_DEBUG( mPreEditFlag );
2465 const std::size_t preEditStringLength = mPreEditString.GetLength();
2466 const std::size_t styledTextSize = mStyledText.size();
2468 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2470 // Prevents erase items outside mStyledText bounds.
2471 if( mPreEditStartPosition > styledTextSize )
2473 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2474 mPreEditStartPosition = styledTextSize;
2477 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2479 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2480 endPosition = styledTextSize;
2483 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2485 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2486 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2488 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2490 return preEditStringLength;
2493 void TextInput::PreEditReset( bool preserveCursorPosition )
2495 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2496 preserveCursorPosition, mCursorPosition);
2498 // 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.
2499 mPreserveCursorPosition = preserveCursorPosition;
2501 // Reset incase we are in a pre-edit state.
2502 ImfManager imfManager = ImfManager::Get();
2505 imfManager.Reset(); // Will trigger a commit message
2509 void TextInput::CursorUpdate()
2513 ImfManager imfManager = ImfManager::Get();
2516 std::string text( GetText() );
2517 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2518 imfManager.SetCursorPosition ( mCursorPosition );
2519 imfManager.NotifyCursorPosition();
2523 /* Delete highlighted characters redisplay*/
2524 void TextInput::DeleteHighlightedText( bool inheritStyle )
2526 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2528 if(mHighlightMeshActor)
2530 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2532 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2533 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2535 // Get the styled text of the characters to be deleted as it may be needed if
2536 // the "exceed the text-input's boundaries" option is disabled.
2537 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2539 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2541 mStyledText.erase( start, end ); // erase range of characters
2543 // Remove text from TextView.
2545 if( mStyledText.empty() )
2547 // Styled text is empty, so set the placeholder text.
2548 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2549 mPlaceHolderSet = true;
2553 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2555 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2557 // It may happen than after removing a white space or a new line character,
2558 // two words merge, this new word could be big enough to not fit in its
2559 // current line, so moved to the next one, and make some part of the text to
2560 // exceed the text-input's boundary.
2561 if( !mExceedEnabled )
2563 // Get the new text layout after removing some characters.
2564 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2566 // Get text-input's size.
2567 const Vector3& size = GetControlSize();
2569 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2570 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2572 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2574 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2575 styledCharactersToDelete.begin(),
2576 styledCharactersToDelete.end() );
2580 GetTextLayoutInfo();
2586 const TextStyle oldInputStyle( mInputStyle );
2588 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2590 if( oldInputStyle != mInputStyle )
2592 // Updates the line height accordingly with the input style.
2595 EmitStyleChangedSignal();
2601 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2603 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2604 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2606 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2609 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2611 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2612 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2614 mStyledText.erase(itStart, itEnd);
2616 // update the selection handles if they are visible.
2617 if( mHighlightMeshActor )
2619 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2620 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2622 if( minHandle >= start + ncharacters )
2624 minHandle -= ncharacters;
2626 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2631 if( maxHandle >= start + ncharacters )
2633 maxHandle -= ncharacters;
2635 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2641 // 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.
2644 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2646 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2647 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2648 // Mean we do not re-draw the text more than we have too.
2651 /* Delete character at current cursor position and redisplay*/
2652 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2654 // Ensure positionToDelete is not out of bounds.
2655 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2656 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2657 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2659 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2662 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2664 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2666 // Get the styled text of the character to be deleted as it may be needed if
2667 // the "exceed the text-input's boundaries" option is disabled.
2668 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2670 mStyledText.erase(it); // erase the character left of positionToDelete
2672 if( mStyledText.empty() )
2674 // Styled text is empty, so set the placeholder text.
2675 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2676 mPlaceHolderSet = true;
2680 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2682 const Character characterToDelete = styledCharacterToDelete.mText[0];
2684 // It may happen than after removing a white space or a new line character,
2685 // two words merge, this new word could be big enough to not fit in its
2686 // current line, so moved to the next one, and make some part of the text to
2687 // exceed the text-input's boundary.
2688 if( !mExceedEnabled )
2690 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2692 // Get the new text layout after removing one character.
2693 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2695 // Get text-input's size.
2696 const Vector3& size = GetControlSize();
2698 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2699 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2701 MarkupProcessor::StyledTextArray array;
2702 array.push_back( styledCharacterToDelete );
2703 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2705 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2710 GetTextLayoutInfo();
2712 ShowGrabHandleAndSetVisibility( false );
2714 mCursorPosition = positionToDelete -1;
2716 const TextStyle oldInputStyle( mInputStyle );
2718 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2720 if( oldInputStyle != mInputStyle )
2722 // Updates the line height accordingly with the input style.
2725 EmitStyleChangedSignal();
2730 /*Insert new character into the string and (optionally) redisplay text-input*/
2731 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2733 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2735 // Ensure insertionPosition is not out of bounds.
2736 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2738 bool textExceedsMaximunNumberOfCharacters = false;
2739 bool textExceedsBoundary = false;
2740 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2742 ShowGrabHandleAndSetVisibility( false );
2744 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2748 mIgnoreCommitFlag = true;
2749 mPreEditFlag = false;
2750 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2751 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2754 if( textExceedsMaximunNumberOfCharacters )
2756 EmitMaxInputCharactersReachedSignal();
2759 if( textExceedsBoundary )
2761 EmitInputTextExceedsBoundariesSignal();
2762 PreEditReset( false );
2766 return insertedStringLength;
2769 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2775 cursor = ImageActor::New( cursorImage );
2779 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2782 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2783 cursor.SetNinePatchBorder( border );
2785 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2786 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2787 cursor.SetVisible(false);
2792 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2794 // As cursor is not moving due to grab handle, handle should be hidden.
2795 ShowGrabHandleAndSetVisibility( false );
2797 bool cursorPositionChanged = false;
2800 if ( mCursorPosition >= places )
2802 mCursorPosition = mCursorPosition - places;
2803 cursorPositionChanged = true;
2808 if ((mCursorPosition + places) <= mStyledText.size())
2810 mCursorPosition = mCursorPosition + places;
2811 cursorPositionChanged = true;
2815 if( cursorPositionChanged )
2817 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2819 const TextStyle oldInputStyle( mInputStyle );
2820 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2824 if( oldInputStyle != mInputStyle )
2826 // Updates the line height accordingly with the input style.
2829 EmitStyleChangedSignal();
2832 ImfManager imfManager = ImfManager::Get();
2835 imfManager.SetCursorPosition ( mCursorPosition );
2836 imfManager.NotifyCursorPosition();
2841 void TextInput::DrawCursor(const std::size_t nthChar)
2843 // Get height of cursor and set its size
2844 Size size( CURSOR_THICKNESS, 0.0f );
2845 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2847 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2851 // Measure Font so know how big text will be if no initial text to measure.
2852 size.height = mLineHeight;
2855 mCursor.SetSize(size);
2857 // If the character is italic then the cursor also tilts.
2858 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2860 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2862 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2864 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2865 bool altPositionValid; // Alternate cursor validity flag.
2866 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2867 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2869 SetAltCursorEnabled( altPositionValid );
2871 if(!altPositionValid)
2873 mCursor.SetPosition( position + UI_OFFSET );
2877 size.height *= 0.5f;
2878 mCursor.SetSize(size);
2879 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2881 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2882 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2883 size.height = rowSize.height * 0.5f;
2884 mCursorRTL.SetSize(size);
2885 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2888 if( IsScrollEnabled() )
2890 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2891 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2896 void TextInput::SetAltCursorEnabled( bool enabled )
2898 mCursorRTLEnabled = enabled;
2899 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2902 void TextInput::SetCursorVisibility( bool visible )
2904 mCursorVisibility = visible;
2905 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2906 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2909 void TextInput::CreateGrabHandle( Dali::Image image )
2915 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2919 mGrabHandleImage = image;
2922 mGrabHandle = ImageActor::New(mGrabHandleImage);
2923 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2924 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2926 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2928 ShowGrabHandleAndSetVisibility( false );
2930 CreateGrabArea( mGrabHandle );
2932 mActiveLayer.Add(mGrabHandle);
2936 void TextInput::CreateGrabArea( Actor& parent )
2938 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2939 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2940 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2941 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2942 mTapDetector.Attach( mGrabArea );
2943 mPanGestureDetector.Attach( mGrabArea );
2945 parent.Add(mGrabArea);
2948 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2950 Vector3 actualHandlePosition;
2954 mActualGrabHandlePosition.x += displacement.x;
2955 mActualGrabHandlePosition.y += displacement.y;
2957 // Grab handle should jump to the nearest character and take cursor with it
2958 std::size_t newCursorPosition = 0;
2959 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2961 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2963 bool handleVisible = true;
2965 if( IsScrollEnabled() )
2967 const Vector3 controlSize = GetControlSize();
2968 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
2969 // Scrolls the text if the handle is not in a visible position
2970 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2977 mCurrentHandlePosition = actualHandlePosition;
2978 mScrollDisplacement = Vector2::ZERO;
2982 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
2984 mScrollDisplacement.x = -SCROLL_SPEED;
2986 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
2988 mScrollDisplacement.x = SCROLL_SPEED;
2990 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
2992 mScrollDisplacement.y = -SCROLL_SPEED;
2994 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
2996 mScrollDisplacement.y = SCROLL_SPEED;
3002 if( handleVisible && // Only redraw cursor and do updates if position changed
3003 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3005 mCursorPosition = newCursorPosition;
3007 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3009 const TextStyle oldInputStyle( mInputStyle );
3011 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3013 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3015 if( oldInputStyle != mInputStyle )
3017 // Updates the line height accordingly with the input style.
3020 EmitStyleChangedSignal();
3025 return actualHandlePosition;
3028 void TextInput::ShowGrabHandle( bool visible )
3030 if ( IsGrabHandleEnabled() )
3034 mGrabHandle.SetVisible( mGrabHandleVisibility );
3036 StartMonitoringStageForTouch();
3040 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3042 mGrabHandleVisibility = visible;
3043 ShowGrabHandle( visible );
3046 // Callbacks connected to be Property notifications for Boundary checking.
3048 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3050 mIsSelectionHandleOneFlipped = true;
3051 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3052 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3055 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3057 mIsSelectionHandleOneFlipped = false;
3058 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3059 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3062 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3064 mIsSelectionHandleTwoFlipped = true;
3065 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3066 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3069 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3071 mIsSelectionHandleTwoFlipped = false;
3072 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3073 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3076 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3077 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3079 mSelectionHandleOne.SetOpacity(0.0f);
3082 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3084 mSelectionHandleOne.SetOpacity(1.0f);
3087 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3089 mSelectionHandleTwo.SetOpacity(0.0f);
3092 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3094 mSelectionHandleTwo.SetOpacity(1.0f);
3097 // End of Callbacks connected to be Property notifications for Boundary checking.
3099 void TextInput::SetUpHandlePropertyNotifications()
3101 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3103 Vector3 handlesize = GetSelectionHandleSize();
3105 // Exceeding horizontal boundary
3106 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3107 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3109 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3110 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3112 // Within horizontal boundary
3113 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3114 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3116 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3117 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3119 // Exceeding vertical boundary
3120 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3121 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3122 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3123 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3125 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3126 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3127 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3128 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3130 // Within vertical boundary
3131 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3132 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3133 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3134 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3136 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3137 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3138 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3139 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3142 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3144 mSelectionHandleOnePosition = start;
3145 mSelectionHandleTwoPosition = end;
3147 if ( !mSelectionHandleOne )
3149 // create normal and pressed images
3150 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3151 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3153 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3154 mSelectionHandleOne.SetName("SelectionHandleOne");
3155 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3156 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3157 mIsSelectionHandleOneFlipped = false;
3158 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3160 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3161 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3163 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3164 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3166 mTapDetector.Attach( mHandleOneGrabArea );
3167 mPanGestureDetector.Attach( mHandleOneGrabArea );
3169 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3171 mSelectionHandleOne.Add( mHandleOneGrabArea );
3172 mActiveLayer.Add( mSelectionHandleOne );
3175 if ( !mSelectionHandleTwo )
3177 // create normal and pressed images
3178 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3179 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3181 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3182 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3183 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3184 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3185 mIsSelectionHandleTwoFlipped = false;
3186 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3188 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3189 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3190 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3191 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3193 mTapDetector.Attach( mHandleTwoGrabArea );
3194 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3196 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3198 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3200 mActiveLayer.Add( mSelectionHandleTwo );
3203 SetUpHandlePropertyNotifications();
3205 // update table as text may have changed.
3206 GetTextLayoutInfo();
3208 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3209 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3211 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3212 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3214 // Calculates and set the visibility if the scroll mode is enabled.
3215 bool isSelectionHandleOneVisible = true;
3216 bool isSelectionHandleTwoVisible = true;
3217 if( IsScrollEnabled() )
3219 const Vector3& controlSize( GetControlSize() );
3220 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3221 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3222 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3223 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3226 CreateHighlight(); // function will only create highlight if not already created.
3229 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3231 Vector3 actualHandlePosition;
3233 if ( mSelectionHandleOne && mSelectionHandleTwo )
3235 const Vector3& controlSize = GetControlSize();
3237 Size cursorSize( CURSOR_THICKNESS, 0.f );
3239 // Get a reference of the wanted selection handle (handle one or two).
3240 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3242 // Get a reference for the current position of the handle and a copy of its pair
3243 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3244 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3246 // Get a handle of the selection handle actor
3247 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3249 // Selection handles should jump to the nearest character
3250 std::size_t newHandlePosition = 0;
3251 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3253 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3255 bool handleVisible = true;
3257 if( IsScrollEnabled() )
3259 mCurrentSelectionId = handleId;
3261 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3262 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3263 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3270 mCurrentSelectionHandlePosition = actualHandlePosition;
3271 mScrollDisplacement = Vector2::ZERO;
3275 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3277 mScrollDisplacement.x = -SCROLL_SPEED;
3279 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3281 mScrollDisplacement.x = SCROLL_SPEED;
3283 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3285 mScrollDisplacement.y = -SCROLL_SPEED;
3287 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3289 mScrollDisplacement.y = SCROLL_SPEED;
3295 if ( handleVisible && // Ensure the handle is visible.
3296 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3297 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3299 currentSelectionHandlePosition = newHandlePosition;
3301 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3302 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3306 if ( handleId == HandleOne )
3308 const TextStyle oldInputStyle( mInputStyle );
3310 // Set Active Style to that of first character in selection
3311 if( mSelectionHandleOnePosition < mStyledText.size() )
3313 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3316 if( oldInputStyle != mInputStyle )
3318 // Updates the line height accordingly with the input style.
3321 EmitStyleChangedSignal();
3327 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3330 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3333 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3334 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3336 if ( selectionHandleActor )
3338 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3339 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3340 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3342 if( IsScrollEnabled() )
3344 const Size cursorSize( CURSOR_THICKNESS,
3345 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3346 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3348 GetControlSize() ) );
3353 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3355 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3356 // For now the visual position of end of logical string will be end of visual string.
3357 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3359 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3362 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3364 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3365 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3366 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3367 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3369 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3371 // Deselect text prior to startSelectionIt
3372 for(;it!=startSelectionIt;++it)
3374 selectedVisualText[*it] = false;
3377 // Select text from startSelectionIt -> endSelectionIt
3378 for(;it!=endSelectionIt;++it)
3380 selectedVisualText[*it] = true;
3383 // Deselect text after endSelection
3386 selectedVisualText[*it] = false;
3389 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3392 // Calculate the dimensions of the quads they will make the highlight mesh
3393 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3395 // At the moment there is no public API to modify the block alignment option.
3396 const bool blockAlignEnabled = true;
3398 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3400 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3402 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3403 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3405 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3406 std::vector<bool> selectedVisualText;
3407 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3408 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3409 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3411 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3412 float rowLeft = 0.0f;
3413 float rowRight = 0.0f;
3414 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3415 float maxRowLeft = std::numeric_limits<float>::max();
3416 float maxRowRight = 0.0f;
3418 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3420 // Scan through entire text.
3423 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3425 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3426 bool charSelected( false );
3427 if( selectedIt != selectedEndIt )
3429 charSelected = *selectedIt++;
3432 if(selectionState == SelectionNone)
3436 selectionState = SelectionStarted;
3437 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3438 rowRight = rowLeft + charInfo.mSize.width;
3441 else if(selectionState == SelectionStarted)
3443 // break selection on:
3444 // 1. new line causing selection break. (\n or wordwrap)
3445 // 2. character not selected.
3446 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3449 // finished selection.
3450 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3451 // that it resides on. That way this enumeration is not necessary.
3453 if(lastIt->mIsNewLineChar)
3455 // 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.
3456 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3458 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3459 maxRowLeft = std::min(maxRowLeft, min.x);
3460 maxRowRight = std::max(maxRowRight, max.x);
3461 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3462 float rowTop = rowBottom - rowSize.height;
3464 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3465 if(charSelected && blockAlignEnabled)
3467 rowRight = std::numeric_limits<float>::max();
3469 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3471 selectionState = SelectionNone;
3473 // Still selected? start a new selection
3476 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3477 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3478 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3479 selectionState = SelectionStarted;
3484 // build up highlight(s) with this selection data.
3485 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3486 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3493 // If reached end, and still on selection, then close selection.
3496 if(selectionState == SelectionStarted)
3498 // finished selection.
3500 if(lastIt->mIsNewLineChar)
3502 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3504 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3505 maxRowLeft = std::min(maxRowLeft, min.x);
3506 maxRowRight = std::max(maxRowRight, max.x);
3507 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3508 float rowTop = rowBottom - rowSize.height;
3509 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3513 // Get the top left and bottom right corners.
3514 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3515 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3516 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3518 // Clamp quads so they appear to clip to borders of the whole text.
3519 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3521 // For block-align align Further Clamp quads to max left and right extents
3522 if(blockAlignEnabled)
3524 // BlockAlign: Will adjust highlight to block:
3526 // H[ello] (top row right = max of all rows right)
3527 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3528 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3529 // [text] (bottom row left = min of all rows left)
3530 // (common in SMS messaging selection)
3532 // As opposed to the default which is tight text highlighting.
3537 // (common in regular text editors/web browser selection)
3539 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3542 // Finally clamp quads again so they don't exceed the boundry of the control.
3543 const Vector3& controlSize = GetControlSize();
3544 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3547 return mNewHighlightInfo;
3550 void TextInput::UpdateHighlight()
3552 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3554 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3556 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3557 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3558 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3559 // [BOTTOM] [ MIDDLE ]
3562 // Each quad is created as 2 triangles.
3563 // Middle is just 1 quad regardless of its size.
3577 if ( mHighlightMeshActor )
3579 // vertex and triangle buffers should always be present if MeshActor is alive.
3580 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3581 MeshData::VertexContainer vertices;
3582 Dali::MeshData::FaceIndices faceIndices;
3584 if( !newHighlightInfo.mQuadList.empty() )
3586 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3587 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3589 // vertex position defaults to (0 0 0)
3590 MeshData::Vertex vertex;
3591 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3594 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3596 // Add each quad geometry (a sub-selection) to the mesh data.
3606 QuadCoordinates& quad = *iter;
3608 vertex.x = quad.min.x;
3609 vertex.y = quad.min.y;
3610 vertices.push_back( vertex );
3613 vertex.x = quad.max.x;
3614 vertex.y = quad.min.y;
3615 vertices.push_back( vertex );
3617 // bottom-left (v+2)
3618 vertex.x = quad.min.x;
3619 vertex.y = quad.max.y;
3620 vertices.push_back( vertex );
3622 // bottom-right (v+3)
3623 vertex.x = quad.max.x;
3624 vertex.y = quad.max.y;
3625 vertices.push_back( vertex );
3627 // triangle A (3, 1, 0)
3628 faceIndices.push_back( v + 3 );
3629 faceIndices.push_back( v + 1 );
3630 faceIndices.push_back( v );
3632 // triangle B (0, 2, 3)
3633 faceIndices.push_back( v );
3634 faceIndices.push_back( v + 2 );
3635 faceIndices.push_back( v + 3 );
3637 mMeshData.SetFaceIndices( faceIndices );
3640 BoneContainer bones(0); // passed empty as bones not required
3641 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3642 mHighlightMesh.UpdateMeshData(mMeshData);
3647 void TextInput::ClearPopup()
3649 mPopUpPanel.Clear();
3652 void TextInput::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
3654 mPopUpPanel.AddOption(name, caption, icon, finalOption);
3657 void TextInput::SetPopupPosition(const Vector3& position)
3659 mPopUpPanel.Self().SetPosition( position );
3662 void TextInput::HidePopup(bool animate, bool signalFinished )
3664 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3666 mPopUpPanel.Hide( animate );
3668 if( animate && signalFinished )
3670 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3675 void TextInput::ShowPopup(bool animate)
3679 if(mHighlightMeshActor && mState == StateEdit)
3683 // When text is selected, show popup above top handle (and text), or below bottom handle.
3684 // topHandle: referring to the top most point of the handle or the top line of selection.
3685 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3687 topHandle = mSelectionHandleOneActualPosition;
3688 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3692 topHandle = mSelectionHandleTwoActualPosition;
3693 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3695 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3696 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3698 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3699 Vector3 bottomHandle;
3700 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3701 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3702 mPopUpPanel.SetAlternativeOffset(Vector2(0.0f, bottomHandle.y - topHandle.y));
3706 // When no text is selected, show popup at world position of grab handle or cursor
3707 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3708 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3709 position.y -= rowSize.height;
3710 // if can't be positioned above, then position below row.
3711 Vector2 alternativePopUpPosition( 0.0f, position.y ); // default if no grab handle
3714 alternativePopUpPosition.y = rowSize.height + ( mGrabHandle.GetCurrentSize().height * DEFAULT_GRAB_HANDLE_RELATIVE_SIZE.y ) ;
3715 // If grab handle enabled then position pop-up below the grab handle.
3717 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3720 // reposition popup above the desired cursor posiiton.
3721 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3722 textViewSize.z = 0.0f;
3723 // World position = world position of ParentOrigin of cursor (i.e. top-left corner of TextView) + cursor position;
3724 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3726 SetPopupPosition( worldPosition );
3729 mPopUpPanel.Show(animate);
3730 StartMonitoringStageForTouch();
3732 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3735 void TextInput::ShowPopupCutCopyPaste()
3738 // Check the selected text is whole text or not.
3739 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3741 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3742 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3745 if ( !mStyledText.empty() )
3747 Image cutIcon = Image::New( DEFAULT_ICON_CUT );
3748 Image copyIcon = Image::New( DEFAULT_ICON_COPY );
3749 AddPopupOption( OPTION_CUT, GET_LOCALE_TEXT("IDS_COM_BODY_CUT"), cutIcon );
3750 AddPopupOption( OPTION_COPY, GET_LOCALE_TEXT("IDS_COM_BODY_COPY"), copyIcon, true );
3753 if(mClipboard.NumberOfItems())
3755 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3756 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3757 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon );
3758 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3761 mPopUpPanel.Hide(false);
3765 void TextInput::SetUpPopUpSelection()
3769 // If no text exists then don't offer to select
3770 if ( !mStyledText.empty() )
3772 Image selectIcon = Image::New( DEFAULT_ICON_SELECT );
3773 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3774 AddPopupOption( OPTION_SELECT_WORD, GET_LOCALE_TEXT("IDS_COM_SK_SELECT"), selectIcon );
3775 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3777 // if clipboard has valid contents then offer paste option
3778 if( mClipboard.NumberOfItems() )
3780 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3781 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3782 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon, true );
3783 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3786 mPopUpPanel.Hide(false);
3789 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3794 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3795 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3796 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3797 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3799 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3801 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3803 float closestYdifference = std::numeric_limits<float>::max();
3804 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3805 std::size_t numberOfMatchedCharacters = 0;
3807 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3808 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3810 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3812 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3813 float baselinePosition = info.mPosition.y - info.mDescender;
3815 if( info.mIsVisible )
3817 // store difference between source y point and the y position of the current character
3818 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3820 if( currentYdifference < closestYdifference )
3822 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3823 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3824 closestYdifference = currentYdifference;
3825 matchedCharacters.clear();
3826 numberOfMatchedCharacters = 0; // reset count
3829 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3830 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3832 // ignore new line character.
3833 if( !info.mIsNewLineChar )
3835 matchedCharacters.push_back( info );
3836 numberOfMatchedCharacters++;
3840 } // End of loop checking each character's y position in the character layout table
3842 // Check if last character is a newline, if it is
3843 // then need pretend there is an imaginary line afterwards,
3844 // and check if user is touching below previous line.
3845 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3847 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3849 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3853 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3854 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3856 bool matched( false );
3858 // 2 Iterate through matching list of y positions and find closest matching X position.
3859 for( ; it != endIt; ++it )
3861 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3863 if( info.mIsVisible )
3865 // stop when on left side of character's center.
3866 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3867 if( sourceScrollOffset.x < characterMidPointPosition )
3869 if(info.mIsRightToLeftCharacter)
3871 rightToLeftChar = true;
3873 glyphIntersection = info.mPosition.x;
3878 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3884 rightToLeftChar = lastRightToLeftChar;
3887 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3888 closestIndex = lineOffset + matchCharacterIndex;
3890 mClosestCursorPositionEOL = false; // reset
3891 if ( it == endIt && !matched )
3893 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3896 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3897 if( rightToLeftChar && lastRightToLeftChar )
3899 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3904 // closestIndex is the visual index, need to convert it to the logical index
3905 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3907 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3909 // Checks for situations where user is touching between LTR and RTL
3910 // characters. To identify if the user means the end of a LTR string
3911 // or the beginning of an RTL string, and vice versa.
3912 if( closestIndex > 0 )
3914 if( rightToLeftChar && !lastRightToLeftChar )
3919 // A: In this touch range, the user is indicating that they wish to place
3920 // the cursor at the end of the LTR text.
3921 // B: In this touch range, the user is indicating that they wish to place
3922 // the cursor at the end of the RTL text.
3924 // Result of touching A area:
3925 // [.....LTR]|[RTL......]+
3927 // |: primary cursor (for typing LTR chars)
3928 // +: secondary cursor (for typing RTL chars)
3930 // Result of touching B area:
3931 // [.....LTR]+[RTL......]|
3933 // |: primary cursor (for typing RTL chars)
3934 // +: secondary cursor (for typing LTR chars)
3936 if( sourceScrollOffset.x < glyphIntersection )
3941 else if( !rightToLeftChar && lastRightToLeftChar )
3943 if( sourceScrollOffset.x < glyphIntersection )
3950 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3951 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3952 // one further ahead
3953 if( rightToLeftChar && !lastRightToLeftChar )
3958 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3960 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3962 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3971 float TextInput::GetLineJustificationPosition() const
3973 const Vector3& size = mDisplayedTextView.GetCurrentSize();
3974 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
3975 float alignmentOffset = 0.f;
3977 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
3978 if( alignment & Toolkit::Alignment::HorizontalLeft )
3980 alignmentOffset = 0.f;
3982 else if( alignment & Toolkit::Alignment::HorizontalCenter )
3984 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
3986 else if( alignment & Toolkit::Alignment::HorizontalRight )
3988 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
3991 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
3992 float justificationOffset = 0.f;
3994 switch( justification )
3996 case Toolkit::TextView::Left:
3998 justificationOffset = 0.f;
4001 case Toolkit::TextView::Center:
4003 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4006 case Toolkit::TextView::Right:
4008 justificationOffset = mTextLayoutInfo.mTextSize.width;
4011 case Toolkit::TextView::Justified:
4013 justificationOffset = 0.f;
4018 DALI_ASSERT_ALWAYS( false );
4022 return alignmentOffset + justificationOffset;
4025 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4027 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4028 A newline character is not inserted in this case */
4030 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4032 Vector3 cursorPosition;
4034 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4036 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4038 // end character so use
4039 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4040 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4044 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4047 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4049 // If previous character on a different line then use current characters position
4050 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4052 if ( mClosestCursorPositionEOL )
4054 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4058 cursorPosition = Vector3(currentCharInfo.mPosition);
4063 // Previous character is on same line so use position of previous character plus it's width.
4064 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4067 return cursorPosition;
4070 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4072 bool direction(false);
4073 Vector3 alternatePosition;
4074 bool alternatePositionValid(false);
4076 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4079 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4081 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4083 alternatePositionValid = false;
4084 directionRTL = false;
4086 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4088 std::size_t visualCharacterPosition;
4090 // When cursor is not at beginning, consider possibility of
4091 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4092 if(characterPosition > 0)
4094 // Cursor position should be the end of the last character.
4095 // If the last character is LTR, then the end is on the right side of the glyph.
4096 // If the last character is RTL, then the end is on the left side of the glyph.
4097 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4099 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4101 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4104 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4105 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4107 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4108 const Vector3& size = GetControlSize();
4110 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4112 --visualCharacterPosition;
4114 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4117 if(!info.mIsNewLineChar)
4119 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4123 // When cursor points to first character on new line, position cursor at the start of this glyph.
4124 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4126 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4127 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4128 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4130 cursorPosition.x = infoNext.mPosition.x + start;
4131 cursorPosition.y = infoNext.mPosition.y;
4135 // If cursor points to the end of text, then can only position
4136 // cursor where the new line starts based on the line-justification position.
4137 cursorPosition.x = GetLineJustificationPosition();
4139 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4141 // If this is after the last character, then we can assume that the new cursor
4142 // should be exactly one row below the current row.
4144 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4145 cursorPosition.y = info.mPosition.y + rowRect.height;
4149 // If this is not after last character, then we can use this row's height.
4150 // should be exactly one row below the current row.
4152 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4153 cursorPosition.y = info.mPosition.y + rowRect.height;
4158 directionRTL = info.mIsRightToLeftCharacter;
4160 // 1. When the cursor is neither at the beginning or the end,
4161 // we can show multiple cursors under situations when the cursor is
4162 // between RTL and LTR text...
4163 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4165 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4167 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4168 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4170 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4172 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4173 // Text: [...LTR...]|[...RTL...]
4175 // Alternate cursor pos: ^
4176 // In which case we need to display an alternate cursor for the RTL text.
4178 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4179 alternatePosition.y = infoAlt.mPosition.y;
4180 alternatePositionValid = true;
4182 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4184 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4185 // Text: |[...RTL...] [...LTR....]
4187 // Alternate cursor pos: ^
4188 // In which case we need to display an alternate cursor for the RTL text.
4190 alternatePosition.x = infoAlt.mPosition.x;
4191 alternatePosition.y = infoAlt.mPosition.y;
4192 alternatePositionValid = true;
4197 // 2. When the cursor is at the end of the text,
4198 // and we have multi-directional text,
4199 // we can also consider showing mulitple cursors.
4200 // The rule here is:
4201 // If first and last characters on row are different
4202 // Directions, then two cursors need to be displayed.
4204 // Get first logical glyph on row
4205 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4207 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4208 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4210 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4212 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4213 // Text: [...LTR...]|[...RTL...]
4215 // Alternate cursor pos: ^
4216 // In which case we need to display an alternate cursor for the RTL text, this cursor
4217 // should be at the end of the given line.
4219 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4220 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4221 alternatePosition.y = infoAlt.mPosition.y;
4222 alternatePositionValid = true;
4224 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4226 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4227 // Text: |[...RTL...] [...LTR....]
4229 // Alternate cursor pos: ^
4230 // In which case we need to display an alternate cursor for the RTL text.
4232 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4233 alternatePosition.x = infoAlt.mPosition.x;
4234 alternatePosition.y = infoAlt.mPosition.y;
4235 alternatePositionValid = true;
4238 } // characterPosition > 0
4239 else if(characterPosition == 0)
4241 // When the cursor position is at the beginning, it should be at the start of the current character.
4242 // If the current character is LTR, then the start is on the right side of the glyph.
4243 // If the current character is RTL, then the start is on the left side of the glyph.
4244 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4246 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4248 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4251 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4252 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4254 cursorPosition.x = info.mPosition.x + start;
4255 cursorPosition.y = info.mPosition.y;
4256 directionRTL = info.mIsRightToLeftCharacter;
4261 // If the character table is void, place the cursor accordingly the text alignment.
4262 const Vector3& size = GetControlSize();
4264 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4265 float alignmentOffset = 0.f;
4267 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4268 if( alignment & Toolkit::Alignment::HorizontalLeft )
4270 alignmentOffset = 0.f;
4272 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4274 alignmentOffset = 0.5f * ( size.width );
4276 else if( alignment & Toolkit::Alignment::HorizontalRight )
4278 alignmentOffset = size.width;
4281 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4282 cursorPosition.x = alignmentOffset;
4284 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4285 if( alignment & Toolkit::Alignment::VerticalTop )
4287 cursorPosition.y = mLineHeight;
4289 else if( alignment & Toolkit::Alignment::VerticalCenter )
4291 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4293 else if( alignment & Toolkit::Alignment::VerticalBottom )
4295 cursorPosition.y = size.height;
4299 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4300 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4301 if( alternatePositionValid )
4303 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4304 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4307 return cursorPosition;
4310 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4312 // scan string from current position to beginning of current line to note direction of line
4313 while(logicalPosition)
4316 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4317 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4324 return logicalPosition;
4327 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4331 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4334 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4336 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4337 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4339 min = Vector2::ZERO;
4340 max = Vector2(0.0f, mLineHeight);
4344 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4345 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4346 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4348 // If cursor is pointing to end of line, then start from last character.
4349 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4351 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4353 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4354 if( !it->mIsVisible )
4356 characterPosition = FindVisibleCharacter( Left, characterPosition );
4357 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4360 // Scan characters left and right of cursor, stopping when end of line/string reached or
4361 // y position greater than threshold of reference line.
4363 // 1. scan left until we reach the beginning or a different line.
4364 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4365 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4366 // min-x position is the left-most char's left (x)
4367 // max-x position is the right-most char's right (x)
4368 // min-y position is the minimum of all character's top (y)
4369 // max-y position is the maximum of all character's bottom (y+height)
4370 min.y = validCharIt->mPosition.y;
4371 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4376 min.y = std::min(min.y, validCharIt->mPosition.y);
4377 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4386 if( (it->mPosition.y < referenceLine) ||
4387 (it->mIsNewLineChar) ||
4394 // info refers to the first character on this line.
4395 min.x = validCharIt->mPosition.x;
4397 // 2. scan right until we reach end or a different line
4398 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4399 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4403 if( (it->mPosition.y > referenceLine) ||
4404 (it->mIsNewLineChar) ||
4411 min.y = std::min(min.y, validCharIt->mPosition.y);
4412 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4417 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4419 if ( validCharIt != end )
4421 // info refers to the last character on this line.
4422 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4425 return Size( max.x - min.x, max.y - min.y );
4428 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4430 Actor popUpPanel = mPopUpPanel.GetRootActor();
4432 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4438 Dali::Actor parent( touchedActor.GetParent() );
4442 return WasTouchedCheck( parent );
4449 void TextInput::StartMonitoringStageForTouch()
4451 Stage stage = Stage::GetCurrent();
4452 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4455 void TextInput::EndMonitoringStageForTouch()
4457 Stage stage = Stage::GetCurrent();
4458 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4461 void TextInput::OnStageTouched(const TouchEvent& event)
4463 if( event.GetPointCount() > 0 )
4465 if ( TouchPoint::Down == event.GetPoint(0).state )
4467 const Actor touchedActor(event.GetPoint(0).hitActor);
4469 bool popUpShown( false );
4471 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4476 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4478 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4480 EndMonitoringStageForTouch();
4481 HidePopup( true, false );
4484 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4486 EndMonitoringStageForTouch();
4487 ShowGrabHandleAndSetVisibility( false );
4493 void TextInput::SelectText(std::size_t start, std::size_t end)
4495 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4496 IsGrabHandleEnabled()?"true":"false",
4497 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4498 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4499 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4501 StartMonitoringStageForTouch();
4503 if ( mEditModeActive ) // Only allow text selection when in edit mode
4505 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4506 mSelectingText = true;
4508 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4510 ImfManager imfManager = ImfManager::Get();
4513 imfManager.SetCursorPosition ( mCursorPosition );
4514 imfManager.SetSurroundingText( GetText() );
4515 imfManager.NotifyCursorPosition();
4517 // As the imfManager has been notified of the new cursor position we do not need to reset the pre-edit as it will be updated instead.
4519 // Hide grab handle when selecting.
4520 ShowGrabHandleAndSetVisibility( false );
4522 if( start != end ) // something to select
4524 SetCursorVisibility( false );
4525 StopCursorBlinkTimer();
4527 CreateSelectionHandles(start, end);
4530 const TextStyle oldInputStyle( mInputStyle );
4531 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4533 if( oldInputStyle != mInputStyle )
4535 // Updates the line height accordingly with the input style.
4538 EmitStyleChangedSignal();
4544 mSelectingText = false;
4548 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4550 MarkupProcessor::StyledTextArray currentSelectedText;
4552 if ( IsTextSelected() )
4554 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4555 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4557 for(; it != end; ++it)
4559 MarkupProcessor::StyledText& styledText( *it );
4560 currentSelectedText.push_back( styledText );
4563 return currentSelectedText;
4566 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4568 const std::size_t beginIndex = std::min( begin, end );
4569 const std::size_t endIndex = std::max( begin, end );
4572 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4574 // Create a styled text array used to replace the text into the text-view.
4575 MarkupProcessor::StyledTextArray text;
4576 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4578 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4579 GetTextLayoutInfo();
4581 if( IsScrollEnabled() )
4583 // Need to set the scroll position as the text's size may have changed.
4584 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4587 ShowGrabHandleAndSetVisibility( false );
4593 // Set Handle positioning as the new style may have repositioned the characters.
4594 SetSelectionHandlePosition(HandleOne);
4595 SetSelectionHandlePosition(HandleTwo);
4598 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4600 // Just hide the grab handle when keyboard is hidden.
4601 if (!keyboardShown )
4603 ShowGrabHandleAndSetVisibility( false );
4605 // If the keyboard is not now being shown, then hide the popup panel
4606 mPopUpPanel.Hide( true );
4610 // Removes highlight and resumes edit mode state
4611 void TextInput::RemoveHighlight()
4613 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4615 if ( mHighlightMeshActor )
4617 if ( mSelectionHandleOne )
4619 mActiveLayer.Remove( mSelectionHandleOne );
4620 mSelectionHandleOne.Reset();
4621 mSelectionHandleOneOffset.x = 0.0f;
4623 if ( mSelectionHandleTwo )
4625 mActiveLayer.Remove( mSelectionHandleTwo );
4626 mSelectionHandleTwo.Reset();
4627 mSelectionHandleTwoOffset.x = 0.0f;
4630 mNewHighlightInfo.mQuadList.clear();
4632 Self().Remove( mHighlightMeshActor );
4634 SetCursorVisibility( true );
4635 StartCursorBlinkTimer();
4637 mHighlightMeshActor.Reset();
4638 // NOTE: We cannot dereference mHighlightMesh, due
4639 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4644 mSelectionHandleOnePosition = 0;
4645 mSelectionHandleTwoPosition = 0;
4648 void TextInput::CreateHighlight()
4650 if ( !mHighlightMeshActor )
4652 mMeshData = MeshData( );
4653 mMeshData.SetHasNormals( true );
4655 mCustomMaterial = Material::New("CustomMaterial");
4656 mCustomMaterial.SetDiffuseColor( LIGHTBLUE );
4658 mMeshData.SetMaterial( mCustomMaterial );
4660 mHighlightMesh = Mesh::New( mMeshData );
4662 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4663 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4664 mHighlightMeshActor.SetInheritShaderEffect( false );
4665 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4666 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4667 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4668 mHighlightMeshActor.SetAffectedByLighting(false);
4670 Self().Add(mHighlightMeshActor);
4675 bool TextInput::CopySelectedTextToClipboard()
4677 mCurrentCopySelecton.clear();
4679 mCurrentCopySelecton = GetSelectedText();
4681 std::string stringToStore;
4683 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4684 * a marked up string.
4686 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4687 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4688 bool success = mClipboard.SetItem( stringToStore );
4692 void TextInput::PasteText( const Text& text )
4694 // Update Flag, indicates whether to update the text-input contents or not.
4695 // Any key stroke that results in a visual change of the text-input should
4696 // set this flag to true.
4697 bool update = false;
4698 if( mHighlightMeshActor )
4700 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4701 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4703 ImfManager imfManager = ImfManager::Get();
4706 imfManager.SetCursorPosition( mCursorPosition );
4707 imfManager.NotifyCursorPosition();
4709 DeleteHighlightedText( true );
4713 bool textExceedsMaximunNumberOfCharacters = false;
4714 bool textExceedsBoundary = false;
4716 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4718 mCursorPosition += insertedStringLength;
4719 ImfManager imfManager = ImfManager::Get();
4722 imfManager.SetCursorPosition ( mCursorPosition );
4723 imfManager.NotifyCursorPosition();
4726 update = update || ( insertedStringLength > 0 );
4732 if( insertedStringLength < text.GetLength() )
4734 EmitMaxInputCharactersReachedSignal();
4737 if( textExceedsBoundary )
4739 EmitInputTextExceedsBoundariesSignal();
4743 void TextInput::SetTextDirection()
4745 // Put the cursor to the right if we are empty and an RTL language is being used.
4746 if ( mStyledText.empty() )
4748 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4750 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4751 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4753 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4754 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4756 int alignment( mDisplayedTextView.GetTextAlignment() &
4757 ( Toolkit::Alignment::VerticalTop |
4758 Toolkit::Alignment::VerticalCenter |
4759 Toolkit::Alignment::VerticalBottom |
4760 Toolkit::Alignment::HorizontalCenter ) );
4761 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4763 // If our alignment is in the center, then do not change.
4764 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4766 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4769 // If our justification is in the center, then do not change.
4770 if ( justification != Toolkit::TextView::Center )
4772 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4775 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4776 mDisplayedTextView.SetLineJustification( justification );
4780 void TextInput::UpdateLineHeight()
4782 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4783 mLineHeight = font.GetLineHeight();
4785 // 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.
4787 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4789 if( !mExceedEnabled || shrink )
4791 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4795 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4797 std::size_t position = 0;
4799 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4805 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4807 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4809 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4815 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4816 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4818 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4824 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4829 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4836 void TextInput::SetSortModifier( float depthOffset )
4838 if(mDisplayedTextView)
4840 mDisplayedTextView.SetSortModifier(depthOffset);
4844 void TextInput::SetSnapshotModeEnabled( bool enable )
4846 if(mDisplayedTextView)
4848 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4852 bool TextInput::IsSnapshotModeEnabled() const
4854 bool snapshotEnabled = false;
4856 if(mDisplayedTextView)
4858 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4861 return snapshotEnabled;
4864 void TextInput::SetMarkupProcessingEnabled( bool enable )
4866 mMarkUpEnabled = enable;
4869 bool TextInput::IsMarkupProcessingEnabled() const
4871 return mMarkUpEnabled;
4874 void TextInput::SetScrollEnabled( bool enable )
4876 if( mDisplayedTextView )
4878 mDisplayedTextView.SetScrollEnabled( enable );
4883 // Don't set cursor's and handle's visibility to false if they are outside the
4884 // boundaries of the text-input.
4885 mIsCursorInScrollArea = true;
4886 mIsGrabHandleInScrollArea = true;
4887 if( mSelectionHandleOne && mSelectionHandleTwo )
4889 mSelectionHandleOne.SetVisible( true );
4890 mSelectionHandleTwo.SetVisible( true );
4892 if( mHighlightMeshActor )
4894 mHighlightMeshActor.SetVisible( true );
4900 bool TextInput::IsScrollEnabled() const
4902 bool scrollEnabled = false;
4904 if( mDisplayedTextView )
4906 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4909 return scrollEnabled;
4912 void TextInput::SetScrollPosition( const Vector2& position )
4914 if( mDisplayedTextView )
4916 mDisplayedTextView.SetScrollPosition( position );
4920 Vector2 TextInput::GetScrollPosition() const
4922 Vector2 scrollPosition;
4924 if( mDisplayedTextView )
4926 scrollPosition = mDisplayedTextView.GetScrollPosition();
4929 return scrollPosition;
4932 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4934 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4935 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4936 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4938 // Add style to the new input text.
4939 MarkupProcessor::StyledTextArray textToInsert;
4940 for( std::size_t i = 0; i < insertedStringLength; ++i )
4942 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4943 textToInsert.push_back( newStyledCharacter );
4946 //Insert text to the TextView.
4947 const bool emptyTextView = mStyledText.empty();
4948 if( emptyTextView && mPlaceHolderSet )
4950 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4951 mDisplayedTextView.SetText( textToInsert );
4955 if( 0 == numberOfCharactersToReplace )
4957 mDisplayedTextView.InsertTextAt( position, textToInsert );
4961 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4964 mPlaceHolderSet = false;
4966 if( textToInsert.empty() )
4968 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4969 GetTextLayoutInfo();
4973 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4974 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4977 textExceedsBoundary = false;
4979 if( !mExceedEnabled )
4981 const Vector3& size = GetControlSize();
4983 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
4985 // If new text does not fit within TextView
4986 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
4987 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
4988 GetTextLayoutInfo();
4989 textExceedsBoundary = true;
4990 insertedStringLength = 0;
4993 if( textExceedsBoundary )
4995 // Add the part of the text which fits on the text-input.
4997 // Split the text which doesn't fit in two halves.
4998 MarkupProcessor::StyledTextArray firstHalf;
4999 MarkupProcessor::StyledTextArray secondHalf;
5000 SplitText( textToInsert, firstHalf, secondHalf );
5002 // Clear text. This text will be filled with the text inserted.
5003 textToInsert.clear();
5005 // Where to insert the text.
5006 std::size_t positionToInsert = position;
5008 bool end = text.GetLength() <= 1;
5011 // Insert text and check ...
5012 const std::size_t textLength = firstHalf.size();
5013 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5014 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5016 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5018 // Inserted text doesn't fit.
5020 // Remove inserted text
5021 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5022 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5024 // The iteration finishes when only one character doesn't fit.
5025 end = textLength <= 1;
5029 // Prepare next two halves for next iteration.
5030 MarkupProcessor::StyledTextArray copyText = firstHalf;
5031 SplitText( copyText, firstHalf, secondHalf );
5038 // store text to be inserted in mStyledText.
5039 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5041 // Increase the inserted characters counter.
5042 insertedStringLength += textLength;
5044 // Prepare next two halves for next iteration.
5045 MarkupProcessor::StyledTextArray copyText = secondHalf;
5046 SplitText( copyText, firstHalf, secondHalf );
5048 // Update where next text has to be inserted
5049 positionToInsert += textLength;
5055 if( textToInsert.empty() && emptyTextView )
5057 // No character has been added and the text-view was empty.
5058 // Set the placeholder text.
5059 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5060 mPlaceHolderSet = true;
5064 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5065 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5066 mPlaceHolderSet = false;
5069 return insertedStringLength;
5072 void TextInput::GetTextLayoutInfo()
5074 if( mStyledText.empty() )
5076 // The text-input has no text, clear the text-view's layout info.
5077 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5081 if( mDisplayedTextView )
5083 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5087 // There is no text-view.
5088 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5093 void TextInput::EmitStyleChangedSignal()
5095 // emit signal if input style changes.
5097 Toolkit::TextInput handle( GetOwner() );
5098 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5101 void TextInput::EmitMaxInputCharactersReachedSignal()
5103 // emit signal if max characters is reached during text input.
5104 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5106 Toolkit::TextInput handle( GetOwner() );
5107 mMaxInputCharactersReachedSignalV2.Emit( handle );
5110 void TextInput::EmitInputTextExceedsBoundariesSignal()
5112 // Emit a signal when the input text exceeds the boundaries of the text input.
5114 Toolkit::TextInput handle( GetOwner() );
5115 mInputTextExceedBoundariesSignalV2.Emit( handle );
5118 } // namespace Internal
5120 } // namespace Toolkit