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()
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 );
450 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
452 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
454 mDisplayedTextView.SetText( styleText );
455 mPlaceHolderSet = false;
457 // If text alignment hasn't been manually set by application developer, then we
458 // automatically determine the alignment based on the content of the text i.e. what
459 // language the text begins with.
460 // TODO: This should determine different alignments for each line (broken by '\n') of text.
461 if(!mOverrideAutomaticAlignment)
463 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
464 bool leftToRight(true);
466 if( !styleText.empty() )
468 bool breakOut(false);
470 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
472 const Text& text = textIter->mText;
474 for( std::size_t i = 0; i < text.GetLength(); ++i )
476 Character character( text[i] );
477 if( character.GetCharacterDirection() != Character::Neutral )
479 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
487 // Based on this direction, either left or right align text if not manually set by application developer.
488 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
489 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
490 Toolkit::Alignment::VerticalTop ) );
491 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
497 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
499 mMaxStringLength = maxChars;
502 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
504 DALI_ASSERT_DEBUG( maxLines > 0 )
508 mNumberOflinesLimit = maxLines;
512 std::size_t TextInput::GetNumberOfLinesLimit() const
514 return mNumberOflinesLimit;
517 std::size_t TextInput::GetNumberOfCharacters() const
519 return mStyledText.size();
522 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
524 return mInputStartedSignalV2;
527 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
529 return mInputFinishedSignalV2;
532 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
534 return mCutAndPasteToolBarDisplayedV2;
537 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
539 return mStyleChangedSignalV2;
542 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
544 return mTextModifiedSignal;
547 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
549 return mMaxInputCharactersReachedSignalV2;
552 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
554 return mInputTextExceedBoundariesSignalV2;
557 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
559 Dali::BaseHandle handle( object );
561 bool connected( true );
562 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
564 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
566 textInput.InputStartedSignal().Connect( tracker, functor );
568 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
570 textInput.InputFinishedSignal().Connect( tracker, functor );
572 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
574 textInput.StyleChangedSignal().Connect( tracker, functor );
576 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
578 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
580 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
582 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
586 // signalName does not match any signal
593 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
597 // update line height before calculate the actual position.
602 if( setCursorOnTouchPoint )
604 // Sets the cursor position for the given touch point.
605 ReturnClosestIndex( touchPoint, mCursorPosition );
607 // Creates the grab handle.
608 if( IsGrabHandleEnabled() )
610 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
614 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
615 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
616 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
617 ShowGrabHandleAndSetVisibility( true );
619 // Scrolls the text-view if needed.
620 if( IsScrollEnabled() )
622 ScrollTextViewToMakeCursorVisible( cursorPosition );
628 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
640 bool TextInput::IsEditable() const
642 return mEditModeActive;
645 void TextInput::SetEditOnTouch( bool editOnTouch )
647 mEditOnTouch = editOnTouch;
650 bool TextInput::IsEditOnTouch() const
655 void TextInput::SetTextSelectable( bool textSelectable )
657 mTextSelection = textSelectable;
660 bool TextInput::IsTextSelectable() const
662 return mTextSelection;
665 bool TextInput::IsTextSelected() const
667 return mHighlightMeshActor;
670 void TextInput::DeSelectText()
677 void TextInput::SetGrabHandleImage(Dali::Image image )
681 CreateGrabHandle(image);
685 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
687 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
691 mCursor.SetImage( image );
692 mCursor.SetNinePatchBorder( border );
696 Vector3 TextInput::GetSelectionHandleSize()
698 return DEFAULT_SELECTION_HANDLE_SIZE;
701 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
703 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
707 mCursorRTL.SetImage( image);
708 mCursorRTL.SetNinePatchBorder( border );
712 void TextInput::EnableGrabHandle(bool toggle)
714 // enables grab handle with will in turn de-activate magnifier
715 mGrabHandleEnabled = toggle;
718 bool TextInput::IsGrabHandleEnabled()
720 // if false then magnifier will be shown instead.
721 return mGrabHandleEnabled;
724 void TextInput::EnableSelectionHandleFlip( bool toggle )
726 // Deprecated function. To be removed.
727 mIsSelectionHandleFlipEnabled = toggle;
730 bool TextInput::IsSelectionHandleFlipEnabled()
732 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
736 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
738 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
739 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
740 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
742 mSelectionHandleFlipMargin = margin;
745 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
747 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
748 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
750 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
751 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
753 const Vector4 boundary( originX,
755 originX + boundingRectangle.width,
756 originY + boundingRectangle.height );
758 mBoundingRectangleWorldCoordinates = boundary;
761 const Rect<float> TextInput::GetBoundingRectangle() const
763 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
765 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
766 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
768 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
773 const Vector4& TextInput::GetSelectionHandleFlipMargin()
775 return mSelectionHandleFlipMargin;
778 void TextInput::SetTextColor( const Vector4& color )
780 mDisplayedTextView.SetColor( color );
783 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
785 if( style != mInputStyle )
788 bool emitSignal = false;
790 // mask: modify style according to mask, if different emit signal.
791 const TextStyle oldInputStyle( mInputStyle );
793 // Copy the new style.
794 mInputStyle.Copy( style, mask );
796 // if style has changed, emit signal.
797 if( oldInputStyle != mInputStyle )
802 // Updates the line height accordingly with the input style.
805 // Changing font point size will require the cursor to be re-sized
810 EmitStyleChangedSignal();
815 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
817 if ( IsTextSelected() )
819 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
820 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
822 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
824 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
827 // Keeps the old style to be compared with the new one.
828 const TextStyle oldInputStyle( mInputStyle );
830 // Copy only those parameters from the style which are set in the mask.
831 mInputStyle.Copy( style, mask );
833 if( mInputStyle != oldInputStyle )
835 // Updates the line height accordingly with the input style.
838 EmitStyleChangedSignal();
843 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
845 if( !mStyledText.empty() )
847 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
851 TextStyle TextInput::GetStyleAtCursor() const
855 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
857 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
858 style = mStyledText.at( mCursorPosition-1 ).mStyle;
864 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
866 Dali::Font defaultFont = Dali::Font::New();
867 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
874 TextStyle TextInput::GetStyleAt( std::size_t position ) const
876 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
878 if( position >= mStyledText.size() )
880 position = mStyledText.size() - 1;
883 return mStyledText.at( position ).mStyle;
886 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
888 mDisplayedTextView.SetTextAlignment( align );
889 mOverrideAutomaticAlignment = true;
892 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
894 mDisplayedTextView.SetLineJustification( justification );
895 mOverrideAutomaticAlignment = true;
898 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
900 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
903 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
905 return mDisplayedTextView.GetFadeBoundary();
908 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
910 return mDisplayedTextView.GetTextAlignment();
913 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
915 mDisplayedTextView.SetMultilinePolicy( policy );
918 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
920 return mDisplayedTextView.GetMultilinePolicy();
923 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
925 mDisplayedTextView.SetWidthExceedPolicy( policy );
928 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
930 return mDisplayedTextView.GetWidthExceedPolicy();
933 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
935 mDisplayedTextView.SetHeightExceedPolicy( policy );
938 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
940 return mDisplayedTextView.GetHeightExceedPolicy();
943 void TextInput::SetExceedEnabled( bool enable )
945 mExceedEnabled = enable;
948 bool TextInput::GetExceedEnabled() const
950 return mExceedEnabled;
953 void TextInput::SetBackground(Dali::Image image )
955 // TODO Should add this function and add public api to match.
958 bool TextInput::OnTouchEvent(const TouchEvent& event)
963 bool TextInput::OnKeyEvent(const KeyEvent& event)
965 switch( event.state )
969 return OnKeyDownEvent(event);
975 return OnKeyUpEvent(event);
987 void TextInput::OnKeyInputFocusGained()
989 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
991 mEditModeActive = true;
993 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
995 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
997 // Updates the line height accordingly with the input style.
1000 // Connect the signals to use in text input.
1001 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1002 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1004 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1007 GetTextLayoutInfo();
1010 SetCursorVisibility( true );
1011 StartCursorBlinkTimer();
1013 Toolkit::TextInput handle( GetOwner() );
1014 mInputStartedSignalV2.Emit( handle );
1016 ImfManager imfManager = ImfManager::Get();
1020 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1022 // Notify that the text editing start.
1023 imfManager.Activate();
1025 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1026 imfManager.SetRestoreAferFocusLost( true );
1028 imfManager.SetCursorPosition( mCursorPosition );
1029 imfManager.NotifyCursorPosition();
1032 mClipboard = Clipboard::Get(); // Store handle to clipboard
1034 // Now in edit mode we can accept string to paste from clipboard
1035 if( Adaptor::IsAvailable() )
1037 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1040 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1045 void TextInput::OnKeyInputFocusLost()
1047 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1051 // If key input focus is lost, it removes the
1052 // underline from the last pre-edit text.
1053 RemovePreEditStyle();
1054 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1055 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1059 ImfManager imfManager = ImfManager::Get();
1062 // The text editing is finished. Therefore the imf manager don't have restore activation.
1063 imfManager.SetRestoreAferFocusLost( false );
1065 // Notify that the text editing finish.
1066 imfManager.Deactivate();
1068 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1070 // Disconnect signal used the text input.
1071 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1073 Toolkit::TextInput handle( GetOwner() );
1074 mInputFinishedSignalV2.Emit( handle );
1075 mEditModeActive = false;
1076 mPreEditFlag = false;
1078 SetCursorVisibility( false );
1079 StopCursorBlinkTimer();
1081 ShowGrabHandleAndSetVisibility( false );
1084 // No longer in edit mode so do not want to receive string from clipboard
1085 if( Adaptor::IsAvailable() )
1087 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1090 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1092 Clipboard clipboard = Clipboard::Get();
1096 clipboard.HideClipboard();
1101 void TextInput::OnControlStageConnection()
1103 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1105 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1107 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1111 void TextInput::CreateActiveLayer()
1113 Actor self = Self();
1114 mActiveLayer = Layer::New();
1116 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1117 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1118 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1120 self.Add( mActiveLayer );
1121 mActiveLayer.RaiseToTop();
1124 void TextInput::OnInitialize()
1126 CreateTextViewActor();
1130 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1131 // different positions depending on language)
1132 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1133 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1134 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1136 Actor self = Self();
1137 self.Add( mCursor );
1138 self.Add( mCursorRTL );
1140 mCursorVisibility = false;
1142 CreateActiveLayer(); // todo move this so layer only created when needed.
1144 // Assign names to image actors
1145 mCursor.SetName("mainCursor");
1146 mCursorRTL.SetName("rtlCursor");
1149 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1151 mDisplayedTextView.SetSize( targetSize );
1152 GetTextLayoutInfo();
1153 mActiveLayer.SetSize(targetSize);
1156 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1158 Relayout( mDisplayedTextView, size, container );
1159 GetTextLayoutInfo();
1164 Vector3 TextInput::GetNaturalSize()
1166 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1168 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1170 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1171 naturalSize.height = mLineHeight;
1177 float TextInput::GetHeightForWidth( float width )
1179 float height = mDisplayedTextView.GetHeightForWidth( width );
1181 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1183 // If the height is zero, it means there is no text. Let's return the cursor height.
1184 height = mLineHeight;
1190 /*end of Virtual methods from parent*/
1192 // Private Internal methods
1194 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1196 switch (gesture.state)
1198 case Gesture::Started:
1199 // fall through so code not duplicated
1200 case Gesture::Continuing:
1202 if (actor == mGrabArea)
1204 SetCursorVisibility( true );
1205 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1206 MoveGrabHandle( gesture.displacement );
1207 HidePopup(); // Do not show popup whilst handle is moving
1209 else if (actor == mHandleOneGrabArea)
1211 // the displacement in PanGesture is affected by the actor's rotation.
1212 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1213 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1215 MoveSelectionHandle( HandleOne, gesture.displacement );
1217 mState = StateDraggingHandle;
1220 else if (actor == mHandleTwoGrabArea)
1222 // the displacement in PanGesture is affected by the actor's rotation.
1223 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1224 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1226 MoveSelectionHandle( HandleTwo, gesture.displacement );
1228 mState = StateDraggingHandle;
1234 case Gesture::Finished:
1236 // Revert back to non-pressed selection handle images
1237 if (actor == mGrabArea)
1239 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1240 SetCursorVisibility( true );
1241 SetUpPopUpSelection();
1244 if (actor == mHandleOneGrabArea)
1246 // the displacement in PanGesture is affected by the actor's rotation.
1247 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1248 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1250 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1252 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1254 ShowPopupCutCopyPaste();
1256 if (actor == mHandleTwoGrabArea)
1258 // the displacement in PanGesture is affected by the actor's rotation.
1259 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1260 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1262 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1264 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1266 ShowPopupCutCopyPaste();
1275 // Stop the flashing animation so easy to see when moved.
1276 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1278 if (touch.GetPoint(0).state == TouchPoint::Down)
1280 SetCursorVisibility( true );
1281 StopCursorBlinkTimer();
1283 else if (touch.GetPoint(0).state == TouchPoint::Up)
1285 SetCursorVisibility( true );
1286 StartCursorBlinkTimer();
1291 // selection handle one
1292 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1294 if (touch.GetPoint(0).state == TouchPoint::Down)
1296 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1298 else if (touch.GetPoint(0).state == TouchPoint::Up)
1300 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1305 // selection handle two
1306 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1308 if (touch.GetPoint(0).state == TouchPoint::Down)
1310 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1312 else if (touch.GetPoint(0).state == TouchPoint::Up)
1314 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1319 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1321 // If text exists then select nearest word.
1322 if ( !mStyledText.empty())
1326 ShowGrabHandleAndSetVisibility( false );
1331 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1332 // converts the pre-edit word being displayed to a committed word.
1333 if ( !mUnderlinedPriorToPreEdit )
1336 style.SetUnderline( false );
1337 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1339 mPreEditFlag = false;
1340 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1341 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1342 PreEditReset( false );
1344 mCursorPosition = 0;
1346 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1347 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1349 ImfManager imfManager = ImfManager::Get();
1352 imfManager.SetCursorPosition ( mCursorPosition );
1353 imfManager.NotifyCursorPosition();
1356 std::size_t start = 0;
1357 std::size_t end = 0;
1358 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1360 SelectText( start, end );
1362 // if no text but clipboard has content then show paste option
1363 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1365 ShowPopupCutCopyPaste();
1368 // If no text and clipboard empty then do nothing
1371 // TODO: Change the function name to be more general.
1372 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1374 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1375 , (mEditOnTouch)?"true":"false"
1376 , (mEditModeActive)?"true":"false");
1378 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1383 if( mGrabArea == actor )
1385 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1387 SetUpPopUpSelection();
1397 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1399 // Initially don't create the grab handle.
1400 bool createGrabHandle = false;
1402 if ( !mEditModeActive )
1404 // update line height before calculate the actual position.
1407 // Only start edit mode if TextInput configured to edit on touch
1410 // Set the initial cursor position in the tap point.
1411 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1413 // Create the grab handle.
1414 // TODO Make this a re-usable function.
1415 if ( IsGrabHandleEnabled() )
1417 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1421 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1422 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1423 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1424 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1428 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1429 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1430 // otherwise the Grab handle will be shown when selecting.
1437 // Show the keyboard if it was hidden.
1438 if (!VirtualKeyboard::IsVisible())
1440 VirtualKeyboard::Show();
1443 // Reset keyboard as tap event has occurred.
1444 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1445 PreEditReset( true );
1447 GetTextLayoutInfo();
1449 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1451 // As already in edit mode, reposition cursor near tap and show grab handle for cursor, if grab handle not enabled then magnifier will be used instead.
1453 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1455 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1457 // Notify keyboard so it can 're-capture' word for predictive text.
1458 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1459 ImfManager imfManager = ImfManager::Get();
1462 imfManager.SetCursorPosition ( mCursorPosition );
1463 imfManager.NotifyCursorPosition();
1465 const TextStyle oldInputStyle( mInputStyle );
1467 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1471 // Create the grab handle.
1472 // Grab handle is created later.
1473 createGrabHandle = true;
1475 if( oldInputStyle != mInputStyle )
1477 // Updates the line height accordingly with the input style.
1480 EmitStyleChangedSignal();
1485 if ( createGrabHandle && IsGrabHandleEnabled() )
1487 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1491 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1492 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1493 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1494 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1499 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1501 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1503 if(longPress.state == Dali::Gesture::Started)
1505 // Start edit mode on long press
1506 if ( !mEditModeActive )
1511 // If text exists then select nearest word.
1512 if ( !mStyledText.empty())
1516 ShowGrabHandleAndSetVisibility( false );
1521 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1522 // converts the pre-edit word being displayed to a committed word.
1523 if ( !mUnderlinedPriorToPreEdit )
1526 style.SetUnderline( false );
1527 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1529 mPreEditFlag = false;
1530 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1531 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1532 PreEditReset( false );
1534 mCursorPosition = 0;
1536 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1537 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1539 ImfManager imfManager = ImfManager::Get();
1542 imfManager.SetCursorPosition ( mCursorPosition );
1543 imfManager.NotifyCursorPosition();
1545 std::size_t start = 0;
1546 std::size_t end = 0;
1547 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1549 SelectText( start, end );
1552 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1553 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1555 ShowPopupCutCopyPaste();
1560 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1562 const Text clipboardText( notifier.GetContent() );
1563 PasteText( clipboardText );
1565 SetCursorVisibility( true );
1566 StartCursorBlinkTimer();
1568 ShowGrabHandleAndSetVisibility( false );
1574 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1576 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1578 const std::string& name = button.GetName();
1580 if(name == OPTION_SELECT_WORD)
1582 std::size_t start = 0;
1583 std::size_t end = 0;
1584 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1586 SelectText( start, end );
1588 else if(name == OPTION_SELECT_ALL)
1590 SetCursorVisibility(false);
1591 StopCursorBlinkTimer();
1593 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1594 std::size_t start = 0;
1596 SelectText( start, end );
1598 else if(name == OPTION_CUT)
1600 bool ret = CopySelectedTextToClipboard();
1604 DeleteHighlightedText( true );
1608 SetCursorVisibility( true );
1609 StartCursorBlinkTimer();
1613 else if(name == OPTION_COPY)
1615 CopySelectedTextToClipboard();
1619 SetCursorVisibility( true );
1620 StartCursorBlinkTimer();
1624 else if(name == OPTION_PASTE)
1626 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1628 PasteText(retrievedString);
1630 SetCursorVisibility( true );
1631 StartCursorBlinkTimer();
1633 ShowGrabHandleAndSetVisibility( false );
1637 else if(name == OPTION_CLIPBOARD)
1639 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1640 // Hence pass the false parameter for signalFinished.
1641 HidePopup( true, false );
1642 mClipboard.ShowClipboard();
1648 bool TextInput::OnCursorBlinkTimerTick()
1651 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1652 if ( mCursorRTLEnabled )
1654 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1656 mCursorBlinkStatus = !mCursorBlinkStatus;
1661 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1663 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1665 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1666 if(mHighlightMeshActor && mState == StateEdit)
1668 ShowPopupCutCopyPaste();
1672 //FIXME this routine needs to be re-written as it contains too many branches.
1673 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1675 std::string keyName = event.keyPressedName;
1676 std::string keyString = event.keyPressed;
1678 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1680 // Do not consume "Tab" and "Escape" keys.
1681 if(keyName == "Tab" || keyName == "Escape")
1683 // Escape key to end the edit mode
1689 HidePopup(); // If Pop-up shown then hides it as editing text.
1691 // Update Flag, indicates whether to update the text-input contents or not.
1692 // Any key stroke that results in a visual change of the text-input should
1693 // set this flag to true.
1696 // Whether to scroll text to cursor position.
1697 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1698 bool scroll = false;
1700 if (keyName == "Return")
1702 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1704 bool preEditFlagPreviouslySet( mPreEditFlag );
1706 if (mHighlightMeshActor)
1708 // replaces highlighted text with new line
1709 DeleteHighlightedText( false );
1711 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1713 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1714 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1717 mCommitByKeyInput = true;
1720 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1721 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1723 mPreEditFlag = true;
1724 mIgnoreCommitFlag = false;
1734 else if ( keyName == "space" )
1736 if ( mHighlightMeshActor )
1738 // Some text is selected so erase it before adding space.
1739 DeleteHighlightedText( true );
1743 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1745 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1746 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1749 mCommitByKeyInput = true;
1754 else if (keyName == "BackSpace")
1756 if ( mHighlightMeshActor )
1758 // Some text is selected so erase it
1759 DeleteHighlightedText( true );
1764 if ( mCursorPosition > 0 )
1766 DeleteCharacter( mCursorPosition );
1771 else if (keyName == "Right")
1776 else if (keyName == "Left")
1778 AdvanceCursor(true);
1781 else // event is a character
1783 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1784 if ( !keyString.empty() )
1786 if ( mHighlightMeshActor )
1788 // replaces highlighted text with new character
1789 DeleteHighlightedText( false );
1793 // Received key String
1794 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1800 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1801 // as this is a costly operation.
1807 if(update || scroll)
1809 if( IsScrollEnabled() )
1811 // Calculates the new cursor position (in actor coordinates)
1812 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1814 ScrollTextViewToMakeCursorVisible( cursorPosition );
1821 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1823 std::string keyName = event.keyPressedName;
1824 std::string keyString = event.keyPressed;
1826 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1828 // The selected text become deselected when the key code is DALI_KEY_BACK.
1829 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1838 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1840 // Updates the stored scroll position.
1841 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1843 const Vector3& controlSize = GetControlSize();
1844 Size cursorSize( CURSOR_THICKNESS, 0.f );
1846 // Updates the cursor and grab handle position and visibility.
1847 if( mGrabHandle || mCursor )
1849 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1850 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1852 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1854 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1858 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1859 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1864 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1865 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1869 // Updates the selection handles and highlighted text position and visibility.
1870 if( mSelectionHandleOne && mSelectionHandleTwo )
1872 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1873 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1874 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1875 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1876 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1877 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1879 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1880 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1882 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1883 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1884 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1885 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1887 if( mHighlightMeshActor )
1889 mHighlightMeshActor.SetVisible( true );
1895 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1897 // Scroll the text to make the cursor visible.
1898 const Size cursorSize( CURSOR_THICKNESS,
1899 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1901 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1903 const Vector3& controlSize = GetControlSize();
1905 // Calculates the new scroll position.
1906 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1907 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1909 scrollOffset.x += cursorPosition.x;
1912 if( cursorPosition.y - cursorSize.height < 0.f )
1914 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1916 else if( cursorPosition.y > controlSize.height )
1918 scrollOffset.y += cursorPosition.y;
1921 // Sets the new scroll position.
1922 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1923 SetScrollPosition( scrollOffset );
1926 void TextInput::StartScrollTimer()
1930 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1931 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1934 if( !mScrollTimer.IsRunning() )
1936 mScrollTimer.Start();
1940 void TextInput::StopScrollTimer()
1944 mScrollTimer.Stop();
1948 bool TextInput::OnScrollTimerTick()
1950 // TODO: need to set the new style accordingly the new handle position.
1952 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1954 // nothing to do if all handles are invisible or doesn't exist.
1960 // Choose between the grab handle or the selection handles.
1961 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1962 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1963 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1965 std::size_t newCursorPosition = 0;
1966 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1968 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1969 // the new selection handle's position needs to be different of the other one.
1970 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1971 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1972 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1974 if( differentSelectionHandles )
1976 handlePosition = newCursorPosition;
1978 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1980 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1982 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1983 scrollPosition += scrollDelta;
1984 SetScrollPosition( scrollPosition );
1986 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1991 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1994 actualHandlePosition.x += mScrollDisplacement.x;
1995 actualHandlePosition.y += mScrollDisplacement.y;
2000 // Public Internal Methods (public for testing purpose)
2002 void TextInput::SetUpTouchEvents()
2004 if ( !mTapDetector )
2006 mTapDetector = TapGestureDetector::New();
2007 // Attach the actors and connect the signal
2008 mTapDetector.Attach(Self());
2010 // As contains children which may register for tap the default control detector is not used.
2011 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2014 if ( !mDoubleTapDetector )
2016 mDoubleTapDetector = TapGestureDetector::New();
2017 mDoubleTapDetector.SetTapsRequired( 2 );
2018 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2020 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2021 // so that we do not, unnecessarily, have a double tap request all the time
2024 if ( !mPanGestureDetector )
2026 mPanGestureDetector = PanGestureDetector::New();
2027 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2030 if ( !mLongPressDetector )
2032 mLongPressDetector = LongPressGestureDetector::New();
2033 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2034 mLongPressDetector.Attach(Self());
2038 void TextInput::CreateTextViewActor()
2040 mDisplayedTextView = Toolkit::TextView::New();
2041 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2042 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2043 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2044 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2045 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2046 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2047 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2048 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2049 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2050 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2052 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2054 Self().Add( mDisplayedTextView );
2057 // Start a timer to initiate, used by the cursor to blink.
2058 void TextInput::StartCursorBlinkTimer()
2060 if ( !mCursorBlinkTimer )
2062 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2063 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2066 if ( !mCursorBlinkTimer.IsRunning() )
2068 mCursorBlinkTimer.Start();
2072 // Start a timer to initiate, used by the cursor to blink.
2073 void TextInput::StopCursorBlinkTimer()
2075 if ( mCursorBlinkTimer )
2077 mCursorBlinkTimer.Stop();
2081 void TextInput::StartEditMode()
2083 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2085 if(!mEditModeActive)
2090 if ( mDoubleTapDetector )
2092 mDoubleTapDetector.Attach( Self() );
2096 void TextInput::EndEditMode()
2098 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2100 ClearKeyInputFocus();
2102 if ( mDoubleTapDetector )
2104 mDoubleTapDetector.Detach( Self() );
2108 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2110 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2112 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2114 style.SetUnderline( true );
2115 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2119 void TextInput::RemovePreEditStyle()
2121 if ( !mUnderlinedPriorToPreEdit )
2124 style.SetUnderline( false );
2125 SetActiveStyle( style, TextStyle::UNDERLINE );
2129 // IMF related methods
2132 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2134 bool update( false );
2135 bool preeditResetRequired ( false );
2137 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2139 HidePopup(); // If Pop-up shown then hides it as editing text.
2142 switch ( imfEvent.eventName )
2144 case ImfManager::PREEDIT:
2146 mIgnoreFirstCommitFlag = false;
2148 // 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
2149 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2151 // replaces highlighted text with new character
2152 DeleteHighlightedText( false );
2155 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2157 if( IsScrollEnabled() )
2159 // Calculates the new cursor position (in actor coordinates)
2160 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2161 ScrollTextViewToMakeCursorVisible( cursorPosition );
2168 case ImfManager::COMMIT:
2170 if( mIgnoreFirstCommitFlag )
2172 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2173 mIgnoreFirstCommitFlag = false;
2177 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2179 // 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
2180 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2182 // replaces highlighted text with new character
2183 DeleteHighlightedText( false );
2186 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2187 // not needed, one such scenario is when the pre-edit word is too long to fit.
2188 if ( !mIgnoreCommitFlag )
2190 update = CommitReceived( imfEvent.predictiveString );
2194 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2200 if( IsScrollEnabled() )
2202 // Calculates the new cursor position (in actor coordinates)
2203 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2205 ScrollTextViewToMakeCursorVisible( cursorPosition );
2210 case ImfManager::DELETESURROUNDING:
2212 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2213 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2215 mPreEditFlag = false;
2217 std::size_t toDelete = 0;
2218 std::size_t numberOfCharacters = 0;
2220 if( mHighlightMeshActor )
2222 // delete highlighted text.
2223 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2224 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2228 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2230 toDelete = mCursorPosition + imfEvent.cursorOffset;
2232 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2234 numberOfCharacters = mStyledText.size() - toDelete;
2238 numberOfCharacters = imfEvent.numberOfChars;
2241 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2242 DeleteRange( toDelete, numberOfCharacters );
2244 mCursorPosition = toDelete;
2245 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2249 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2252 case ImfManager::GETSURROUNDING:
2254 // 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
2255 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2256 if (! ( mHighlightMeshActor || mSelectingText ) )
2258 std::string text( GetText() );
2259 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2261 imfManager.SetCursorPosition( mCursorPosition );
2262 imfManager.SetSurroundingText( text );
2265 if( 0 != mNumberOfSurroundingCharactersDeleted )
2267 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2268 mNumberOfSurroundingCharactersDeleted = 0;
2270 if( mStyledText.empty() )
2272 // Styled text is empty, so set the placeholder text.
2273 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2274 mPlaceHolderSet = true;
2279 case ImfManager::VOID:
2281 DALI_ASSERT_DEBUG( false );
2285 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2287 return callbackData;
2290 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2292 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2294 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2295 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2297 bool preeditResetRequest ( false );
2299 if( mPreEditFlag ) // Already in pre-edit state.
2301 if( mStyledText.size() >= mMaxStringLength )
2303 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2304 // Cannot fit these characters into field, clear pre-edit.
2305 if ( !mUnderlinedPriorToPreEdit )
2308 style.SetUnderline( false );
2309 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2311 mIgnoreCommitFlag = true;
2312 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2313 mPreEditFlag = false;
2314 EmitMaxInputCharactersReachedSignal();
2318 // delete existing pre-edit string
2319 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2321 // Store new pre-edit string
2322 mPreEditString.SetText( keyString );
2324 if ( keyString.empty() )
2326 mPreEditFlag = false;
2327 mCursorPosition = mPreEditStartPosition;
2329 if( mStyledText.empty() )
2331 // Styled text is empty, so set the placeholder text.
2332 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2333 mPlaceHolderSet = true;
2337 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2339 GetTextLayoutInfo();
2344 // Insert new pre-edit string. InsertAt updates the size and position table.
2345 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2346 // 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.
2347 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2348 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2349 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2352 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2356 else // mPreEditFlag not set
2358 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2360 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2361 // new pre-edit so move into pre-edit state by setting flag
2362 mPreEditFlag = true;
2363 mPreEditString.SetText( keyString ); // store new pre-edit string
2364 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2365 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2366 // 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.
2367 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2368 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2369 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2370 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2376 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2380 return preeditResetRequest;
2383 bool TextInput::CommitReceived(const std::string& keyString )
2385 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2386 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2388 bool update( false );
2390 RemovePreEditStyle();
2392 const std::size_t styledTextSize( mStyledText.size() );
2393 if( styledTextSize >= mMaxStringLength )
2395 // Cannot fit these characters into field, clear pre-edit.
2398 mIgnoreCommitFlag = true;
2399 mPreEditFlag = false;
2401 EmitMaxInputCharactersReachedSignal();
2407 // delete existing pre-edit string
2408 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2409 mPreEditFlag = false;
2411 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2412 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2414 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2416 // No need to update cursor position as Cursor location given by touch.
2417 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2418 mPreserveCursorPosition = false;
2422 // Cursor not set by touch so needs to be re-positioned to input more text
2423 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2425 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2426 if ( mCommitByKeyInput )
2428 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2429 mCommitByKeyInput = false;
2435 if ( mSelectTextOnCommit )
2437 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2442 else // mPreEditFlag not set
2444 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2446 if( mStyledText.empty() && mPlaceHolderSet )
2448 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2449 mDisplayedTextView.SetText( "" );
2450 mNumberOfSurroundingCharactersDeleted = 0;
2451 mPlaceHolderSet = false;
2453 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2455 mNumberOfSurroundingCharactersDeleted = 0;
2460 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2465 mSelectTextOnCommit = false;
2467 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2468 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2473 // End of IMF related methods
2475 std::size_t TextInput::DeletePreEdit()
2477 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2479 DALI_ASSERT_DEBUG( mPreEditFlag );
2481 const std::size_t preEditStringLength = mPreEditString.GetLength();
2482 const std::size_t styledTextSize = mStyledText.size();
2484 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2486 // Prevents erase items outside mStyledText bounds.
2487 if( mPreEditStartPosition > styledTextSize )
2489 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2490 mPreEditStartPosition = styledTextSize;
2493 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2495 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2496 endPosition = styledTextSize;
2499 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2501 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2502 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2504 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2506 return preEditStringLength;
2509 void TextInput::PreEditReset( bool preserveCursorPosition )
2511 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2512 preserveCursorPosition, mCursorPosition);
2514 // 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.
2515 mPreserveCursorPosition = preserveCursorPosition;
2517 // Reset incase we are in a pre-edit state.
2518 ImfManager imfManager = ImfManager::Get();
2521 imfManager.Reset(); // Will trigger a commit message
2525 void TextInput::CursorUpdate()
2529 ImfManager imfManager = ImfManager::Get();
2532 std::string text( GetText() );
2533 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2534 imfManager.SetCursorPosition ( mCursorPosition );
2535 imfManager.NotifyCursorPosition();
2539 /* Delete highlighted characters redisplay*/
2540 void TextInput::DeleteHighlightedText( bool inheritStyle )
2542 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2544 if(mHighlightMeshActor)
2546 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2548 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2549 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2551 // Get the styled text of the characters to be deleted as it may be needed if
2552 // the "exceed the text-input's boundaries" option is disabled.
2553 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2555 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2557 mStyledText.erase( start, end ); // erase range of characters
2559 // Remove text from TextView.
2561 if( mStyledText.empty() )
2563 // Styled text is empty, so set the placeholder text.
2564 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2565 mPlaceHolderSet = true;
2569 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2571 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2573 // It may happen than after removing a white space or a new line character,
2574 // two words merge, this new word could be big enough to not fit in its
2575 // current line, so moved to the next one, and make some part of the text to
2576 // exceed the text-input's boundary.
2577 if( !mExceedEnabled )
2579 // Get the new text layout after removing some characters.
2580 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2582 // Get text-input's size.
2583 const Vector3& size = GetControlSize();
2585 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2586 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2588 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2590 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2591 styledCharactersToDelete.begin(),
2592 styledCharactersToDelete.end() );
2596 GetTextLayoutInfo();
2602 const TextStyle oldInputStyle( mInputStyle );
2604 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2606 if( oldInputStyle != mInputStyle )
2608 // Updates the line height accordingly with the input style.
2611 EmitStyleChangedSignal();
2617 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2619 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2620 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2622 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2625 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2627 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2628 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2630 mStyledText.erase(itStart, itEnd);
2632 // update the selection handles if they are visible.
2633 if( mHighlightMeshActor )
2635 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2636 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2638 if( minHandle >= start + ncharacters )
2640 minHandle -= ncharacters;
2642 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2647 if( maxHandle >= start + ncharacters )
2649 maxHandle -= ncharacters;
2651 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2657 // 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.
2660 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2662 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2663 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2664 // Mean we do not re-draw the text more than we have too.
2667 /* Delete character at current cursor position and redisplay*/
2668 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2670 // Ensure positionToDelete is not out of bounds.
2671 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2672 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2673 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2675 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2678 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2680 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2682 // Get the styled text of the character to be deleted as it may be needed if
2683 // the "exceed the text-input's boundaries" option is disabled.
2684 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2686 mStyledText.erase(it); // erase the character left of positionToDelete
2688 if( mStyledText.empty() )
2690 // Styled text is empty, so set the placeholder text.
2691 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2692 mPlaceHolderSet = true;
2696 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2698 const Character characterToDelete = styledCharacterToDelete.mText[0];
2700 // It may happen than after removing a white space or a new line character,
2701 // two words merge, this new word could be big enough to not fit in its
2702 // current line, so moved to the next one, and make some part of the text to
2703 // exceed the text-input's boundary.
2704 if( !mExceedEnabled )
2706 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2708 // Get the new text layout after removing one character.
2709 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2711 // Get text-input's size.
2712 const Vector3& size = GetControlSize();
2714 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2715 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2717 MarkupProcessor::StyledTextArray array;
2718 array.push_back( styledCharacterToDelete );
2719 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2721 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2726 GetTextLayoutInfo();
2728 ShowGrabHandleAndSetVisibility( false );
2730 mCursorPosition = positionToDelete -1;
2732 const TextStyle oldInputStyle( mInputStyle );
2734 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2736 if( oldInputStyle != mInputStyle )
2738 // Updates the line height accordingly with the input style.
2741 EmitStyleChangedSignal();
2746 /*Insert new character into the string and (optionally) redisplay text-input*/
2747 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2749 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2751 // Ensure insertionPosition is not out of bounds.
2752 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2754 bool textExceedsMaximunNumberOfCharacters = false;
2755 bool textExceedsBoundary = false;
2756 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2758 ShowGrabHandleAndSetVisibility( false );
2760 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2764 mIgnoreCommitFlag = true;
2765 mPreEditFlag = false;
2766 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2767 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2770 if( textExceedsMaximunNumberOfCharacters )
2772 EmitMaxInputCharactersReachedSignal();
2775 if( textExceedsBoundary )
2777 EmitInputTextExceedsBoundariesSignal();
2778 PreEditReset( false );
2782 return insertedStringLength;
2785 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2791 cursor = ImageActor::New( cursorImage );
2795 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2798 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2799 cursor.SetNinePatchBorder( border );
2801 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2802 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2803 cursor.SetVisible(false);
2808 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2810 // As cursor is not moving due to grab handle, handle should be hidden.
2811 ShowGrabHandleAndSetVisibility( false );
2813 bool cursorPositionChanged = false;
2816 if ( mCursorPosition >= places )
2818 mCursorPosition = mCursorPosition - places;
2819 cursorPositionChanged = true;
2824 if ((mCursorPosition + places) <= mStyledText.size())
2826 mCursorPosition = mCursorPosition + places;
2827 cursorPositionChanged = true;
2831 if( cursorPositionChanged )
2833 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2835 const TextStyle oldInputStyle( mInputStyle );
2836 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2840 if( oldInputStyle != mInputStyle )
2842 // Updates the line height accordingly with the input style.
2845 EmitStyleChangedSignal();
2848 ImfManager imfManager = ImfManager::Get();
2851 imfManager.SetCursorPosition ( mCursorPosition );
2852 imfManager.NotifyCursorPosition();
2857 void TextInput::DrawCursor(const std::size_t nthChar)
2859 // Get height of cursor and set its size
2860 Size size( CURSOR_THICKNESS, 0.0f );
2861 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2863 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2867 // Measure Font so know how big text will be if no initial text to measure.
2868 size.height = mLineHeight;
2871 mCursor.SetSize(size);
2873 // If the character is italic then the cursor also tilts.
2874 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2876 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2878 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2880 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2881 bool altPositionValid; // Alternate cursor validity flag.
2882 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2883 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2885 SetAltCursorEnabled( altPositionValid );
2887 if(!altPositionValid)
2889 mCursor.SetPosition( position + UI_OFFSET );
2893 size.height *= 0.5f;
2894 mCursor.SetSize(size);
2895 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2897 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2898 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2899 size.height = rowSize.height * 0.5f;
2900 mCursorRTL.SetSize(size);
2901 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2904 if( IsScrollEnabled() )
2906 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2907 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2912 void TextInput::SetAltCursorEnabled( bool enabled )
2914 mCursorRTLEnabled = enabled;
2915 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2918 void TextInput::SetCursorVisibility( bool visible )
2920 mCursorVisibility = visible;
2921 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2922 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2925 void TextInput::CreateGrabHandle( Dali::Image image )
2931 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2935 mGrabHandleImage = image;
2938 mGrabHandle = ImageActor::New(mGrabHandleImage);
2939 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2940 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2942 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2944 ShowGrabHandleAndSetVisibility( false );
2946 CreateGrabArea( mGrabHandle );
2948 mActiveLayer.Add(mGrabHandle);
2952 void TextInput::CreateGrabArea( Actor& parent )
2954 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2955 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2956 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2957 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2958 mTapDetector.Attach( mGrabArea );
2959 mPanGestureDetector.Attach( mGrabArea );
2961 parent.Add(mGrabArea);
2964 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2966 Vector3 actualHandlePosition;
2970 mActualGrabHandlePosition.x += displacement.x;
2971 mActualGrabHandlePosition.y += displacement.y;
2973 // Grab handle should jump to the nearest character and take cursor with it
2974 std::size_t newCursorPosition = 0;
2975 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2977 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2979 bool handleVisible = true;
2981 if( IsScrollEnabled() )
2983 const Vector3 controlSize = GetControlSize();
2984 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
2985 // Scrolls the text if the handle is not in a visible position
2986 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2993 mCurrentHandlePosition = actualHandlePosition;
2994 mScrollDisplacement = Vector2::ZERO;
2998 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3000 mScrollDisplacement.x = -SCROLL_SPEED;
3002 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3004 mScrollDisplacement.x = SCROLL_SPEED;
3006 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3008 mScrollDisplacement.y = -SCROLL_SPEED;
3010 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3012 mScrollDisplacement.y = SCROLL_SPEED;
3018 if( handleVisible && // Only redraw cursor and do updates if position changed
3019 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3021 mCursorPosition = newCursorPosition;
3023 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3025 const TextStyle oldInputStyle( mInputStyle );
3027 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3029 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3031 if( oldInputStyle != mInputStyle )
3033 // Updates the line height accordingly with the input style.
3036 EmitStyleChangedSignal();
3041 return actualHandlePosition;
3044 void TextInput::ShowGrabHandle( bool visible )
3046 if ( IsGrabHandleEnabled() )
3050 mGrabHandle.SetVisible( mGrabHandleVisibility );
3052 StartMonitoringStageForTouch();
3056 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3058 mGrabHandleVisibility = visible;
3059 ShowGrabHandle( visible );
3062 // Callbacks connected to be Property notifications for Boundary checking.
3064 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3066 mIsSelectionHandleOneFlipped = true;
3067 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3068 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3071 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3073 mIsSelectionHandleOneFlipped = false;
3074 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3075 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3078 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3080 mIsSelectionHandleTwoFlipped = true;
3081 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3082 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3085 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3087 mIsSelectionHandleTwoFlipped = false;
3088 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3089 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3092 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3093 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3095 mSelectionHandleOne.SetOpacity(0.0f);
3098 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3100 mSelectionHandleOne.SetOpacity(1.0f);
3103 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3105 mSelectionHandleTwo.SetOpacity(0.0f);
3108 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3110 mSelectionHandleTwo.SetOpacity(1.0f);
3113 // End of Callbacks connected to be Property notifications for Boundary checking.
3115 void TextInput::SetUpHandlePropertyNotifications()
3117 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3119 Vector3 handlesize = GetSelectionHandleSize();
3121 // Exceeding horizontal boundary
3122 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3123 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3125 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3126 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3128 // Within horizontal boundary
3129 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3130 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3132 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3133 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3135 // Exceeding vertical boundary
3136 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3137 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3138 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3139 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3141 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3142 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3143 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3144 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3146 // Within vertical boundary
3147 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3148 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3149 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3150 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3152 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3153 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3154 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3155 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3158 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3160 mSelectionHandleOnePosition = start;
3161 mSelectionHandleTwoPosition = end;
3163 if ( !mSelectionHandleOne )
3165 // create normal and pressed images
3166 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3167 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3169 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3170 mSelectionHandleOne.SetName("SelectionHandleOne");
3171 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3172 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3173 mIsSelectionHandleOneFlipped = false;
3174 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3176 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3177 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3179 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3180 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3182 mTapDetector.Attach( mHandleOneGrabArea );
3183 mPanGestureDetector.Attach( mHandleOneGrabArea );
3185 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3187 mSelectionHandleOne.Add( mHandleOneGrabArea );
3188 mActiveLayer.Add( mSelectionHandleOne );
3191 if ( !mSelectionHandleTwo )
3193 // create normal and pressed images
3194 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3195 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3197 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3198 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3199 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3200 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3201 mIsSelectionHandleTwoFlipped = false;
3202 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3204 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3205 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3206 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3207 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3209 mTapDetector.Attach( mHandleTwoGrabArea );
3210 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3212 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3214 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3216 mActiveLayer.Add( mSelectionHandleTwo );
3219 SetUpHandlePropertyNotifications();
3221 // update table as text may have changed.
3222 GetTextLayoutInfo();
3224 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3225 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3227 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3228 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3230 // Calculates and set the visibility if the scroll mode is enabled.
3231 bool isSelectionHandleOneVisible = true;
3232 bool isSelectionHandleTwoVisible = true;
3233 if( IsScrollEnabled() )
3235 const Vector3& controlSize( GetControlSize() );
3236 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3237 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3238 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3239 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3242 CreateHighlight(); // function will only create highlight if not already created.
3245 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3247 Vector3 actualHandlePosition;
3249 if ( mSelectionHandleOne && mSelectionHandleTwo )
3251 const Vector3& controlSize = GetControlSize();
3253 Size cursorSize( CURSOR_THICKNESS, 0.f );
3255 // Get a reference of the wanted selection handle (handle one or two).
3256 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3258 // Get a reference for the current position of the handle and a copy of its pair
3259 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3260 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3262 // Get a handle of the selection handle actor
3263 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3265 // Selection handles should jump to the nearest character
3266 std::size_t newHandlePosition = 0;
3267 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3269 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3271 bool handleVisible = true;
3273 if( IsScrollEnabled() )
3275 mCurrentSelectionId = handleId;
3277 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3278 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3279 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3286 mCurrentSelectionHandlePosition = actualHandlePosition;
3287 mScrollDisplacement = Vector2::ZERO;
3291 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3293 mScrollDisplacement.x = -SCROLL_SPEED;
3295 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3297 mScrollDisplacement.x = SCROLL_SPEED;
3299 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3301 mScrollDisplacement.y = -SCROLL_SPEED;
3303 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3305 mScrollDisplacement.y = SCROLL_SPEED;
3311 if ( handleVisible && // Ensure the handle is visible.
3312 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3313 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3315 currentSelectionHandlePosition = newHandlePosition;
3317 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3318 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3322 if ( handleId == HandleOne )
3324 const TextStyle oldInputStyle( mInputStyle );
3326 // Set Active Style to that of first character in selection
3327 if( mSelectionHandleOnePosition < mStyledText.size() )
3329 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3332 if( oldInputStyle != mInputStyle )
3334 // Updates the line height accordingly with the input style.
3337 EmitStyleChangedSignal();
3343 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3346 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3349 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3350 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3352 if ( selectionHandleActor )
3354 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3355 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3356 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3358 if( IsScrollEnabled() )
3360 const Size cursorSize( CURSOR_THICKNESS,
3361 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3362 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3364 GetControlSize() ) );
3369 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3371 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3372 // For now the visual position of end of logical string will be end of visual string.
3373 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3375 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3378 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3380 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3381 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3382 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3383 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3385 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3387 // Deselect text prior to startSelectionIt
3388 for(;it!=startSelectionIt;++it)
3390 selectedVisualText[*it] = false;
3393 // Select text from startSelectionIt -> endSelectionIt
3394 for(;it!=endSelectionIt;++it)
3396 selectedVisualText[*it] = true;
3399 // Deselect text after endSelection
3402 selectedVisualText[*it] = false;
3405 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3408 // Calculate the dimensions of the quads they will make the highlight mesh
3409 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3411 // At the moment there is no public API to modify the block alignment option.
3412 const bool blockAlignEnabled = true;
3414 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3416 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3418 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3419 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3421 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3422 std::vector<bool> selectedVisualText;
3423 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3424 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3425 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3427 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3428 float rowLeft = 0.0f;
3429 float rowRight = 0.0f;
3430 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3431 float maxRowLeft = std::numeric_limits<float>::max();
3432 float maxRowRight = 0.0f;
3434 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3436 // Scan through entire text.
3439 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3441 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3442 bool charSelected( false );
3443 if( selectedIt != selectedEndIt )
3445 charSelected = *selectedIt++;
3448 if(selectionState == SelectionNone)
3452 selectionState = SelectionStarted;
3453 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3454 rowRight = rowLeft + charInfo.mSize.width;
3457 else if(selectionState == SelectionStarted)
3459 // break selection on:
3460 // 1. new line causing selection break. (\n or wordwrap)
3461 // 2. character not selected.
3462 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3465 // finished selection.
3466 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3467 // that it resides on. That way this enumeration is not necessary.
3469 if(lastIt->mIsNewLineChar)
3471 // 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.
3472 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3474 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3475 maxRowLeft = std::min(maxRowLeft, min.x);
3476 maxRowRight = std::max(maxRowRight, max.x);
3477 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3478 float rowTop = rowBottom - rowSize.height;
3480 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3481 if(charSelected && blockAlignEnabled)
3483 rowRight = std::numeric_limits<float>::max();
3485 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3487 selectionState = SelectionNone;
3489 // Still selected? start a new selection
3492 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3493 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3494 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3495 selectionState = SelectionStarted;
3500 // build up highlight(s) with this selection data.
3501 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3502 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3509 // If reached end, and still on selection, then close selection.
3512 if(selectionState == SelectionStarted)
3514 // finished selection.
3516 if(lastIt->mIsNewLineChar)
3518 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3520 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3521 maxRowLeft = std::min(maxRowLeft, min.x);
3522 maxRowRight = std::max(maxRowRight, max.x);
3523 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3524 float rowTop = rowBottom - rowSize.height;
3525 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3529 // Get the top left and bottom right corners.
3530 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3531 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3532 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3534 // Clamp quads so they appear to clip to borders of the whole text.
3535 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3537 // For block-align align Further Clamp quads to max left and right extents
3538 if(blockAlignEnabled)
3540 // BlockAlign: Will adjust highlight to block:
3542 // H[ello] (top row right = max of all rows right)
3543 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3544 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3545 // [text] (bottom row left = min of all rows left)
3546 // (common in SMS messaging selection)
3548 // As opposed to the default which is tight text highlighting.
3553 // (common in regular text editors/web browser selection)
3555 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3558 // Finally clamp quads again so they don't exceed the boundry of the control.
3559 const Vector3& controlSize = GetControlSize();
3560 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3563 return mNewHighlightInfo;
3566 void TextInput::UpdateHighlight()
3568 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3570 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3572 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3573 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3574 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3575 // [BOTTOM] [ MIDDLE ]
3578 // Each quad is created as 2 triangles.
3579 // Middle is just 1 quad regardless of its size.
3593 if ( mHighlightMeshActor )
3595 // vertex and triangle buffers should always be present if MeshActor is alive.
3596 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3597 MeshData::VertexContainer vertices;
3598 Dali::MeshData::FaceIndices faceIndices;
3600 if( !newHighlightInfo.mQuadList.empty() )
3602 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3603 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3605 // vertex position defaults to (0 0 0)
3606 MeshData::Vertex vertex;
3607 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3610 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3612 // Add each quad geometry (a sub-selection) to the mesh data.
3622 QuadCoordinates& quad = *iter;
3624 vertex.x = quad.min.x;
3625 vertex.y = quad.min.y;
3626 vertices.push_back( vertex );
3629 vertex.x = quad.max.x;
3630 vertex.y = quad.min.y;
3631 vertices.push_back( vertex );
3633 // bottom-left (v+2)
3634 vertex.x = quad.min.x;
3635 vertex.y = quad.max.y;
3636 vertices.push_back( vertex );
3638 // bottom-right (v+3)
3639 vertex.x = quad.max.x;
3640 vertex.y = quad.max.y;
3641 vertices.push_back( vertex );
3643 // triangle A (3, 1, 0)
3644 faceIndices.push_back( v + 3 );
3645 faceIndices.push_back( v + 1 );
3646 faceIndices.push_back( v );
3648 // triangle B (0, 2, 3)
3649 faceIndices.push_back( v );
3650 faceIndices.push_back( v + 2 );
3651 faceIndices.push_back( v + 3 );
3653 mMeshData.SetFaceIndices( faceIndices );
3656 BoneContainer bones(0); // passed empty as bones not required
3657 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3658 mHighlightMesh.UpdateMeshData(mMeshData);
3663 void TextInput::ClearPopup()
3665 mPopUpPanel.Clear();
3668 void TextInput::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
3670 mPopUpPanel.AddOption(name, caption, icon, finalOption);
3673 void TextInput::SetPopupPosition(const Vector3& position)
3675 mPopUpPanel.Self().SetPosition( position );
3678 void TextInput::HidePopup(bool animate, bool signalFinished )
3680 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3682 mPopUpPanel.Hide( animate );
3684 if( animate && signalFinished )
3686 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3691 void TextInput::ShowPopup(bool animate)
3695 if(mHighlightMeshActor && mState == StateEdit)
3699 // When text is selected, show popup above top handle (and text), or below bottom handle.
3700 // topHandle: referring to the top most point of the handle or the top line of selection.
3701 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3703 topHandle = mSelectionHandleOneActualPosition;
3704 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3708 topHandle = mSelectionHandleTwoActualPosition;
3709 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3711 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3712 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3714 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3715 Vector3 bottomHandle;
3716 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3717 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3718 mPopUpPanel.SetAlternativeOffset(Vector2(0.0f, bottomHandle.y - topHandle.y));
3722 // When no text is selected, show popup at world position of grab handle or cursor
3723 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3724 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3725 position.y -= rowSize.height;
3726 // if can't be positioned above, then position below row.
3727 Vector2 alternativePopUpPosition( 0.0f, position.y ); // default if no grab handle
3730 alternativePopUpPosition.y = rowSize.height + ( mGrabHandle.GetCurrentSize().height * DEFAULT_GRAB_HANDLE_RELATIVE_SIZE.y ) ;
3731 // If grab handle enabled then position pop-up below the grab handle.
3733 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3736 // reposition popup above the desired cursor posiiton.
3737 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3738 textViewSize.z = 0.0f;
3739 // World position = world position of ParentOrigin of cursor (i.e. top-left corner of TextView) + cursor position;
3740 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3742 SetPopupPosition( worldPosition );
3745 mPopUpPanel.Show(animate);
3746 StartMonitoringStageForTouch();
3748 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3751 void TextInput::ShowPopupCutCopyPaste()
3754 // Check the selected text is whole text or not.
3755 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3757 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3758 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3761 if ( !mStyledText.empty() )
3763 Image cutIcon = Image::New( DEFAULT_ICON_CUT );
3764 Image copyIcon = Image::New( DEFAULT_ICON_COPY );
3765 AddPopupOption( OPTION_CUT, GET_LOCALE_TEXT("IDS_COM_BODY_CUT"), cutIcon );
3766 AddPopupOption( OPTION_COPY, GET_LOCALE_TEXT("IDS_COM_BODY_COPY"), copyIcon, true );
3769 if(mClipboard.NumberOfItems())
3771 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3772 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3773 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon );
3774 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3777 mPopUpPanel.Hide(false);
3781 void TextInput::SetUpPopUpSelection()
3785 // If no text exists then don't offer to select
3786 if ( !mStyledText.empty() )
3788 Image selectIcon = Image::New( DEFAULT_ICON_SELECT );
3789 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3790 AddPopupOption( OPTION_SELECT_WORD, GET_LOCALE_TEXT("IDS_COM_SK_SELECT"), selectIcon );
3791 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3793 // if clipboard has valid contents then offer paste option
3794 if( mClipboard.NumberOfItems() )
3796 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3797 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3798 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon, true );
3799 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3802 mPopUpPanel.Hide(false);
3805 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3810 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3811 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3812 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3813 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3815 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3817 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3819 float closestYdifference = std::numeric_limits<float>::max();
3820 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3821 std::size_t numberOfMatchedCharacters = 0;
3823 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3824 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3826 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3828 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3829 float baselinePosition = info.mPosition.y - info.mDescender;
3831 if( info.mIsVisible )
3833 // store difference between source y point and the y position of the current character
3834 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3836 if( currentYdifference < closestYdifference )
3838 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3839 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3840 closestYdifference = currentYdifference;
3841 matchedCharacters.clear();
3842 numberOfMatchedCharacters = 0; // reset count
3845 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3846 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3848 // ignore new line character.
3849 if( !info.mIsNewLineChar )
3851 matchedCharacters.push_back( info );
3852 numberOfMatchedCharacters++;
3856 } // End of loop checking each character's y position in the character layout table
3858 // Check if last character is a newline, if it is
3859 // then need pretend there is an imaginary line afterwards,
3860 // and check if user is touching below previous line.
3861 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3863 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3865 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3869 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3870 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3872 bool matched( false );
3874 // 2 Iterate through matching list of y positions and find closest matching X position.
3875 for( ; it != endIt; ++it )
3877 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3879 if( info.mIsVisible )
3881 // stop when on left side of character's center.
3882 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3883 if( sourceScrollOffset.x < characterMidPointPosition )
3885 if(info.mIsRightToLeftCharacter)
3887 rightToLeftChar = true;
3889 glyphIntersection = info.mPosition.x;
3894 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3900 rightToLeftChar = lastRightToLeftChar;
3903 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3904 closestIndex = lineOffset + matchCharacterIndex;
3906 mClosestCursorPositionEOL = false; // reset
3907 if ( it == endIt && !matched )
3909 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3912 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3913 if( rightToLeftChar && lastRightToLeftChar )
3915 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3920 // closestIndex is the visual index, need to convert it to the logical index
3921 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3923 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3925 // Checks for situations where user is touching between LTR and RTL
3926 // characters. To identify if the user means the end of a LTR string
3927 // or the beginning of an RTL string, and vice versa.
3928 if( closestIndex > 0 )
3930 if( rightToLeftChar && !lastRightToLeftChar )
3935 // A: In this touch range, the user is indicating that they wish to place
3936 // the cursor at the end of the LTR text.
3937 // B: In this touch range, the user is indicating that they wish to place
3938 // the cursor at the end of the RTL text.
3940 // Result of touching A area:
3941 // [.....LTR]|[RTL......]+
3943 // |: primary cursor (for typing LTR chars)
3944 // +: secondary cursor (for typing RTL chars)
3946 // Result of touching B area:
3947 // [.....LTR]+[RTL......]|
3949 // |: primary cursor (for typing RTL chars)
3950 // +: secondary cursor (for typing LTR chars)
3952 if( sourceScrollOffset.x < glyphIntersection )
3957 else if( !rightToLeftChar && lastRightToLeftChar )
3959 if( sourceScrollOffset.x < glyphIntersection )
3966 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3967 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3968 // one further ahead
3969 if( rightToLeftChar && !lastRightToLeftChar )
3974 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3976 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3978 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3987 float TextInput::GetLineJustificationPosition() const
3989 const Vector3& size = mDisplayedTextView.GetCurrentSize();
3990 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
3991 float alignmentOffset = 0.f;
3993 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
3994 if( alignment & Toolkit::Alignment::HorizontalLeft )
3996 alignmentOffset = 0.f;
3998 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4000 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4002 else if( alignment & Toolkit::Alignment::HorizontalRight )
4004 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4007 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4008 float justificationOffset = 0.f;
4010 switch( justification )
4012 case Toolkit::TextView::Left:
4014 justificationOffset = 0.f;
4017 case Toolkit::TextView::Center:
4019 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4022 case Toolkit::TextView::Right:
4024 justificationOffset = mTextLayoutInfo.mTextSize.width;
4027 case Toolkit::TextView::Justified:
4029 justificationOffset = 0.f;
4034 DALI_ASSERT_ALWAYS( false );
4038 return alignmentOffset + justificationOffset;
4041 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4043 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4044 A newline character is not inserted in this case */
4046 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4048 Vector3 cursorPosition;
4050 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4052 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4054 // end character so use
4055 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4056 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4060 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4063 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4065 // If previous character on a different line then use current characters position
4066 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4068 if ( mClosestCursorPositionEOL )
4070 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4074 cursorPosition = Vector3(currentCharInfo.mPosition);
4079 // Previous character is on same line so use position of previous character plus it's width.
4080 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4083 return cursorPosition;
4086 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4088 bool direction(false);
4089 Vector3 alternatePosition;
4090 bool alternatePositionValid(false);
4092 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4095 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4097 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4099 alternatePositionValid = false;
4100 directionRTL = false;
4102 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4104 std::size_t visualCharacterPosition;
4106 // When cursor is not at beginning, consider possibility of
4107 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4108 if(characterPosition > 0)
4110 // Cursor position should be the end of the last character.
4111 // If the last character is LTR, then the end is on the right side of the glyph.
4112 // If the last character is RTL, then the end is on the left side of the glyph.
4113 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4115 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4117 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4120 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4121 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4123 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4124 const Vector3& size = GetControlSize();
4126 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4128 --visualCharacterPosition;
4130 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4133 if(!info.mIsNewLineChar)
4135 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4139 // When cursor points to first character on new line, position cursor at the start of this glyph.
4140 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4142 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4143 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4144 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4146 cursorPosition.x = infoNext.mPosition.x + start;
4147 cursorPosition.y = infoNext.mPosition.y;
4151 // If cursor points to the end of text, then can only position
4152 // cursor where the new line starts based on the line-justification position.
4153 cursorPosition.x = GetLineJustificationPosition();
4155 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4157 // If this is after the last character, then we can assume that the new cursor
4158 // should be exactly one row below the current row.
4160 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4161 cursorPosition.y = info.mPosition.y + rowRect.height;
4165 // If this is not after last character, then we can use this row's height.
4166 // should be exactly one row below the current row.
4168 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4169 cursorPosition.y = info.mPosition.y + rowRect.height;
4174 directionRTL = info.mIsRightToLeftCharacter;
4176 // 1. When the cursor is neither at the beginning or the end,
4177 // we can show multiple cursors under situations when the cursor is
4178 // between RTL and LTR text...
4179 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4181 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4183 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4184 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4186 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4188 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4189 // Text: [...LTR...]|[...RTL...]
4191 // Alternate cursor pos: ^
4192 // In which case we need to display an alternate cursor for the RTL text.
4194 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4195 alternatePosition.y = infoAlt.mPosition.y;
4196 alternatePositionValid = true;
4198 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4200 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4201 // Text: |[...RTL...] [...LTR....]
4203 // Alternate cursor pos: ^
4204 // In which case we need to display an alternate cursor for the RTL text.
4206 alternatePosition.x = infoAlt.mPosition.x;
4207 alternatePosition.y = infoAlt.mPosition.y;
4208 alternatePositionValid = true;
4213 // 2. When the cursor is at the end of the text,
4214 // and we have multi-directional text,
4215 // we can also consider showing mulitple cursors.
4216 // The rule here is:
4217 // If first and last characters on row are different
4218 // Directions, then two cursors need to be displayed.
4220 // Get first logical glyph on row
4221 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4223 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4224 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4226 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4228 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4229 // Text: [...LTR...]|[...RTL...]
4231 // Alternate cursor pos: ^
4232 // In which case we need to display an alternate cursor for the RTL text, this cursor
4233 // should be at the end of the given line.
4235 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4236 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4237 alternatePosition.y = infoAlt.mPosition.y;
4238 alternatePositionValid = true;
4240 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4242 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4243 // Text: |[...RTL...] [...LTR....]
4245 // Alternate cursor pos: ^
4246 // In which case we need to display an alternate cursor for the RTL text.
4248 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4249 alternatePosition.x = infoAlt.mPosition.x;
4250 alternatePosition.y = infoAlt.mPosition.y;
4251 alternatePositionValid = true;
4254 } // characterPosition > 0
4255 else if(characterPosition == 0)
4257 // When the cursor position is at the beginning, it should be at the start of the current character.
4258 // If the current character is LTR, then the start is on the right side of the glyph.
4259 // If the current character is RTL, then the start is on the left side of the glyph.
4260 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4262 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4264 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4267 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4268 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4270 cursorPosition.x = info.mPosition.x + start;
4271 cursorPosition.y = info.mPosition.y;
4272 directionRTL = info.mIsRightToLeftCharacter;
4277 // If the character table is void, place the cursor accordingly the text alignment.
4278 const Vector3& size = GetControlSize();
4280 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4281 float alignmentOffset = 0.f;
4283 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4284 if( alignment & Toolkit::Alignment::HorizontalLeft )
4286 alignmentOffset = 0.f;
4288 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4290 alignmentOffset = 0.5f * ( size.width );
4292 else if( alignment & Toolkit::Alignment::HorizontalRight )
4294 alignmentOffset = size.width;
4297 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4298 cursorPosition.x = alignmentOffset;
4300 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4301 if( alignment & Toolkit::Alignment::VerticalTop )
4303 cursorPosition.y = mLineHeight;
4305 else if( alignment & Toolkit::Alignment::VerticalCenter )
4307 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4309 else if( alignment & Toolkit::Alignment::VerticalBottom )
4311 cursorPosition.y = size.height;
4315 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4316 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4317 if( alternatePositionValid )
4319 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4320 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4323 return cursorPosition;
4326 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4328 // scan string from current position to beginning of current line to note direction of line
4329 while(logicalPosition)
4332 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4333 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4340 return logicalPosition;
4343 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4347 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4350 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4352 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4353 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4355 min = Vector2::ZERO;
4356 max = Vector2(0.0f, mLineHeight);
4360 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4361 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4362 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4364 // If cursor is pointing to end of line, then start from last character.
4365 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4367 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4369 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4370 if( !it->mIsVisible )
4372 characterPosition = FindVisibleCharacter( Left, characterPosition );
4373 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4376 // Scan characters left and right of cursor, stopping when end of line/string reached or
4377 // y position greater than threshold of reference line.
4379 // 1. scan left until we reach the beginning or a different line.
4380 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4381 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4382 // min-x position is the left-most char's left (x)
4383 // max-x position is the right-most char's right (x)
4384 // min-y position is the minimum of all character's top (y)
4385 // max-y position is the maximum of all character's bottom (y+height)
4386 min.y = validCharIt->mPosition.y;
4387 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4392 min.y = std::min(min.y, validCharIt->mPosition.y);
4393 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4402 if( (it->mPosition.y < referenceLine) ||
4403 (it->mIsNewLineChar) ||
4410 // info refers to the first character on this line.
4411 min.x = validCharIt->mPosition.x;
4413 // 2. scan right until we reach end or a different line
4414 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4415 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4419 if( (it->mPosition.y > referenceLine) ||
4420 (it->mIsNewLineChar) ||
4427 min.y = std::min(min.y, validCharIt->mPosition.y);
4428 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4433 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4435 if ( validCharIt != end )
4437 // info refers to the last character on this line.
4438 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4441 return Size( max.x - min.x, max.y - min.y );
4444 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4446 Actor popUpPanel = mPopUpPanel.GetRootActor();
4448 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4454 Dali::Actor parent( touchedActor.GetParent() );
4458 return WasTouchedCheck( parent );
4465 void TextInput::StartMonitoringStageForTouch()
4467 Stage stage = Stage::GetCurrent();
4468 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4471 void TextInput::EndMonitoringStageForTouch()
4473 Stage stage = Stage::GetCurrent();
4474 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4477 void TextInput::OnStageTouched(const TouchEvent& event)
4479 if( event.GetPointCount() > 0 )
4481 if ( TouchPoint::Down == event.GetPoint(0).state )
4483 const Actor touchedActor(event.GetPoint(0).hitActor);
4485 bool popUpShown( false );
4487 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4492 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4494 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4496 EndMonitoringStageForTouch();
4497 HidePopup( true, false );
4500 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4502 EndMonitoringStageForTouch();
4503 ShowGrabHandleAndSetVisibility( false );
4509 void TextInput::SelectText(std::size_t start, std::size_t end)
4511 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4512 IsGrabHandleEnabled()?"true":"false",
4513 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4514 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4515 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4517 StartMonitoringStageForTouch();
4519 if ( mEditModeActive ) // Only allow text selection when in edit mode
4521 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4522 mSelectingText = true;
4524 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4526 ImfManager imfManager = ImfManager::Get();
4529 imfManager.SetCursorPosition ( mCursorPosition );
4530 imfManager.SetSurroundingText( GetText() );
4531 imfManager.NotifyCursorPosition();
4533 // 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.
4535 // Hide grab handle when selecting.
4536 ShowGrabHandleAndSetVisibility( false );
4538 if( start != end ) // something to select
4540 SetCursorVisibility( false );
4541 StopCursorBlinkTimer();
4543 CreateSelectionHandles(start, end);
4546 const TextStyle oldInputStyle( mInputStyle );
4547 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4549 if( oldInputStyle != mInputStyle )
4551 // Updates the line height accordingly with the input style.
4554 EmitStyleChangedSignal();
4560 mSelectingText = false;
4564 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4566 MarkupProcessor::StyledTextArray currentSelectedText;
4568 if ( IsTextSelected() )
4570 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4571 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4573 for(; it != end; ++it)
4575 MarkupProcessor::StyledText& styledText( *it );
4576 currentSelectedText.push_back( styledText );
4579 return currentSelectedText;
4582 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4584 const std::size_t beginIndex = std::min( begin, end );
4585 const std::size_t endIndex = std::max( begin, end );
4588 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4590 // Create a styled text array used to replace the text into the text-view.
4591 MarkupProcessor::StyledTextArray text;
4592 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4594 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4595 GetTextLayoutInfo();
4597 if( IsScrollEnabled() )
4599 // Need to set the scroll position as the text's size may have changed.
4600 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4603 ShowGrabHandleAndSetVisibility( false );
4609 // Set Handle positioning as the new style may have repositioned the characters.
4610 SetSelectionHandlePosition(HandleOne);
4611 SetSelectionHandlePosition(HandleTwo);
4614 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4616 // Just hide the grab handle when keyboard is hidden.
4617 if (!keyboardShown )
4619 ShowGrabHandleAndSetVisibility( false );
4621 // If the keyboard is not now being shown, then hide the popup panel
4622 mPopUpPanel.Hide( true );
4626 // Removes highlight and resumes edit mode state
4627 void TextInput::RemoveHighlight()
4629 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4631 if ( mHighlightMeshActor )
4633 if ( mSelectionHandleOne )
4635 mActiveLayer.Remove( mSelectionHandleOne );
4636 mSelectionHandleOne.Reset();
4637 mSelectionHandleOneOffset.x = 0.0f;
4639 if ( mSelectionHandleTwo )
4641 mActiveLayer.Remove( mSelectionHandleTwo );
4642 mSelectionHandleTwo.Reset();
4643 mSelectionHandleTwoOffset.x = 0.0f;
4646 mNewHighlightInfo.mQuadList.clear();
4648 Self().Remove( mHighlightMeshActor );
4650 SetCursorVisibility( true );
4651 StartCursorBlinkTimer();
4653 mHighlightMeshActor.Reset();
4654 // NOTE: We cannot dereference mHighlightMesh, due
4655 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4660 mSelectionHandleOnePosition = 0;
4661 mSelectionHandleTwoPosition = 0;
4664 void TextInput::CreateHighlight()
4666 if ( !mHighlightMeshActor )
4668 mMeshData = MeshData( );
4669 mMeshData.SetHasNormals( true );
4671 mCustomMaterial = Material::New("CustomMaterial");
4672 mCustomMaterial.SetDiffuseColor( LIGHTBLUE );
4674 mMeshData.SetMaterial( mCustomMaterial );
4676 mHighlightMesh = Mesh::New( mMeshData );
4678 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4679 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4680 mHighlightMeshActor.SetInheritShaderEffect( false );
4681 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4682 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4683 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4684 mHighlightMeshActor.SetAffectedByLighting(false);
4686 Self().Add(mHighlightMeshActor);
4691 bool TextInput::CopySelectedTextToClipboard()
4693 mCurrentCopySelecton.clear();
4695 mCurrentCopySelecton = GetSelectedText();
4697 std::string stringToStore;
4699 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4700 * a marked up string.
4702 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4703 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4704 bool success = mClipboard.SetItem( stringToStore );
4708 void TextInput::PasteText( const Text& text )
4710 // Update Flag, indicates whether to update the text-input contents or not.
4711 // Any key stroke that results in a visual change of the text-input should
4712 // set this flag to true.
4713 bool update = false;
4714 if( mHighlightMeshActor )
4716 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4717 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4719 ImfManager imfManager = ImfManager::Get();
4722 imfManager.SetCursorPosition( mCursorPosition );
4723 imfManager.NotifyCursorPosition();
4725 DeleteHighlightedText( true );
4729 bool textExceedsMaximunNumberOfCharacters = false;
4730 bool textExceedsBoundary = false;
4732 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4734 mCursorPosition += insertedStringLength;
4735 ImfManager imfManager = ImfManager::Get();
4738 imfManager.SetCursorPosition ( mCursorPosition );
4739 imfManager.NotifyCursorPosition();
4742 update = update || ( insertedStringLength > 0 );
4748 if( insertedStringLength < text.GetLength() )
4750 EmitMaxInputCharactersReachedSignal();
4753 if( textExceedsBoundary )
4755 EmitInputTextExceedsBoundariesSignal();
4759 void TextInput::SetTextDirection()
4761 // Put the cursor to the right if we are empty and an RTL language is being used.
4762 if ( mStyledText.empty() )
4764 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4766 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4767 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4769 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4770 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4772 int alignment( mDisplayedTextView.GetTextAlignment() &
4773 ( Toolkit::Alignment::VerticalTop |
4774 Toolkit::Alignment::VerticalCenter |
4775 Toolkit::Alignment::VerticalBottom |
4776 Toolkit::Alignment::HorizontalCenter ) );
4777 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4779 // If our alignment is in the center, then do not change.
4780 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4782 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4785 // If our justification is in the center, then do not change.
4786 if ( justification != Toolkit::TextView::Center )
4788 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4791 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4792 mDisplayedTextView.SetLineJustification( justification );
4796 void TextInput::UpdateLineHeight()
4798 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4799 mLineHeight = font.GetLineHeight();
4801 // 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.
4803 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4805 if( !mExceedEnabled || shrink )
4807 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4811 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4813 std::size_t position = 0;
4815 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4821 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4823 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4825 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4831 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4832 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4834 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4840 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4845 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4852 void TextInput::SetSortModifier( float depthOffset )
4854 if(mDisplayedTextView)
4856 mDisplayedTextView.SetSortModifier(depthOffset);
4860 void TextInput::SetSnapshotModeEnabled( bool enable )
4862 if(mDisplayedTextView)
4864 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4868 bool TextInput::IsSnapshotModeEnabled() const
4870 bool snapshotEnabled = false;
4872 if(mDisplayedTextView)
4874 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4877 return snapshotEnabled;
4880 void TextInput::SetMarkupProcessingEnabled( bool enable )
4882 mMarkUpEnabled = enable;
4885 bool TextInput::IsMarkupProcessingEnabled() const
4887 return mMarkUpEnabled;
4890 void TextInput::SetScrollEnabled( bool enable )
4892 if( mDisplayedTextView )
4894 mDisplayedTextView.SetScrollEnabled( enable );
4899 // Don't set cursor's and handle's visibility to false if they are outside the
4900 // boundaries of the text-input.
4901 mIsCursorInScrollArea = true;
4902 mIsGrabHandleInScrollArea = true;
4903 if( mSelectionHandleOne && mSelectionHandleTwo )
4905 mSelectionHandleOne.SetVisible( true );
4906 mSelectionHandleTwo.SetVisible( true );
4908 if( mHighlightMeshActor )
4910 mHighlightMeshActor.SetVisible( true );
4916 bool TextInput::IsScrollEnabled() const
4918 bool scrollEnabled = false;
4920 if( mDisplayedTextView )
4922 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4925 return scrollEnabled;
4928 void TextInput::SetScrollPosition( const Vector2& position )
4930 if( mDisplayedTextView )
4932 mDisplayedTextView.SetScrollPosition( position );
4936 Vector2 TextInput::GetScrollPosition() const
4938 Vector2 scrollPosition;
4940 if( mDisplayedTextView )
4942 scrollPosition = mDisplayedTextView.GetScrollPosition();
4945 return scrollPosition;
4948 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4950 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4951 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4952 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4954 // Add style to the new input text.
4955 MarkupProcessor::StyledTextArray textToInsert;
4956 for( std::size_t i = 0; i < insertedStringLength; ++i )
4958 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4959 textToInsert.push_back( newStyledCharacter );
4962 //Insert text to the TextView.
4963 const bool emptyTextView = mStyledText.empty();
4964 if( emptyTextView && mPlaceHolderSet )
4966 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4967 mDisplayedTextView.SetText( textToInsert );
4971 if( 0 == numberOfCharactersToReplace )
4973 mDisplayedTextView.InsertTextAt( position, textToInsert );
4977 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4980 mPlaceHolderSet = false;
4982 if( textToInsert.empty() )
4984 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4985 GetTextLayoutInfo();
4989 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4990 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4993 textExceedsBoundary = false;
4995 if( !mExceedEnabled )
4997 const Vector3& size = GetControlSize();
4999 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5001 // If new text does not fit within TextView
5002 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
5003 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
5004 GetTextLayoutInfo();
5005 textExceedsBoundary = true;
5006 insertedStringLength = 0;
5009 if( textExceedsBoundary )
5011 // Add the part of the text which fits on the text-input.
5013 // Split the text which doesn't fit in two halves.
5014 MarkupProcessor::StyledTextArray firstHalf;
5015 MarkupProcessor::StyledTextArray secondHalf;
5016 SplitText( textToInsert, firstHalf, secondHalf );
5018 // Clear text. This text will be filled with the text inserted.
5019 textToInsert.clear();
5021 // Where to insert the text.
5022 std::size_t positionToInsert = position;
5024 bool end = text.GetLength() <= 1;
5027 // Insert text and check ...
5028 const std::size_t textLength = firstHalf.size();
5029 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5030 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5032 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5034 // Inserted text doesn't fit.
5036 // Remove inserted text
5037 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5038 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5040 // The iteration finishes when only one character doesn't fit.
5041 end = textLength <= 1;
5045 // Prepare next two halves for next iteration.
5046 MarkupProcessor::StyledTextArray copyText = firstHalf;
5047 SplitText( copyText, firstHalf, secondHalf );
5054 // store text to be inserted in mStyledText.
5055 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5057 // Increase the inserted characters counter.
5058 insertedStringLength += textLength;
5060 // Prepare next two halves for next iteration.
5061 MarkupProcessor::StyledTextArray copyText = secondHalf;
5062 SplitText( copyText, firstHalf, secondHalf );
5064 // Update where next text has to be inserted
5065 positionToInsert += textLength;
5071 if( textToInsert.empty() && emptyTextView )
5073 // No character has been added and the text-view was empty.
5074 // Set the placeholder text.
5075 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5076 mPlaceHolderSet = true;
5080 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5081 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5082 mPlaceHolderSet = false;
5085 return insertedStringLength;
5088 void TextInput::GetTextLayoutInfo()
5090 if( mStyledText.empty() )
5092 // The text-input has no text, clear the text-view's layout info.
5093 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5097 if( mDisplayedTextView )
5099 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5103 // There is no text-view.
5104 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5109 void TextInput::EmitStyleChangedSignal()
5111 // emit signal if input style changes.
5112 Toolkit::TextInput handle( GetOwner() );
5113 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5116 void TextInput::EmitTextModified()
5118 // emit signal when text changes.
5119 Toolkit::TextInput handle( GetOwner() );
5120 mTextModifiedSignal.Emit( handle );
5124 void TextInput::EmitMaxInputCharactersReachedSignal()
5126 // emit signal if max characters is reached during text input.
5127 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5129 Toolkit::TextInput handle( GetOwner() );
5130 mMaxInputCharactersReachedSignalV2.Emit( handle );
5133 void TextInput::EmitInputTextExceedsBoundariesSignal()
5135 // Emit a signal when the input text exceeds the boundaries of the text input.
5137 Toolkit::TextInput handle( GetOwner() );
5138 mInputTextExceedBoundariesSignalV2.Emit( handle );
5141 } // namespace Internal
5143 } // namespace Toolkit