2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 // Licensed under the Flora License, Version 1.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://floralicense.org/license/
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.
17 #include <dali/dali.h>
19 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
20 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
21 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
22 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
24 #include <dali/integration-api/debug.h>
31 #define GET_LOCALE_TEXT(string) dgettext("sys_string", string)
40 #if defined(DEBUG_ENABLED)
41 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
44 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
45 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
46 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
47 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
48 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
49 const Vector4 LIGHTBLUE( 10.0f/255.0f, 140.0f/255.0f, 210.0f/255.0f, 1.0f ); // Used for Selection highlight
51 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
52 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
53 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
54 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
55 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
56 const char* DEFAULT_CURSOR( DALI_IMAGE_DIR "cursor.png" );
58 const char* DEFAULT_ICON_CLIPBOARD( DALI_IMAGE_DIR "copy_paste_icon_clipboard.png" );
59 const char* DEFAULT_ICON_COPY( DALI_IMAGE_DIR "copy_paste_icon_copy.png" );
60 const char* DEFAULT_ICON_CUT( DALI_IMAGE_DIR "copy_paste_icon_cut.png" );
61 const char* DEFAULT_ICON_PASTE( DALI_IMAGE_DIR "copy_paste_icon_paste.png" );
62 const char* DEFAULT_ICON_SELECT( DALI_IMAGE_DIR "copy_paste_icon_select.png" );
63 const char* DEFAULT_ICON_SELECT_ALL( DALI_IMAGE_DIR "copy_paste_icon_select_all.png" );
65 const Vector4 DEFAULT_CURSOR_IMAGE_9_BORDER( 2.0f, 2.0f, 2.0f, 2.0f );
67 const std::string OPTION_SELECT_WORD("select_word"); ///< "Select Word" popup option.
68 const std::string OPTION_SELECT_ALL("select_all"); ///< "Select All" popup option.
69 const std::string OPTION_CUT("cut"); ///< "Cut" popup option.
70 const std::string OPTION_COPY("copy"); ///< "Copy" popup option.
71 const std::string OPTION_PASTE("paste"); ///< "Paste" popup option.
72 const std::string OPTION_CLIPBOARD("clipboard"); ///< "Clipboard" popup option.
74 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
75 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
76 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.0f ); ///< 1. Highlight rendered (z-offset).
77 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.1f ); ///< 2. Text rendered (z-offset).
78 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
80 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
81 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
82 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
83 const float TOP_HANDLE_TOP_OFFSET(-1.5f); ///< Offset between top handle and cutCopyPaste pop-up
84 const float BOTTOM_HANDLE_BOTTOM_OFFSET(1.5f); ///< Offset between bottom handle and cutCopyPaste pop-up
85 const float CURSOR_THICKNESS(6.0f);
86 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
88 const std::string NEWLINE( "\n" );
90 const TextStyle DEFAULT_TEXT_STYLE;
92 const unsigned int SCROLL_TICK_INTERVAL = 50u;
93 const float SCROLL_THRESHOLD = 10.f;
94 const float SCROLL_SPEED = 15.f;
97 * Whether the given style is the default style or not.
98 * @param[in] style The given style.
99 * @return \e true if the given style is the default. Otherwise it returns \e false.
101 bool IsDefaultStyle( const TextStyle& style )
103 return DEFAULT_TEXT_STYLE == style;
107 * Whether the given styled text is using the default style or not.
108 * @param[in] textArray The given text.
109 * @return \e true if the given styled text is using the default style. Otherwise it returns \e false.
111 bool IsTextDefaultStyle( const Toolkit::MarkupProcessor::StyledTextArray& textArray )
113 for( Toolkit::MarkupProcessor::StyledTextArray::const_iterator it = textArray.begin(), endIt = textArray.end(); it != endIt; ++it )
115 const TextStyle& style( (*it).mStyle );
117 if( !IsDefaultStyle( style ) )
127 * Selection state enumeration (FSM)
131 SelectionNone, ///< Currently not encountered selected section.
132 SelectionStarted, ///< Encountered selected section
133 SelectionFinished ///< Finished selected section
136 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
138 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
142 if( ( *it ).mIsVisible )
144 return --cursorPosition;
153 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
155 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
157 if( ( *it ).mIsVisible )
159 return cursorPosition;
165 return cursorPosition;
169 * Whether the given position plus the cursor size offset is inside the given boundary.
171 * @param[in] position The given position.
172 * @param[in] cursorSize The cursor size.
173 * @param[in] controlSize The given boundary.
175 * @return whether the given position is inside the given boundary.
177 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
179 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
180 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
181 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
182 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
186 * Splits a text in two halves.
188 * If the text's number of characters is odd, firstHalf has one more character.
190 * @param[in] text The text to be split.
191 * @param[out] firstHalf The first half of the text.
192 * @param[out] secondHalf The second half of the text.
194 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
195 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
196 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
201 const std::size_t textLength = text.size();
202 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
204 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
205 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
208 } // end of namespace
224 return Toolkit::TextInput::New();
227 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
229 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
230 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
231 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
232 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
233 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
234 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
238 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
240 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
242 QuadCoordinates quad(x1, y1, x2, y2);
243 mQuadList.push_back( quad );
246 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
248 for(std::size_t i = 0;i < mQuadList.size(); i++)
250 QuadCoordinates& quad = mQuadList[i];
252 quad.min.Clamp(min, max);
253 quad.max.Clamp(min, max);
257 // [TextInput] ////////////////////////////////////////////////////////////////
259 Dali::Toolkit::TextInput TextInput::New()
261 // Create the implementation
262 TextInputPtr textInput(new TextInput());
263 // Pass ownership to CustomActor via derived handle
264 Dali::Toolkit::TextInput handle(*textInput);
266 textInput->Initialize();
271 TextInput::TextInput()
272 :ControlImpl( true ),
277 mDisplayedTextView(),
278 mStyledPlaceHolderText(),
279 mMaxStringLength( DEFAULT_MAX_SIZE ),
280 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
281 mCursorPosition( 0 ),
282 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
283 mIsSelectionHandleOneFlipped( false ),
284 mIsSelectionHandleTwoFlipped( false ),
285 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
286 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
287 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
288 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
289 mSelectionHandleOnePosition( 0 ),
290 mSelectionHandleTwoPosition( 0 ),
292 mPreEditStartPosition( 0 ),
293 mPreEditLength ( 0 ),
294 mNumberOfSurroundingCharactersDeleted( 0 ),
295 mTouchStartTime( 0 ),
297 mCurrentCopySelecton(),
299 mScrollDisplacement(),
300 mCurrentHandlePosition(),
301 mCurrentSelectionId(),
302 mCurrentSelectionHandlePosition(),
303 mRequestedSelection( 0, 0 ),
304 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
305 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
307 mOverrideAutomaticAlignment( false ),
308 mCursorRTLEnabled( false ),
309 mClosestCursorPositionEOL ( false ),
310 mCursorBlinkStatus( true ),
311 mCursorVisibility( false ),
312 mGrabHandleVisibility( false ),
313 mIsCursorInScrollArea( true ),
314 mIsGrabHandleInScrollArea( true ),
315 mEditModeActive( false ),
316 mEditOnTouch( true ),
317 mTextSelection( true ),
318 mExceedEnabled( true ),
319 mGrabHandleEnabled( true ),
320 mIsSelectionHandleFlipEnabled( true ),
321 mPreEditFlag( false ),
322 mIgnoreCommitFlag( false ),
323 mIgnoreFirstCommitFlag( false ),
324 mSelectingText( false ),
325 mPreserveCursorPosition( false ),
326 mSelectTextOnCommit( false ),
327 mUnderlinedPriorToPreEdit ( false ),
328 mCommitByKeyInput( false ),
329 mPlaceHolderSet( false ),
330 mMarkUpEnabled( false )
332 // Updates the line height accordingly with the input style.
336 TextInput::~TextInput()
338 StopCursorBlinkTimer();
343 std::string TextInput::GetText() const
347 // Return text-view's text only if the text-input's text is not empty
348 // in order to not to return the placeholder text.
349 if( !mStyledText.empty() )
351 text = mDisplayedTextView.GetText();
357 std::string TextInput::GetMarkupText() const
359 std::string markupString;
360 MarkupProcessor::GetMarkupString( mStyledText, markupString );
365 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
367 // Get the placeholder styled text array from the markup string.
368 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
370 if( mStyledText.empty() )
372 // Set the placeholder text only if the styled text is empty.
373 mDisplayedTextView.SetText( mStyledPlaceHolderText );
374 mPlaceHolderSet = true;
378 std::string TextInput::GetPlaceholderText()
380 // Traverses the styled placeholder array getting only the text.
381 // Note that for some languages a 'character' could be represented by more than one 'char'
383 std::string placeholderText;
384 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
386 placeholderText.append( (*it).mText.GetText() );
389 return placeholderText ;
392 void TextInput::SetInitialText(const std::string& initialText)
394 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
396 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
398 mPreEditFlag = false;
399 mIgnoreCommitFlag = true;
402 SetText( initialText );
403 PreEditReset( false ); // Reset keyboard as text changed
406 void TextInput::SetText(const std::string& initialText)
408 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
410 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
412 if( mStyledText.empty() )
414 // If the initial text is empty, set the placeholder text.
415 mDisplayedTextView.SetText( mStyledPlaceHolderText );
416 mPlaceHolderSet = true;
420 mDisplayedTextView.SetText( mStyledText );
421 mPlaceHolderSet = false;
426 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
428 ImfManager imfManager = ImfManager::Get();
431 imfManager.SetCursorPosition( mCursorPosition );
432 imfManager.SetSurroundingText( initialText );
433 imfManager.NotifyCursorPosition();
436 if( IsScrollEnabled() )
438 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
441 ShowGrabHandleAndSetVisibility( false );
448 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
450 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
452 mDisplayedTextView.SetText( styleText );
453 mPlaceHolderSet = false;
455 // If text alignment hasn't been manually set by application developer, then we
456 // automatically determine the alignment based on the content of the text i.e. what
457 // language the text begins with.
458 // TODO: This should determine different alignments for each line (broken by '\n') of text.
459 if(!mOverrideAutomaticAlignment)
461 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
462 bool leftToRight(true);
464 if( !styleText.empty() )
466 bool breakOut(false);
468 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
470 const Text& text = textIter->mText;
472 for( std::size_t i = 0; i < text.GetLength(); ++i )
474 Character character( text[i] );
475 if( character.GetCharacterDirection() != Character::Neutral )
477 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
485 // Based on this direction, either left or right align text if not manually set by application developer.
486 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
487 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
488 Toolkit::Alignment::VerticalTop ) );
489 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
493 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
495 mMaxStringLength = maxChars;
498 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
500 DALI_ASSERT_DEBUG( maxLines > 0 )
504 mNumberOflinesLimit = maxLines;
508 std::size_t TextInput::GetNumberOfLinesLimit() const
510 return mNumberOflinesLimit;
513 std::size_t TextInput::GetNumberOfCharacters() const
515 return mStyledText.size();
518 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
520 return mInputStartedSignalV2;
523 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
525 return mInputFinishedSignalV2;
528 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
530 return mCutAndPasteToolBarDisplayedV2;
533 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
535 return mStyleChangedSignalV2;
538 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
540 return mMaxInputCharactersReachedSignalV2;
543 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
545 return mInputTextExceedBoundariesSignalV2;
548 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
550 Dali::BaseHandle handle( object );
552 bool connected( true );
553 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
555 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
557 textInput.InputStartedSignal().Connect( tracker, functor );
559 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
561 textInput.InputFinishedSignal().Connect( tracker, functor );
563 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
565 textInput.StyleChangedSignal().Connect( tracker, functor );
567 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
569 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
571 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
573 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
577 // signalName does not match any signal
584 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
588 // update line height before calculate the actual position.
593 if( setCursorOnTouchPoint )
595 // Sets the cursor position for the given touch point.
596 ReturnClosestIndex( touchPoint, mCursorPosition );
598 // Creates the grab handle.
599 if( IsGrabHandleEnabled() )
601 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
605 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
606 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
607 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
608 ShowGrabHandleAndSetVisibility( true );
610 // Scrolls the text-view if needed.
611 if( IsScrollEnabled() )
613 ScrollTextViewToMakeCursorVisible( cursorPosition );
619 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
631 bool TextInput::IsEditable() const
633 return mEditModeActive;
636 void TextInput::SetEditOnTouch( bool editOnTouch )
638 mEditOnTouch = editOnTouch;
641 bool TextInput::IsEditOnTouch() const
646 void TextInput::SetTextSelectable( bool textSelectable )
648 mTextSelection = textSelectable;
651 bool TextInput::IsTextSelectable() const
653 return mTextSelection;
656 bool TextInput::IsTextSelected() const
658 return mHighlightMeshActor;
661 void TextInput::DeSelectText()
668 void TextInput::SetGrabHandleImage(Dali::Image image )
672 CreateGrabHandle(image);
676 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
678 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
682 mCursor.SetImage( image );
683 mCursor.SetNinePatchBorder( border );
687 Vector3 TextInput::GetSelectionHandleSize()
689 return DEFAULT_SELECTION_HANDLE_SIZE;
692 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
694 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
698 mCursorRTL.SetImage( image);
699 mCursorRTL.SetNinePatchBorder( border );
703 void TextInput::EnableGrabHandle(bool toggle)
705 // enables grab handle with will in turn de-activate magnifier
706 mGrabHandleEnabled = toggle;
709 bool TextInput::IsGrabHandleEnabled()
711 // if false then magnifier will be shown instead.
712 return mGrabHandleEnabled;
715 void TextInput::EnableSelectionHandleFlip( bool toggle )
717 // Deprecated function. To be removed.
718 mIsSelectionHandleFlipEnabled = toggle;
721 bool TextInput::IsSelectionHandleFlipEnabled()
723 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
727 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
729 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
730 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
731 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
733 mSelectionHandleFlipMargin = margin;
736 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
738 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
739 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
741 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
742 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
744 const Vector4 boundary( originX,
746 originX + boundingRectangle.width,
747 originY + boundingRectangle.height );
749 mBoundingRectangleWorldCoordinates = boundary;
752 const Rect<float> TextInput::GetBoundingRectangle() const
754 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
756 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
757 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
759 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
764 const Vector4& TextInput::GetSelectionHandleFlipMargin()
766 return mSelectionHandleFlipMargin;
769 void TextInput::SetTextColor( const Vector4& color )
771 mDisplayedTextView.SetColor( color );
774 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
776 if( style != mInputStyle )
779 bool emitSignal = false;
781 // mask: modify style according to mask, if different emit signal.
782 const TextStyle oldInputStyle( mInputStyle );
784 // Copy the new style.
785 mInputStyle.Copy( style, mask );
787 // if style has changed, emit signal.
788 if( oldInputStyle != mInputStyle )
793 // Updates the line height accordingly with the input style.
796 // Changing font point size will require the cursor to be re-sized
801 EmitStyleChangedSignal();
806 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
808 if ( IsTextSelected() )
810 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
811 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
813 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
815 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
818 // Keeps the old style to be compared with the new one.
819 const TextStyle oldInputStyle( mInputStyle );
821 // Copy only those parameters from the style which are set in the mask.
822 mInputStyle.Copy( style, mask );
824 if( mInputStyle != oldInputStyle )
826 // Updates the line height accordingly with the input style.
829 EmitStyleChangedSignal();
834 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
836 if( !mStyledText.empty() )
838 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
842 TextStyle TextInput::GetStyleAtCursor() const
846 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
848 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.SetParentOrigin(ParentOrigin::TOP_LEFT);
2033 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2034 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2035 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2036 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2037 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2038 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2039 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2040 mDisplayedTextView.SetSizePolicy( Control::Fixed, Control::Fixed );
2042 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2044 Self().Add( mDisplayedTextView );
2047 // Start a timer to initiate, used by the cursor to blink.
2048 void TextInput::StartCursorBlinkTimer()
2050 if ( !mCursorBlinkTimer )
2052 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2053 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2056 if ( !mCursorBlinkTimer.IsRunning() )
2058 mCursorBlinkTimer.Start();
2062 // Start a timer to initiate, used by the cursor to blink.
2063 void TextInput::StopCursorBlinkTimer()
2065 if ( mCursorBlinkTimer )
2067 mCursorBlinkTimer.Stop();
2071 void TextInput::StartEditMode()
2073 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2075 if(!mEditModeActive)
2080 if ( mDoubleTapDetector )
2082 mDoubleTapDetector.Attach( Self() );
2086 void TextInput::EndEditMode()
2088 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2090 ClearKeyInputFocus();
2092 if ( mDoubleTapDetector )
2094 mDoubleTapDetector.Detach( Self() );
2098 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2100 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2102 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2104 style.SetUnderline( true );
2105 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2109 void TextInput::RemovePreEditStyle()
2111 if ( !mUnderlinedPriorToPreEdit )
2114 style.SetUnderline( false );
2115 SetActiveStyle( style, TextStyle::UNDERLINE );
2119 // IMF related methods
2122 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2124 bool update( false );
2125 bool preeditResetRequired ( false );
2127 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2129 HidePopup(); // If Pop-up shown then hides it as editing text.
2132 switch ( imfEvent.eventName )
2134 case ImfManager::PREEDIT:
2136 mIgnoreFirstCommitFlag = false;
2138 // 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
2139 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2141 // replaces highlighted text with new character
2142 DeleteHighlightedText( false );
2145 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2147 if( IsScrollEnabled() )
2149 // Calculates the new cursor position (in actor coordinates)
2150 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2151 ScrollTextViewToMakeCursorVisible( cursorPosition );
2158 case ImfManager::COMMIT:
2160 if( mIgnoreFirstCommitFlag )
2162 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2163 mIgnoreFirstCommitFlag = false;
2167 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2169 // 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
2170 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2172 // replaces highlighted text with new character
2173 DeleteHighlightedText( false );
2176 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2177 // not needed, one such scenario is when the pre-edit word is too long to fit.
2178 if ( !mIgnoreCommitFlag )
2180 update = CommitReceived( imfEvent.predictiveString );
2184 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2190 if( IsScrollEnabled() )
2192 // Calculates the new cursor position (in actor coordinates)
2193 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2195 ScrollTextViewToMakeCursorVisible( cursorPosition );
2200 case ImfManager::DELETESURROUNDING:
2202 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2203 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2205 mPreEditFlag = false;
2207 std::size_t toDelete = 0;
2208 std::size_t numberOfCharacters = 0;
2210 if( mHighlightMeshActor )
2212 // delete highlighted text.
2213 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2214 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2218 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2220 toDelete = mCursorPosition + imfEvent.cursorOffset;
2222 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2224 numberOfCharacters = mStyledText.size() - toDelete;
2228 numberOfCharacters = imfEvent.numberOfChars;
2231 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2232 DeleteRange( toDelete, numberOfCharacters );
2234 mCursorPosition = toDelete;
2235 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2237 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2240 case ImfManager::GETSURROUNDING:
2242 // 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
2243 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2244 if (! ( mHighlightMeshActor || mSelectingText ) )
2246 std::string text( GetText() );
2247 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2249 imfManager.SetCursorPosition( mCursorPosition );
2250 imfManager.SetSurroundingText( text );
2253 if( 0 != mNumberOfSurroundingCharactersDeleted )
2255 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2256 mNumberOfSurroundingCharactersDeleted = 0;
2258 if( mStyledText.empty() )
2260 // Styled text is empty, so set the placeholder text.
2261 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2262 mPlaceHolderSet = true;
2267 case ImfManager::VOID:
2269 DALI_ASSERT_DEBUG( false );
2273 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2275 return callbackData;
2278 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2280 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2282 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2283 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2285 bool preeditResetRequest ( false );
2287 if( mPreEditFlag ) // Already in pre-edit state.
2289 if( mStyledText.size() >= mMaxStringLength )
2291 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2292 // Cannot fit these characters into field, clear pre-edit.
2293 if ( !mUnderlinedPriorToPreEdit )
2296 style.SetUnderline( false );
2297 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2299 mIgnoreCommitFlag = true;
2300 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2301 mPreEditFlag = false;
2302 EmitMaxInputCharactersReachedSignal();
2306 // delete existing pre-edit string
2307 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2309 // Store new pre-edit string
2310 mPreEditString.SetText( keyString );
2312 if ( keyString.empty() )
2314 mPreEditFlag = false;
2315 mCursorPosition = mPreEditStartPosition;
2317 if( mStyledText.empty() )
2319 // Styled text is empty, so set the placeholder text.
2320 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2321 mPlaceHolderSet = true;
2325 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2327 GetTextLayoutInfo();
2331 // Insert new pre-edit string. InsertAt updates the size and position table.
2332 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2333 // 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.
2334 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2335 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2336 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2338 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2342 else // mPreEditFlag not set
2344 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2346 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2347 // new pre-edit so move into pre-edit state by setting flag
2348 mPreEditFlag = true;
2349 mPreEditString.SetText( keyString ); // store new pre-edit string
2350 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2351 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2352 // 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.
2353 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2354 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2355 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2357 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2362 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2366 return preeditResetRequest;
2369 bool TextInput::CommitReceived(const std::string& keyString )
2371 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2372 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2374 bool update( false );
2376 RemovePreEditStyle();
2378 const std::size_t styledTextSize( mStyledText.size() );
2379 if( styledTextSize >= mMaxStringLength )
2381 // Cannot fit these characters into field, clear pre-edit.
2384 mIgnoreCommitFlag = true;
2385 mPreEditFlag = false;
2387 EmitMaxInputCharactersReachedSignal();
2393 // delete existing pre-edit string
2394 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2395 mPreEditFlag = false;
2397 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2398 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2400 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2402 // No need to update cursor position as Cursor location given by touch.
2403 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2404 mPreserveCursorPosition = false;
2408 // Cursor not set by touch so needs to be re-positioned to input more text
2409 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2411 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2412 if ( mCommitByKeyInput )
2414 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2415 mCommitByKeyInput = false;
2419 if ( mSelectTextOnCommit )
2421 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2426 else // mPreEditFlag not set
2428 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2430 if( mStyledText.empty() && mPlaceHolderSet )
2432 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2433 mDisplayedTextView.SetText( "" );
2434 mNumberOfSurroundingCharactersDeleted = 0;
2435 mPlaceHolderSet = false;
2437 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2439 mNumberOfSurroundingCharactersDeleted = 0;
2443 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2448 mSelectTextOnCommit = false;
2450 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2451 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2456 // End of IMF related methods
2458 std::size_t TextInput::DeletePreEdit()
2460 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2462 DALI_ASSERT_DEBUG( mPreEditFlag );
2464 const std::size_t preEditStringLength = mPreEditString.GetLength();
2465 const std::size_t styledTextSize = mStyledText.size();
2467 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2469 // Prevents erase items outside mStyledText bounds.
2470 if( mPreEditStartPosition > styledTextSize )
2472 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2473 mPreEditStartPosition = styledTextSize;
2476 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2478 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2479 endPosition = styledTextSize;
2482 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2484 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2485 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2487 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2489 return preEditStringLength;
2492 void TextInput::PreEditReset( bool preserveCursorPosition )
2494 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2495 preserveCursorPosition, mCursorPosition);
2497 // 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.
2498 mPreserveCursorPosition = preserveCursorPosition;
2500 // Reset incase we are in a pre-edit state.
2501 ImfManager imfManager = ImfManager::Get();
2504 imfManager.Reset(); // Will trigger a commit message
2508 void TextInput::CursorUpdate()
2512 ImfManager imfManager = ImfManager::Get();
2515 std::string text( GetText() );
2516 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2517 imfManager.SetCursorPosition ( mCursorPosition );
2518 imfManager.NotifyCursorPosition();
2522 /* Delete highlighted characters redisplay*/
2523 void TextInput::DeleteHighlightedText( bool inheritStyle )
2525 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2527 if(mHighlightMeshActor)
2529 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2531 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2532 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2534 // Get the styled text of the characters to be deleted as it may be needed if
2535 // the "exceed the text-input's boundaries" option is disabled.
2536 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2538 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2540 mStyledText.erase( start, end ); // erase range of characters
2542 // Remove text from TextView.
2544 if( mStyledText.empty() )
2546 // Styled text is empty, so set the placeholder text.
2547 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2548 mPlaceHolderSet = true;
2552 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2554 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2556 // It may happen than after removing a white space or a new line character,
2557 // two words merge, this new word could be big enough to not fit in its
2558 // current line, so moved to the next one, and make some part of the text to
2559 // exceed the text-input's boundary.
2560 if( !mExceedEnabled )
2562 // Get the new text layout after removing some characters.
2563 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2565 // Get text-input's size.
2566 const Vector3& size = GetControlSize();
2568 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2569 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2571 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2573 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2574 styledCharactersToDelete.begin(),
2575 styledCharactersToDelete.end() );
2579 GetTextLayoutInfo();
2585 const TextStyle oldInputStyle( mInputStyle );
2587 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2589 if( oldInputStyle != mInputStyle )
2591 // Updates the line height accordingly with the input style.
2594 EmitStyleChangedSignal();
2600 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2602 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2603 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2605 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2608 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2610 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2611 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2613 mStyledText.erase(itStart, itEnd);
2615 // update the selection handles if they are visible.
2616 if( mHighlightMeshActor )
2618 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2619 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2621 if( minHandle >= start + ncharacters )
2623 minHandle -= ncharacters;
2625 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2630 if( maxHandle >= start + ncharacters )
2632 maxHandle -= ncharacters;
2634 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2640 // 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.
2643 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2645 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2646 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2647 // Mean we do not re-draw the text more than we have too.
2650 /* Delete character at current cursor position and redisplay*/
2651 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2653 // Ensure positionToDelete is not out of bounds.
2654 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2655 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2656 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2658 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2661 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2663 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2665 // Get the styled text of the character to be deleted as it may be needed if
2666 // the "exceed the text-input's boundaries" option is disabled.
2667 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2669 mStyledText.erase(it); // erase the character left of positionToDelete
2671 if( mStyledText.empty() )
2673 // Styled text is empty, so set the placeholder text.
2674 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2675 mPlaceHolderSet = true;
2679 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2681 const Character characterToDelete = styledCharacterToDelete.mText[0];
2683 // It may happen than after removing a white space or a new line character,
2684 // two words merge, this new word could be big enough to not fit in its
2685 // current line, so moved to the next one, and make some part of the text to
2686 // exceed the text-input's boundary.
2687 if( !mExceedEnabled )
2689 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2691 // Get the new text layout after removing one character.
2692 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2694 // Get text-input's size.
2695 const Vector3& size = GetControlSize();
2697 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2698 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2700 MarkupProcessor::StyledTextArray array;
2701 array.push_back( styledCharacterToDelete );
2702 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2704 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2709 GetTextLayoutInfo();
2711 ShowGrabHandleAndSetVisibility( false );
2713 mCursorPosition = positionToDelete -1;
2715 const TextStyle oldInputStyle( mInputStyle );
2717 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2719 if( oldInputStyle != mInputStyle )
2721 // Updates the line height accordingly with the input style.
2724 EmitStyleChangedSignal();
2729 /*Insert new character into the string and (optionally) redisplay text-input*/
2730 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2732 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2734 // Ensure insertionPosition is not out of bounds.
2735 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2737 bool textExceedsMaximunNumberOfCharacters = false;
2738 bool textExceedsBoundary = false;
2739 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2741 ShowGrabHandleAndSetVisibility( false );
2743 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2747 mIgnoreCommitFlag = true;
2748 mPreEditFlag = false;
2749 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2750 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2753 if( textExceedsMaximunNumberOfCharacters )
2755 EmitMaxInputCharactersReachedSignal();
2758 if( textExceedsBoundary )
2760 EmitInputTextExceedsBoundariesSignal();
2761 PreEditReset( false );
2765 return insertedStringLength;
2768 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2774 cursor = ImageActor::New( cursorImage );
2778 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2781 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2782 cursor.SetNinePatchBorder( border );
2784 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2785 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2786 cursor.SetVisible(false);
2791 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2793 // As cursor is not moving due to grab handle, handle should be hidden.
2794 ShowGrabHandleAndSetVisibility( false );
2796 bool cursorPositionChanged = false;
2799 if ( mCursorPosition >= places )
2801 mCursorPosition = mCursorPosition - places;
2802 cursorPositionChanged = true;
2807 if ((mCursorPosition + places) <= mStyledText.size())
2809 mCursorPosition = mCursorPosition + places;
2810 cursorPositionChanged = true;
2814 if( cursorPositionChanged )
2816 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2818 const TextStyle oldInputStyle( mInputStyle );
2819 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2823 if( oldInputStyle != mInputStyle )
2825 // Updates the line height accordingly with the input style.
2828 EmitStyleChangedSignal();
2831 ImfManager imfManager = ImfManager::Get();
2834 imfManager.SetCursorPosition ( mCursorPosition );
2835 imfManager.NotifyCursorPosition();
2840 void TextInput::DrawCursor(const std::size_t nthChar)
2842 // Get height of cursor and set its size
2843 Size size( CURSOR_THICKNESS, 0.0f );
2844 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2846 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2850 // Measure Font so know how big text will be if no initial text to measure.
2851 size.height = mLineHeight;
2854 mCursor.SetSize(size);
2856 // If the character is italic then the cursor also tilts.
2857 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2859 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2861 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2863 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2864 bool altPositionValid; // Alternate cursor validity flag.
2865 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2866 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2868 SetAltCursorEnabled( altPositionValid );
2870 if(!altPositionValid)
2872 mCursor.SetPosition( position + UI_OFFSET );
2876 size.height *= 0.5f;
2877 mCursor.SetSize(size);
2878 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2880 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2881 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2882 size.height = rowSize.height * 0.5f;
2883 mCursorRTL.SetSize(size);
2884 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2887 if( IsScrollEnabled() )
2889 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2890 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2895 void TextInput::SetAltCursorEnabled( bool enabled )
2897 mCursorRTLEnabled = enabled;
2898 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2901 void TextInput::SetCursorVisibility( bool visible )
2903 mCursorVisibility = visible;
2904 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2905 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2908 void TextInput::CreateGrabHandle( Dali::Image image )
2914 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2918 mGrabHandleImage = image;
2921 mGrabHandle = ImageActor::New(mGrabHandleImage);
2922 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2923 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2925 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2927 ShowGrabHandleAndSetVisibility( false );
2929 CreateGrabArea( mGrabHandle );
2931 mActiveLayer.Add(mGrabHandle);
2935 void TextInput::CreateGrabArea( Actor& parent )
2937 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2938 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2939 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2940 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2941 mTapDetector.Attach( mGrabArea );
2942 mPanGestureDetector.Attach( mGrabArea );
2944 parent.Add(mGrabArea);
2947 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2949 Vector3 actualHandlePosition;
2953 mActualGrabHandlePosition.x += displacement.x;
2954 mActualGrabHandlePosition.y += displacement.y;
2956 // Grab handle should jump to the nearest character and take cursor with it
2957 std::size_t newCursorPosition = 0;
2958 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2960 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2962 bool handleVisible = true;
2964 if( IsScrollEnabled() )
2966 const Vector3 controlSize = GetControlSize();
2967 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
2968 // Scrolls the text if the handle is not in a visible position
2969 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2976 mCurrentHandlePosition = actualHandlePosition;
2977 mScrollDisplacement = Vector2::ZERO;
2981 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
2983 mScrollDisplacement.x = -SCROLL_SPEED;
2985 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
2987 mScrollDisplacement.x = SCROLL_SPEED;
2989 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
2991 mScrollDisplacement.y = -SCROLL_SPEED;
2993 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
2995 mScrollDisplacement.y = SCROLL_SPEED;
3001 if( handleVisible && // Only redraw cursor and do updates if position changed
3002 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3004 mCursorPosition = newCursorPosition;
3006 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3008 const TextStyle oldInputStyle( mInputStyle );
3010 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3012 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3014 if( oldInputStyle != mInputStyle )
3016 // Updates the line height accordingly with the input style.
3019 EmitStyleChangedSignal();
3024 return actualHandlePosition;
3027 void TextInput::ShowGrabHandle( bool visible )
3029 if ( IsGrabHandleEnabled() )
3033 mGrabHandle.SetVisible( mGrabHandleVisibility );
3035 StartMonitoringStageForTouch();
3039 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3041 mGrabHandleVisibility = visible;
3042 ShowGrabHandle( visible );
3045 // Callbacks connected to be Property notifications for Boundary checking.
3047 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3049 mIsSelectionHandleOneFlipped = true;
3050 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3051 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3054 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3056 mIsSelectionHandleOneFlipped = false;
3057 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3058 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3061 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3063 mIsSelectionHandleTwoFlipped = true;
3064 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3065 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3068 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3070 mIsSelectionHandleTwoFlipped = false;
3071 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3072 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3075 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3076 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3078 mSelectionHandleOne.SetOpacity(0.0f);
3081 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3083 mSelectionHandleOne.SetOpacity(1.0f);
3086 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3088 mSelectionHandleTwo.SetOpacity(0.0f);
3091 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3093 mSelectionHandleTwo.SetOpacity(1.0f);
3096 // End of Callbacks connected to be Property notifications for Boundary checking.
3098 void TextInput::SetUpHandlePropertyNotifications()
3100 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3102 Vector3 handlesize = GetSelectionHandleSize();
3104 // Exceeding horizontal boundary
3105 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3106 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3108 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3109 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3111 // Within horizontal boundary
3112 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3113 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3115 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3116 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3118 // Exceeding vertical boundary
3119 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3120 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3121 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3122 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3124 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3125 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3126 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3127 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3129 // Within vertical boundary
3130 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3131 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3132 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3133 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3135 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3136 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3137 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3138 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3141 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3143 mSelectionHandleOnePosition = start;
3144 mSelectionHandleTwoPosition = end;
3146 if ( !mSelectionHandleOne )
3148 // create normal and pressed images
3149 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3150 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3152 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3153 mSelectionHandleOne.SetName("SelectionHandleOne");
3154 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3155 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3156 mIsSelectionHandleOneFlipped = false;
3157 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3159 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3160 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3162 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3163 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3165 mTapDetector.Attach( mHandleOneGrabArea );
3166 mPanGestureDetector.Attach( mHandleOneGrabArea );
3168 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3170 mSelectionHandleOne.Add( mHandleOneGrabArea );
3171 mActiveLayer.Add( mSelectionHandleOne );
3174 if ( !mSelectionHandleTwo )
3176 // create normal and pressed images
3177 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3178 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3180 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3181 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3182 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3183 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3184 mIsSelectionHandleTwoFlipped = false;
3185 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3187 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3188 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3189 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3190 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3192 mTapDetector.Attach( mHandleTwoGrabArea );
3193 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3195 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3197 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3199 mActiveLayer.Add( mSelectionHandleTwo );
3202 SetUpHandlePropertyNotifications();
3204 // update table as text may have changed.
3205 GetTextLayoutInfo();
3207 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3208 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3210 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3211 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3213 // Calculates and set the visibility if the scroll mode is enabled.
3214 bool isSelectionHandleOneVisible = true;
3215 bool isSelectionHandleTwoVisible = true;
3216 if( IsScrollEnabled() )
3218 const Vector3& controlSize( GetControlSize() );
3219 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3220 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3221 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3222 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3225 CreateHighlight(); // function will only create highlight if not already created.
3228 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3230 Vector3 actualHandlePosition;
3232 if ( mSelectionHandleOne && mSelectionHandleTwo )
3234 const Vector3& controlSize = GetControlSize();
3236 Size cursorSize( CURSOR_THICKNESS, 0.f );
3238 // Get a reference of the wanted selection handle (handle one or two).
3239 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3241 // Get a reference for the current position of the handle and a copy of its pair
3242 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3243 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3245 // Get a handle of the selection handle actor
3246 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3248 // Selection handles should jump to the nearest character
3249 std::size_t newHandlePosition = 0;
3250 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3252 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3254 bool handleVisible = true;
3256 if( IsScrollEnabled() )
3258 mCurrentSelectionId = handleId;
3260 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3261 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3262 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3269 mCurrentSelectionHandlePosition = actualHandlePosition;
3270 mScrollDisplacement = Vector2::ZERO;
3274 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3276 mScrollDisplacement.x = -SCROLL_SPEED;
3278 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3280 mScrollDisplacement.x = SCROLL_SPEED;
3282 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3284 mScrollDisplacement.y = -SCROLL_SPEED;
3286 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3288 mScrollDisplacement.y = SCROLL_SPEED;
3294 if ( handleVisible && // Ensure the handle is visible.
3295 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3296 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3298 currentSelectionHandlePosition = newHandlePosition;
3300 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3301 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3305 if ( handleId == HandleOne )
3307 const TextStyle oldInputStyle( mInputStyle );
3309 // Set Active Style to that of first character in selection
3310 if( mSelectionHandleOnePosition < mStyledText.size() )
3312 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3315 if( oldInputStyle != mInputStyle )
3317 // Updates the line height accordingly with the input style.
3320 EmitStyleChangedSignal();
3326 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3329 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3332 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3333 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3335 if ( selectionHandleActor )
3337 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3338 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3339 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3341 if( IsScrollEnabled() )
3343 const Size cursorSize( CURSOR_THICKNESS,
3344 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3345 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3347 GetControlSize() ) );
3352 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3354 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3355 // For now the visual position of end of logical string will be end of visual string.
3356 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3358 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3361 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3363 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3364 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3365 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3366 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3368 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3370 // Deselect text prior to startSelectionIt
3371 for(;it!=startSelectionIt;++it)
3373 selectedVisualText[*it] = false;
3376 // Select text from startSelectionIt -> endSelectionIt
3377 for(;it!=endSelectionIt;++it)
3379 selectedVisualText[*it] = true;
3382 // Deselect text after endSelection
3385 selectedVisualText[*it] = false;
3388 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3391 // Calculate the dimensions of the quads they will make the highlight mesh
3392 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3394 // At the moment there is no public API to modify the block alignment option.
3395 const bool blockAlignEnabled = true;
3397 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3399 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3401 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3402 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3404 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3405 std::vector<bool> selectedVisualText;
3406 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3407 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3408 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3410 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3411 float rowLeft = 0.0f;
3412 float rowRight = 0.0f;
3413 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3414 float maxRowLeft = std::numeric_limits<float>::max();
3415 float maxRowRight = 0.0f;
3417 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3419 // Scan through entire text.
3422 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3424 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3425 bool charSelected( false );
3426 if( selectedIt != selectedEndIt )
3428 charSelected = *selectedIt++;
3431 if(selectionState == SelectionNone)
3435 selectionState = SelectionStarted;
3436 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3437 rowRight = rowLeft + charInfo.mSize.width;
3440 else if(selectionState == SelectionStarted)
3442 // break selection on:
3443 // 1. new line causing selection break. (\n or wordwrap)
3444 // 2. character not selected.
3445 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3448 // finished selection.
3449 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3450 // that it resides on. That way this enumeration is not necessary.
3452 if(lastIt->mIsNewLineChar)
3454 // 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.
3455 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3457 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3458 maxRowLeft = std::min(maxRowLeft, min.x);
3459 maxRowRight = std::max(maxRowRight, max.x);
3460 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3461 float rowTop = rowBottom - rowSize.height;
3463 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3464 if(charSelected && blockAlignEnabled)
3466 rowRight = std::numeric_limits<float>::max();
3468 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3470 selectionState = SelectionNone;
3472 // Still selected? start a new selection
3475 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3476 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3477 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3478 selectionState = SelectionStarted;
3483 // build up highlight(s) with this selection data.
3484 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3485 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3492 // If reached end, and still on selection, then close selection.
3495 if(selectionState == SelectionStarted)
3497 // finished selection.
3499 if(lastIt->mIsNewLineChar)
3501 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3503 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3504 maxRowLeft = std::min(maxRowLeft, min.x);
3505 maxRowRight = std::max(maxRowRight, max.x);
3506 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3507 float rowTop = rowBottom - rowSize.height;
3508 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3512 // Get the top left and bottom right corners.
3513 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3514 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3515 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3517 // Clamp quads so they appear to clip to borders of the whole text.
3518 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3520 // For block-align align Further Clamp quads to max left and right extents
3521 if(blockAlignEnabled)
3523 // BlockAlign: Will adjust highlight to block:
3525 // H[ello] (top row right = max of all rows right)
3526 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3527 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3528 // [text] (bottom row left = min of all rows left)
3529 // (common in SMS messaging selection)
3531 // As opposed to the default which is tight text highlighting.
3536 // (common in regular text editors/web browser selection)
3538 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3541 // Finally clamp quads again so they don't exceed the boundry of the control.
3542 const Vector3& controlSize = GetControlSize();
3543 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3546 return mNewHighlightInfo;
3549 void TextInput::UpdateHighlight()
3551 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3553 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3555 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3556 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3557 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3558 // [BOTTOM] [ MIDDLE ]
3561 // Each quad is created as 2 triangles.
3562 // Middle is just 1 quad regardless of its size.
3576 if ( mHighlightMeshActor )
3578 // vertex and triangle buffers should always be present if MeshActor is alive.
3579 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3580 MeshData::VertexContainer vertices;
3581 Dali::MeshData::FaceIndices faceIndices;
3583 if( !newHighlightInfo.mQuadList.empty() )
3585 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3586 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3588 // vertex position defaults to (0 0 0)
3589 MeshData::Vertex vertex;
3590 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3593 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3595 // Add each quad geometry (a sub-selection) to the mesh data.
3605 QuadCoordinates& quad = *iter;
3607 vertex.x = quad.min.x;
3608 vertex.y = quad.min.y;
3609 vertices.push_back( vertex );
3612 vertex.x = quad.max.x;
3613 vertex.y = quad.min.y;
3614 vertices.push_back( vertex );
3616 // bottom-left (v+2)
3617 vertex.x = quad.min.x;
3618 vertex.y = quad.max.y;
3619 vertices.push_back( vertex );
3621 // bottom-right (v+3)
3622 vertex.x = quad.max.x;
3623 vertex.y = quad.max.y;
3624 vertices.push_back( vertex );
3626 // triangle A (3, 1, 0)
3627 faceIndices.push_back( v + 3 );
3628 faceIndices.push_back( v + 1 );
3629 faceIndices.push_back( v );
3631 // triangle B (0, 2, 3)
3632 faceIndices.push_back( v );
3633 faceIndices.push_back( v + 2 );
3634 faceIndices.push_back( v + 3 );
3636 mMeshData.SetFaceIndices( faceIndices );
3639 BoneContainer bones(0); // passed empty as bones not required
3640 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3641 mHighlightMesh.UpdateMeshData(mMeshData);
3646 void TextInput::ClearPopup()
3648 mPopUpPanel.Clear();
3651 void TextInput::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
3653 mPopUpPanel.AddOption(name, caption, icon, finalOption);
3656 void TextInput::SetPopupPosition(const Vector3& position)
3658 mPopUpPanel.Self().SetPosition( position );
3661 void TextInput::HidePopup(bool animate, bool signalFinished )
3663 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3665 mPopUpPanel.Hide( animate );
3667 if( animate && signalFinished )
3669 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3674 void TextInput::ShowPopup(bool animate)
3678 if(mHighlightMeshActor && mState == StateEdit)
3682 // When text is selected, show popup above top handle (and text), or below bottom handle.
3683 // topHandle: referring to the top most point of the handle or the top line of selection.
3684 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3686 topHandle = mSelectionHandleOneActualPosition;
3687 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3691 topHandle = mSelectionHandleTwoActualPosition;
3692 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3694 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3695 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3697 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3698 Vector3 bottomHandle;
3699 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3700 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3701 mPopUpPanel.SetAlternativeOffset(Vector2(0.0f, bottomHandle.y - topHandle.y));
3705 // When no text is selected, show popup at world position of grab handle or cursor
3706 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3707 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3708 position.y -= rowSize.height;
3709 // if can't be positioned above, then position below row.
3710 Vector2 alternativePopUpPosition( 0.0f, position.y ); // default if no grab handle
3713 alternativePopUpPosition.y = rowSize.height + ( mGrabHandle.GetCurrentSize().height * DEFAULT_GRAB_HANDLE_RELATIVE_SIZE.y ) ;
3714 // If grab handle enabled then position pop-up below the grab handle.
3716 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3719 // reposition popup above the desired cursor posiiton.
3720 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3721 textViewSize.z = 0.0f;
3722 // World position = world position of ParentOrigin of cursor (i.e. top-left corner of TextView) + cursor position;
3723 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3725 SetPopupPosition( worldPosition );
3728 mPopUpPanel.Show(animate);
3729 StartMonitoringStageForTouch();
3731 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3734 void TextInput::ShowPopupCutCopyPaste()
3737 // Check the selected text is whole text or not.
3738 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3740 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3741 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3744 if ( !mStyledText.empty() )
3746 Image cutIcon = Image::New( DEFAULT_ICON_CUT );
3747 Image copyIcon = Image::New( DEFAULT_ICON_COPY );
3748 AddPopupOption( OPTION_CUT, GET_LOCALE_TEXT("IDS_COM_BODY_CUT"), cutIcon );
3749 AddPopupOption( OPTION_COPY, GET_LOCALE_TEXT("IDS_COM_BODY_COPY"), copyIcon, true );
3752 if(mClipboard.NumberOfItems())
3754 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3755 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3756 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon );
3757 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3760 mPopUpPanel.Hide(false);
3764 void TextInput::SetUpPopUpSelection()
3768 // If no text exists then don't offer to select
3769 if ( !mStyledText.empty() )
3771 Image selectIcon = Image::New( DEFAULT_ICON_SELECT );
3772 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3773 AddPopupOption( OPTION_SELECT_WORD, GET_LOCALE_TEXT("IDS_COM_SK_SELECT"), selectIcon );
3774 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3776 // if clipboard has valid contents then offer paste option
3777 if( mClipboard.NumberOfItems() )
3779 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3780 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3781 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon, true );
3782 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3785 mPopUpPanel.Hide(false);
3788 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3793 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3794 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3795 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3796 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3798 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3800 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3802 float closestYdifference = std::numeric_limits<float>::max();
3803 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3804 std::size_t numberOfMatchedCharacters = 0;
3806 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3807 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3809 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3811 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3812 float baselinePosition = info.mPosition.y - info.mDescender;
3814 if( info.mIsVisible )
3816 // store difference between source y point and the y position of the current character
3817 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3819 if( currentYdifference < closestYdifference )
3821 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3822 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3823 closestYdifference = currentYdifference;
3824 matchedCharacters.clear();
3825 numberOfMatchedCharacters = 0; // reset count
3828 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3829 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3831 // ignore new line character.
3832 if( !info.mIsNewLineChar )
3834 matchedCharacters.push_back( info );
3835 numberOfMatchedCharacters++;
3839 } // End of loop checking each character's y position in the character layout table
3841 // Check if last character is a newline, if it is
3842 // then need pretend there is an imaginary line afterwards,
3843 // and check if user is touching below previous line.
3844 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3846 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3848 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3852 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3853 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3855 bool matched( false );
3857 // 2 Iterate through matching list of y positions and find closest matching X position.
3858 for( ; it != endIt; ++it )
3860 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3862 if( info.mIsVisible )
3864 // stop when on left side of character's center.
3865 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3866 if( sourceScrollOffset.x < characterMidPointPosition )
3868 if(info.mIsRightToLeftCharacter)
3870 rightToLeftChar = true;
3872 glyphIntersection = info.mPosition.x;
3877 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3883 rightToLeftChar = lastRightToLeftChar;
3886 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3887 closestIndex = lineOffset + matchCharacterIndex;
3889 mClosestCursorPositionEOL = false; // reset
3890 if ( it == endIt && !matched )
3892 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3895 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3896 if( rightToLeftChar && lastRightToLeftChar )
3898 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3903 // closestIndex is the visual index, need to convert it to the logical index
3904 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3906 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3908 // Checks for situations where user is touching between LTR and RTL
3909 // characters. To identify if the user means the end of a LTR string
3910 // or the beginning of an RTL string, and vice versa.
3911 if( closestIndex > 0 )
3913 if( rightToLeftChar && !lastRightToLeftChar )
3918 // A: In this touch range, the user is indicating that they wish to place
3919 // the cursor at the end of the LTR text.
3920 // B: In this touch range, the user is indicating that they wish to place
3921 // the cursor at the end of the RTL text.
3923 // Result of touching A area:
3924 // [.....LTR]|[RTL......]+
3926 // |: primary cursor (for typing LTR chars)
3927 // +: secondary cursor (for typing RTL chars)
3929 // Result of touching B area:
3930 // [.....LTR]+[RTL......]|
3932 // |: primary cursor (for typing RTL chars)
3933 // +: secondary cursor (for typing LTR chars)
3935 if( sourceScrollOffset.x < glyphIntersection )
3940 else if( !rightToLeftChar && lastRightToLeftChar )
3942 if( sourceScrollOffset.x < glyphIntersection )
3949 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3950 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3951 // one further ahead
3952 if( rightToLeftChar && !lastRightToLeftChar )
3957 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3959 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3961 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3970 float TextInput::GetLineJustificationPosition() const
3972 const Vector3& size = mDisplayedTextView.GetCurrentSize();
3973 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
3974 float alignmentOffset = 0.f;
3976 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
3977 if( alignment & Toolkit::Alignment::HorizontalLeft )
3979 alignmentOffset = 0.f;
3981 else if( alignment & Toolkit::Alignment::HorizontalCenter )
3983 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
3985 else if( alignment & Toolkit::Alignment::HorizontalRight )
3987 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
3990 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
3991 float justificationOffset = 0.f;
3993 switch( justification )
3995 case Toolkit::TextView::Left:
3997 justificationOffset = 0.f;
4000 case Toolkit::TextView::Center:
4002 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4005 case Toolkit::TextView::Right:
4007 justificationOffset = mTextLayoutInfo.mTextSize.width;
4010 case Toolkit::TextView::Justified:
4012 justificationOffset = 0.f;
4017 DALI_ASSERT_ALWAYS( false );
4021 return alignmentOffset + justificationOffset;
4024 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4026 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4027 A newline character is not inserted in this case */
4029 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4031 Vector3 cursorPosition;
4033 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4035 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4037 // end character so use
4038 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4039 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4043 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4046 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4048 // If previous character on a different line then use current characters position
4049 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4051 if ( mClosestCursorPositionEOL )
4053 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4057 cursorPosition = Vector3(currentCharInfo.mPosition);
4062 // Previous character is on same line so use position of previous character plus it's width.
4063 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4066 return cursorPosition;
4069 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4071 bool direction(false);
4072 Vector3 alternatePosition;
4073 bool alternatePositionValid(false);
4075 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4078 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4080 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4082 alternatePositionValid = false;
4083 directionRTL = false;
4085 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4087 std::size_t visualCharacterPosition;
4089 // When cursor is not at beginning, consider possibility of
4090 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4091 if(characterPosition > 0)
4093 // Cursor position should be the end of the last character.
4094 // If the last character is LTR, then the end is on the right side of the glyph.
4095 // If the last character is RTL, then the end is on the left side of the glyph.
4096 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4098 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4100 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4103 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4104 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4106 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4107 const Vector3& size = GetControlSize();
4109 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4111 --visualCharacterPosition;
4113 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4116 if(!info.mIsNewLineChar)
4118 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4122 // When cursor points to first character on new line, position cursor at the start of this glyph.
4123 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4125 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4126 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4127 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4129 cursorPosition.x = infoNext.mPosition.x + start;
4130 cursorPosition.y = infoNext.mPosition.y;
4134 // If cursor points to the end of text, then can only position
4135 // cursor where the new line starts based on the line-justification position.
4136 cursorPosition.x = GetLineJustificationPosition();
4138 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4140 // If this is after the last character, then we can assume that the new cursor
4141 // should be exactly one row below the current row.
4143 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4144 cursorPosition.y = info.mPosition.y + rowRect.height;
4148 // If this is not after last character, then we can use this row's height.
4149 // should be exactly one row below the current row.
4151 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4152 cursorPosition.y = info.mPosition.y + rowRect.height;
4157 directionRTL = info.mIsRightToLeftCharacter;
4159 // 1. When the cursor is neither at the beginning or the end,
4160 // we can show multiple cursors under situations when the cursor is
4161 // between RTL and LTR text...
4162 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4164 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4166 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4167 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4169 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4171 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4172 // Text: [...LTR...]|[...RTL...]
4174 // Alternate cursor pos: ^
4175 // In which case we need to display an alternate cursor for the RTL text.
4177 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4178 alternatePosition.y = infoAlt.mPosition.y;
4179 alternatePositionValid = true;
4181 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4183 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4184 // Text: |[...RTL...] [...LTR....]
4186 // Alternate cursor pos: ^
4187 // In which case we need to display an alternate cursor for the RTL text.
4189 alternatePosition.x = infoAlt.mPosition.x;
4190 alternatePosition.y = infoAlt.mPosition.y;
4191 alternatePositionValid = true;
4196 // 2. When the cursor is at the end of the text,
4197 // and we have multi-directional text,
4198 // we can also consider showing mulitple cursors.
4199 // The rule here is:
4200 // If first and last characters on row are different
4201 // Directions, then two cursors need to be displayed.
4203 // Get first logical glyph on row
4204 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4206 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4207 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4209 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4211 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4212 // Text: [...LTR...]|[...RTL...]
4214 // Alternate cursor pos: ^
4215 // In which case we need to display an alternate cursor for the RTL text, this cursor
4216 // should be at the end of the given line.
4218 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4219 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4220 alternatePosition.y = infoAlt.mPosition.y;
4221 alternatePositionValid = true;
4223 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4225 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4226 // Text: |[...RTL...] [...LTR....]
4228 // Alternate cursor pos: ^
4229 // In which case we need to display an alternate cursor for the RTL text.
4231 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4232 alternatePosition.x = infoAlt.mPosition.x;
4233 alternatePosition.y = infoAlt.mPosition.y;
4234 alternatePositionValid = true;
4237 } // characterPosition > 0
4238 else if(characterPosition == 0)
4240 // When the cursor position is at the beginning, it should be at the start of the current character.
4241 // If the current character is LTR, then the start is on the right side of the glyph.
4242 // If the current character is RTL, then the start is on the left side of the glyph.
4243 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4245 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4247 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4250 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4251 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4253 cursorPosition.x = info.mPosition.x + start;
4254 cursorPosition.y = info.mPosition.y;
4255 directionRTL = info.mIsRightToLeftCharacter;
4260 // If the character table is void, place the cursor accordingly the text alignment.
4261 const Vector3& size = GetControlSize();
4263 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4264 float alignmentOffset = 0.f;
4266 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4267 if( alignment & Toolkit::Alignment::HorizontalLeft )
4269 alignmentOffset = 0.f;
4271 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4273 alignmentOffset = 0.5f * ( size.width );
4275 else if( alignment & Toolkit::Alignment::HorizontalRight )
4277 alignmentOffset = size.width;
4280 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4281 cursorPosition.x = alignmentOffset;
4283 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4284 if( alignment & Toolkit::Alignment::VerticalTop )
4286 cursorPosition.y = mLineHeight;
4288 else if( alignment & Toolkit::Alignment::VerticalCenter )
4290 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4292 else if( alignment & Toolkit::Alignment::VerticalBottom )
4294 cursorPosition.y = size.height;
4298 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4299 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4300 if( alternatePositionValid )
4302 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4303 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4306 return cursorPosition;
4309 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4311 // scan string from current position to beginning of current line to note direction of line
4312 while(logicalPosition)
4315 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4316 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4323 return logicalPosition;
4326 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4330 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4333 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4335 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4336 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4338 min = Vector2::ZERO;
4339 max = Vector2(0.0f, mLineHeight);
4343 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4344 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4345 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4347 // If cursor is pointing to end of line, then start from last character.
4348 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4350 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4352 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4353 if( !it->mIsVisible )
4355 characterPosition = FindVisibleCharacter( Left, characterPosition );
4356 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4359 // Scan characters left and right of cursor, stopping when end of line/string reached or
4360 // y position greater than threshold of reference line.
4362 // 1. scan left until we reach the beginning or a different line.
4363 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4364 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4365 // min-x position is the left-most char's left (x)
4366 // max-x position is the right-most char's right (x)
4367 // min-y position is the minimum of all character's top (y)
4368 // max-y position is the maximum of all character's bottom (y+height)
4369 min.y = validCharIt->mPosition.y;
4370 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4375 min.y = std::min(min.y, validCharIt->mPosition.y);
4376 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4385 if( (it->mPosition.y < referenceLine) ||
4386 (it->mIsNewLineChar) ||
4393 // info refers to the first character on this line.
4394 min.x = validCharIt->mPosition.x;
4396 // 2. scan right until we reach end or a different line
4397 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4398 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4402 if( (it->mPosition.y > referenceLine) ||
4403 (it->mIsNewLineChar) ||
4410 min.y = std::min(min.y, validCharIt->mPosition.y);
4411 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4416 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4418 if ( validCharIt != end )
4420 // info refers to the last character on this line.
4421 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4424 return Size( max.x - min.x, max.y - min.y );
4427 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4429 Actor popUpPanel = mPopUpPanel.GetRootActor();
4431 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4437 Dali::Actor parent( touchedActor.GetParent() );
4441 return WasTouchedCheck( parent );
4448 void TextInput::StartMonitoringStageForTouch()
4450 Stage stage = Stage::GetCurrent();
4451 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4454 void TextInput::EndMonitoringStageForTouch()
4456 Stage stage = Stage::GetCurrent();
4457 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4460 void TextInput::OnStageTouched(const TouchEvent& event)
4462 if( event.GetPointCount() > 0 )
4464 if ( TouchPoint::Down == event.GetPoint(0).state )
4466 const Actor touchedActor(event.GetPoint(0).hitActor);
4468 bool popUpShown( false );
4470 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4475 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4477 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4479 EndMonitoringStageForTouch();
4480 HidePopup( true, false );
4483 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4485 EndMonitoringStageForTouch();
4486 ShowGrabHandleAndSetVisibility( false );
4492 void TextInput::SelectText(std::size_t start, std::size_t end)
4494 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4495 IsGrabHandleEnabled()?"true":"false",
4496 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4497 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4498 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4500 StartMonitoringStageForTouch();
4502 if ( mEditModeActive ) // Only allow text selection when in edit mode
4504 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4505 mSelectingText = true;
4507 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4509 ImfManager imfManager = ImfManager::Get();
4512 imfManager.SetCursorPosition ( mCursorPosition );
4513 imfManager.SetSurroundingText( GetText() );
4514 imfManager.NotifyCursorPosition();
4516 // 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.
4518 // Hide grab handle when selecting.
4519 ShowGrabHandleAndSetVisibility( false );
4521 if( start != end ) // something to select
4523 SetCursorVisibility( false );
4524 StopCursorBlinkTimer();
4526 CreateSelectionHandles(start, end);
4529 const TextStyle oldInputStyle( mInputStyle );
4530 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4532 if( oldInputStyle != mInputStyle )
4534 // Updates the line height accordingly with the input style.
4537 EmitStyleChangedSignal();
4543 mSelectingText = false;
4547 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4549 MarkupProcessor::StyledTextArray currentSelectedText;
4551 if ( IsTextSelected() )
4553 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4554 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4556 for(; it != end; ++it)
4558 MarkupProcessor::StyledText& styledText( *it );
4559 currentSelectedText.push_back( styledText );
4562 return currentSelectedText;
4565 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4567 const std::size_t beginIndex = std::min( begin, end );
4568 const std::size_t endIndex = std::max( begin, end );
4571 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4573 // Create a styled text array used to replace the text into the text-view.
4574 MarkupProcessor::StyledTextArray text;
4575 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4577 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4578 GetTextLayoutInfo();
4580 if( IsScrollEnabled() )
4582 // Need to set the scroll position as the text's size may have changed.
4583 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4586 ShowGrabHandleAndSetVisibility( false );
4592 // Set Handle positioning as the new style may have repositioned the characters.
4593 SetSelectionHandlePosition(HandleOne);
4594 SetSelectionHandlePosition(HandleTwo);
4597 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4599 // Just hide the grab handle when keyboard is hidden.
4600 if (!keyboardShown )
4602 ShowGrabHandleAndSetVisibility( false );
4604 // If the keyboard is not now being shown, then hide the popup panel
4605 mPopUpPanel.Hide( true );
4609 // Removes highlight and resumes edit mode state
4610 void TextInput::RemoveHighlight()
4612 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4614 if ( mHighlightMeshActor )
4616 if ( mSelectionHandleOne )
4618 mActiveLayer.Remove( mSelectionHandleOne );
4619 mSelectionHandleOne.Reset();
4620 mSelectionHandleOneOffset.x = 0.0f;
4622 if ( mSelectionHandleTwo )
4624 mActiveLayer.Remove( mSelectionHandleTwo );
4625 mSelectionHandleTwo.Reset();
4626 mSelectionHandleTwoOffset.x = 0.0f;
4629 mNewHighlightInfo.mQuadList.clear();
4631 Self().Remove( mHighlightMeshActor );
4633 SetCursorVisibility( true );
4634 StartCursorBlinkTimer();
4636 mHighlightMeshActor.Reset();
4637 // NOTE: We cannot dereference mHighlightMesh, due
4638 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4643 mSelectionHandleOnePosition = 0;
4644 mSelectionHandleTwoPosition = 0;
4647 void TextInput::CreateHighlight()
4649 if ( !mHighlightMeshActor )
4651 mMeshData = MeshData( );
4652 mMeshData.SetHasNormals( true );
4654 mCustomMaterial = Material::New("CustomMaterial");
4655 mCustomMaterial.SetDiffuseColor( LIGHTBLUE );
4657 mMeshData.SetMaterial( mCustomMaterial );
4659 mHighlightMesh = Mesh::New( mMeshData );
4661 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4662 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4663 mHighlightMeshActor.SetInheritShaderEffect( false );
4664 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4665 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4666 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4667 mHighlightMeshActor.SetAffectedByLighting(false);
4669 Self().Add(mHighlightMeshActor);
4674 bool TextInput::CopySelectedTextToClipboard()
4676 mCurrentCopySelecton.clear();
4678 mCurrentCopySelecton = GetSelectedText();
4680 std::string stringToStore;
4682 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4683 * a marked up string.
4685 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4686 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4687 bool success = mClipboard.SetItem( stringToStore );
4691 void TextInput::PasteText( const Text& text )
4693 // Update Flag, indicates whether to update the text-input contents or not.
4694 // Any key stroke that results in a visual change of the text-input should
4695 // set this flag to true.
4696 bool update = false;
4697 if( mHighlightMeshActor )
4699 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4700 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4702 ImfManager imfManager = ImfManager::Get();
4705 imfManager.SetCursorPosition( mCursorPosition );
4706 imfManager.NotifyCursorPosition();
4708 DeleteHighlightedText( true );
4712 bool textExceedsMaximunNumberOfCharacters = false;
4713 bool textExceedsBoundary = false;
4715 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4717 mCursorPosition += insertedStringLength;
4718 ImfManager imfManager = ImfManager::Get();
4721 imfManager.SetCursorPosition ( mCursorPosition );
4722 imfManager.NotifyCursorPosition();
4725 update = update || ( insertedStringLength > 0 );
4731 if( insertedStringLength < text.GetLength() )
4733 EmitMaxInputCharactersReachedSignal();
4736 if( textExceedsBoundary )
4738 EmitInputTextExceedsBoundariesSignal();
4742 void TextInput::SetTextDirection()
4744 // Put the cursor to the right if we are empty and an RTL language is being used.
4745 if ( mStyledText.empty() )
4747 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4749 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4750 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4752 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4753 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4755 int alignment( mDisplayedTextView.GetTextAlignment() &
4756 ( Toolkit::Alignment::VerticalTop |
4757 Toolkit::Alignment::VerticalCenter |
4758 Toolkit::Alignment::VerticalBottom |
4759 Toolkit::Alignment::HorizontalCenter ) );
4760 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4762 // If our alignment is in the center, then do not change.
4763 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4765 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4768 // If our justification is in the center, then do not change.
4769 if ( justification != Toolkit::TextView::Center )
4771 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4774 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4775 mDisplayedTextView.SetLineJustification( justification );
4779 void TextInput::UpdateLineHeight()
4781 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4782 mLineHeight = font.GetLineHeight();
4784 // 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.
4786 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4788 if( !mExceedEnabled || shrink )
4790 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4794 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4796 std::size_t position = 0;
4798 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4804 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4806 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4808 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4814 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4815 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4817 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4823 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4828 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4835 void TextInput::SetSortModifier( float depthOffset )
4837 if(mDisplayedTextView)
4839 mDisplayedTextView.SetSortModifier(depthOffset);
4843 void TextInput::SetSnapshotModeEnabled( bool enable )
4845 if(mDisplayedTextView)
4847 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4851 bool TextInput::IsSnapshotModeEnabled() const
4853 bool snapshotEnabled = false;
4855 if(mDisplayedTextView)
4857 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4860 return snapshotEnabled;
4863 void TextInput::SetMarkupProcessingEnabled( bool enable )
4865 mMarkUpEnabled = enable;
4868 bool TextInput::IsMarkupProcessingEnabled() const
4870 return mMarkUpEnabled;
4873 void TextInput::SetScrollEnabled( bool enable )
4875 if( mDisplayedTextView )
4877 mDisplayedTextView.SetScrollEnabled( enable );
4882 // Don't set cursor's and handle's visibility to false if they are outside the
4883 // boundaries of the text-input.
4884 mIsCursorInScrollArea = true;
4885 mIsGrabHandleInScrollArea = true;
4886 if( mSelectionHandleOne && mSelectionHandleTwo )
4888 mSelectionHandleOne.SetVisible( true );
4889 mSelectionHandleTwo.SetVisible( true );
4891 if( mHighlightMeshActor )
4893 mHighlightMeshActor.SetVisible( true );
4899 bool TextInput::IsScrollEnabled() const
4901 bool scrollEnabled = false;
4903 if( mDisplayedTextView )
4905 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4908 return scrollEnabled;
4911 void TextInput::SetScrollPosition( const Vector2& position )
4913 if( mDisplayedTextView )
4915 mDisplayedTextView.SetScrollPosition( position );
4919 Vector2 TextInput::GetScrollPosition() const
4921 Vector2 scrollPosition;
4923 if( mDisplayedTextView )
4925 scrollPosition = mDisplayedTextView.GetScrollPosition();
4928 return scrollPosition;
4931 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4933 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4934 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4935 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4937 // Add style to the new input text.
4938 MarkupProcessor::StyledTextArray textToInsert;
4939 for( std::size_t i = 0; i < insertedStringLength; ++i )
4941 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4942 textToInsert.push_back( newStyledCharacter );
4945 //Insert text to the TextView.
4946 const bool emptyTextView = mStyledText.empty();
4947 if( emptyTextView && mPlaceHolderSet )
4949 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4950 mDisplayedTextView.SetText( textToInsert );
4954 if( 0 == numberOfCharactersToReplace )
4956 mDisplayedTextView.InsertTextAt( position, textToInsert );
4960 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4963 mPlaceHolderSet = false;
4965 if( textToInsert.empty() )
4967 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4968 GetTextLayoutInfo();
4972 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4973 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4976 textExceedsBoundary = false;
4978 if( !mExceedEnabled )
4980 const Vector3& size = GetControlSize();
4982 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
4984 // If new text does not fit within TextView
4985 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
4986 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
4987 GetTextLayoutInfo();
4988 textExceedsBoundary = true;
4989 insertedStringLength = 0;
4992 if( textExceedsBoundary )
4994 // Add the part of the text which fits on the text-input.
4996 // Split the text which doesn't fit in two halves.
4997 MarkupProcessor::StyledTextArray firstHalf;
4998 MarkupProcessor::StyledTextArray secondHalf;
4999 SplitText( textToInsert, firstHalf, secondHalf );
5001 // Clear text. This text will be filled with the text inserted.
5002 textToInsert.clear();
5004 // Where to insert the text.
5005 std::size_t positionToInsert = position;
5007 bool end = text.GetLength() <= 1;
5010 // Insert text and check ...
5011 const std::size_t textLength = firstHalf.size();
5012 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5013 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5015 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5017 // Inserted text doesn't fit.
5019 // Remove inserted text
5020 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5021 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5023 // The iteration finishes when only one character doesn't fit.
5024 end = textLength <= 1;
5028 // Prepare next two halves for next iteration.
5029 MarkupProcessor::StyledTextArray copyText = firstHalf;
5030 SplitText( copyText, firstHalf, secondHalf );
5037 // store text to be inserted in mStyledText.
5038 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5040 // Increase the inserted characters counter.
5041 insertedStringLength += textLength;
5043 // Prepare next two halves for next iteration.
5044 MarkupProcessor::StyledTextArray copyText = secondHalf;
5045 SplitText( copyText, firstHalf, secondHalf );
5047 // Update where next text has to be inserted
5048 positionToInsert += textLength;
5054 if( textToInsert.empty() && emptyTextView )
5056 // No character has been added and the text-view was empty.
5057 // Set the placeholder text.
5058 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5059 mPlaceHolderSet = true;
5063 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5064 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5065 mPlaceHolderSet = false;
5068 return insertedStringLength;
5071 void TextInput::GetTextLayoutInfo()
5073 if( mStyledText.empty() )
5075 // The text-input has no text, clear the text-view's layout info.
5076 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5080 if( mDisplayedTextView )
5082 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5086 // There is no text-view.
5087 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5092 void TextInput::EmitStyleChangedSignal()
5094 // emit signal if input style changes.
5096 Toolkit::TextInput handle( GetOwner() );
5097 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5100 void TextInput::EmitMaxInputCharactersReachedSignal()
5102 // emit signal if max characters is reached during text input.
5103 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5105 Toolkit::TextInput handle( GetOwner() );
5106 mMaxInputCharactersReachedSignalV2.Emit( handle );
5109 void TextInput::EmitInputTextExceedsBoundariesSignal()
5111 // Emit a signal when the input text exceeds the boundaries of the text input.
5113 Toolkit::TextInput handle( GetOwner() );
5114 mInputTextExceedBoundariesSignalV2.Emit( handle );
5117 } // namespace Internal
5119 } // namespace Toolkit