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 );
1772 else if (keyName == "Right")
1777 else if (keyName == "Left")
1779 AdvanceCursor(true);
1782 else // event is a character
1784 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1785 if ( !keyString.empty() )
1787 if ( mHighlightMeshActor )
1789 // replaces highlighted text with new character
1790 DeleteHighlightedText( false );
1794 // Received key String
1795 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1801 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1802 // as this is a costly operation.
1808 if(update || scroll)
1810 if( IsScrollEnabled() )
1812 // Calculates the new cursor position (in actor coordinates)
1813 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1815 ScrollTextViewToMakeCursorVisible( cursorPosition );
1822 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1824 std::string keyName = event.keyPressedName;
1825 std::string keyString = event.keyPressed;
1827 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1829 // The selected text become deselected when the key code is DALI_KEY_BACK.
1830 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1839 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1841 // Updates the stored scroll position.
1842 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1844 const Vector3& controlSize = GetControlSize();
1845 Size cursorSize( CURSOR_THICKNESS, 0.f );
1847 // Updates the cursor and grab handle position and visibility.
1848 if( mGrabHandle || mCursor )
1850 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1851 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1853 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1855 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1859 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1860 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1865 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1866 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1870 // Updates the selection handles and highlighted text position and visibility.
1871 if( mSelectionHandleOne && mSelectionHandleTwo )
1873 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1874 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1875 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1876 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1877 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1878 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1880 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1881 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1883 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1884 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1885 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1886 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1888 if( mHighlightMeshActor )
1890 mHighlightMeshActor.SetVisible( true );
1896 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1898 // Scroll the text to make the cursor visible.
1899 const Size cursorSize( CURSOR_THICKNESS,
1900 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1902 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1904 const Vector3& controlSize = GetControlSize();
1906 // Calculates the new scroll position.
1907 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1908 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1910 scrollOffset.x += cursorPosition.x;
1913 if( cursorPosition.y - cursorSize.height < 0.f )
1915 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1917 else if( cursorPosition.y > controlSize.height )
1919 scrollOffset.y += cursorPosition.y;
1922 // Sets the new scroll position.
1923 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1924 SetScrollPosition( scrollOffset );
1927 void TextInput::StartScrollTimer()
1931 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1932 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1935 if( !mScrollTimer.IsRunning() )
1937 mScrollTimer.Start();
1941 void TextInput::StopScrollTimer()
1945 mScrollTimer.Stop();
1949 bool TextInput::OnScrollTimerTick()
1951 // TODO: need to set the new style accordingly the new handle position.
1953 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1955 // nothing to do if all handles are invisible or doesn't exist.
1961 // Choose between the grab handle or the selection handles.
1962 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1963 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1964 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1966 std::size_t newCursorPosition = 0;
1967 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1969 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1970 // the new selection handle's position needs to be different of the other one.
1971 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1972 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1973 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1975 if( differentSelectionHandles )
1977 handlePosition = newCursorPosition;
1979 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1981 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1983 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1984 scrollPosition += scrollDelta;
1985 SetScrollPosition( scrollPosition );
1987 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1992 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1995 actualHandlePosition.x += mScrollDisplacement.x;
1996 actualHandlePosition.y += mScrollDisplacement.y;
2001 // Public Internal Methods (public for testing purpose)
2003 void TextInput::SetUpTouchEvents()
2005 if ( !mTapDetector )
2007 mTapDetector = TapGestureDetector::New();
2008 // Attach the actors and connect the signal
2009 mTapDetector.Attach(Self());
2011 // As contains children which may register for tap the default control detector is not used.
2012 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2015 if ( !mDoubleTapDetector )
2017 mDoubleTapDetector = TapGestureDetector::New();
2018 mDoubleTapDetector.SetTapsRequired( 2 );
2019 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2021 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2022 // so that we do not, unnecessarily, have a double tap request all the time
2025 if ( !mPanGestureDetector )
2027 mPanGestureDetector = PanGestureDetector::New();
2028 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2031 if ( !mLongPressDetector )
2033 mLongPressDetector = LongPressGestureDetector::New();
2034 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2035 mLongPressDetector.Attach(Self());
2039 void TextInput::CreateTextViewActor()
2041 mDisplayedTextView = Toolkit::TextView::New();
2042 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2043 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2044 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2045 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2046 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2047 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2048 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2049 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2050 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2051 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2053 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2055 Self().Add( mDisplayedTextView );
2058 // Start a timer to initiate, used by the cursor to blink.
2059 void TextInput::StartCursorBlinkTimer()
2061 if ( !mCursorBlinkTimer )
2063 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2064 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2067 if ( !mCursorBlinkTimer.IsRunning() )
2069 mCursorBlinkTimer.Start();
2073 // Start a timer to initiate, used by the cursor to blink.
2074 void TextInput::StopCursorBlinkTimer()
2076 if ( mCursorBlinkTimer )
2078 mCursorBlinkTimer.Stop();
2082 void TextInput::StartEditMode()
2084 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2086 if(!mEditModeActive)
2091 if ( mDoubleTapDetector )
2093 mDoubleTapDetector.Attach( Self() );
2097 void TextInput::EndEditMode()
2099 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2101 ClearKeyInputFocus();
2103 if ( mDoubleTapDetector )
2105 mDoubleTapDetector.Detach( Self() );
2109 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2111 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2113 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2115 style.SetUnderline( true );
2116 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2120 void TextInput::RemovePreEditStyle()
2122 if ( !mUnderlinedPriorToPreEdit )
2125 style.SetUnderline( false );
2126 SetActiveStyle( style, TextStyle::UNDERLINE );
2130 // IMF related methods
2133 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2135 bool update( false );
2136 bool preeditResetRequired ( false );
2138 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2140 HidePopup(); // If Pop-up shown then hides it as editing text.
2143 switch ( imfEvent.eventName )
2145 case ImfManager::PREEDIT:
2147 mIgnoreFirstCommitFlag = false;
2149 // 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
2150 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2152 // replaces highlighted text with new character
2153 DeleteHighlightedText( false );
2156 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2158 if( IsScrollEnabled() )
2160 // Calculates the new cursor position (in actor coordinates)
2161 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2162 ScrollTextViewToMakeCursorVisible( cursorPosition );
2169 case ImfManager::COMMIT:
2171 if( mIgnoreFirstCommitFlag )
2173 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2174 mIgnoreFirstCommitFlag = false;
2178 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2180 // 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
2181 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2183 // replaces highlighted text with new character
2184 DeleteHighlightedText( false );
2187 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2188 // not needed, one such scenario is when the pre-edit word is too long to fit.
2189 if ( !mIgnoreCommitFlag )
2191 update = CommitReceived( imfEvent.predictiveString );
2195 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2201 if( IsScrollEnabled() )
2203 // Calculates the new cursor position (in actor coordinates)
2204 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2206 ScrollTextViewToMakeCursorVisible( cursorPosition );
2211 case ImfManager::DELETESURROUNDING:
2213 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2214 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2216 mPreEditFlag = false;
2218 std::size_t toDelete = 0;
2219 std::size_t numberOfCharacters = 0;
2221 if( mHighlightMeshActor )
2223 // delete highlighted text.
2224 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2225 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2229 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2231 toDelete = mCursorPosition + imfEvent.cursorOffset;
2233 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2235 numberOfCharacters = mStyledText.size() - toDelete;
2239 numberOfCharacters = imfEvent.numberOfChars;
2242 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2243 DeleteRange( toDelete, numberOfCharacters );
2245 mCursorPosition = toDelete;
2246 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2250 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2253 case ImfManager::GETSURROUNDING:
2255 // 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
2256 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2257 if (! ( mHighlightMeshActor || mSelectingText ) )
2259 std::string text( GetText() );
2260 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2262 imfManager.SetCursorPosition( mCursorPosition );
2263 imfManager.SetSurroundingText( text );
2266 if( 0 != mNumberOfSurroundingCharactersDeleted )
2268 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2269 mNumberOfSurroundingCharactersDeleted = 0;
2271 if( mStyledText.empty() )
2273 // Styled text is empty, so set the placeholder text.
2274 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2275 mPlaceHolderSet = true;
2280 case ImfManager::VOID:
2282 DALI_ASSERT_DEBUG( false );
2286 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2288 return callbackData;
2291 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2293 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2295 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2296 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2298 bool preeditResetRequest ( false );
2300 if( mPreEditFlag ) // Already in pre-edit state.
2302 if( mStyledText.size() >= mMaxStringLength )
2304 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2305 // Cannot fit these characters into field, clear pre-edit.
2306 if ( !mUnderlinedPriorToPreEdit )
2309 style.SetUnderline( false );
2310 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2312 mIgnoreCommitFlag = true;
2313 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2314 mPreEditFlag = false;
2315 EmitMaxInputCharactersReachedSignal();
2319 // delete existing pre-edit string
2320 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2322 // Store new pre-edit string
2323 mPreEditString.SetText( keyString );
2325 if ( keyString.empty() )
2327 mPreEditFlag = false;
2328 mCursorPosition = mPreEditStartPosition;
2330 if( mStyledText.empty() )
2332 // Styled text is empty, so set the placeholder text.
2333 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2334 mPlaceHolderSet = true;
2338 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2340 GetTextLayoutInfo();
2345 // Insert new pre-edit string. InsertAt updates the size and position table.
2346 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2347 // If word was too long to be inserted then cursorOffset would be out of range as keyboard assumes there is not limit. Hence use of std::min.
2348 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2349 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2350 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2353 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2357 else // mPreEditFlag not set
2359 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2361 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2362 // new pre-edit so move into pre-edit state by setting flag
2363 mPreEditFlag = true;
2364 mPreEditString.SetText( keyString ); // store new pre-edit string
2365 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2366 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2367 // If word was too long to be inserted then cursorOffset would be out of range as keyboard assumes there is not limit. Hence use of std::min.
2368 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2369 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2370 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2371 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2377 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2381 return preeditResetRequest;
2384 bool TextInput::CommitReceived(const std::string& keyString )
2386 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2387 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2389 bool update( false );
2391 RemovePreEditStyle();
2393 const std::size_t styledTextSize( mStyledText.size() );
2394 if( styledTextSize >= mMaxStringLength )
2396 // Cannot fit these characters into field, clear pre-edit.
2399 mIgnoreCommitFlag = true;
2400 mPreEditFlag = false;
2402 EmitMaxInputCharactersReachedSignal();
2408 // delete existing pre-edit string
2409 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2410 mPreEditFlag = false;
2412 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2413 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2415 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2417 // No need to update cursor position as Cursor location given by touch.
2418 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2419 mPreserveCursorPosition = false;
2423 // Cursor not set by touch so needs to be re-positioned to input more text
2424 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2426 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2427 if ( mCommitByKeyInput )
2429 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2430 mCommitByKeyInput = false;
2436 if ( mSelectTextOnCommit )
2438 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2443 else // mPreEditFlag not set
2445 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2447 if( mStyledText.empty() && mPlaceHolderSet )
2449 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2450 mDisplayedTextView.SetText( "" );
2451 mNumberOfSurroundingCharactersDeleted = 0;
2452 mPlaceHolderSet = false;
2454 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2456 mNumberOfSurroundingCharactersDeleted = 0;
2461 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2466 mSelectTextOnCommit = false;
2468 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2469 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2474 // End of IMF related methods
2476 std::size_t TextInput::DeletePreEdit()
2478 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2480 DALI_ASSERT_DEBUG( mPreEditFlag );
2482 const std::size_t preEditStringLength = mPreEditString.GetLength();
2483 const std::size_t styledTextSize = mStyledText.size();
2485 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2487 // Prevents erase items outside mStyledText bounds.
2488 if( mPreEditStartPosition > styledTextSize )
2490 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2491 mPreEditStartPosition = styledTextSize;
2494 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2496 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2497 endPosition = styledTextSize;
2500 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2502 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2503 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2505 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2507 return preEditStringLength;
2510 void TextInput::PreEditReset( bool preserveCursorPosition )
2512 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2513 preserveCursorPosition, mCursorPosition);
2515 // Store flag to indicate that we do not want to lose the cursor position as the reset may have occurred due to touch event moving the cursor.
2516 mPreserveCursorPosition = preserveCursorPosition;
2518 // Reset incase we are in a pre-edit state.
2519 ImfManager imfManager = ImfManager::Get();
2522 imfManager.Reset(); // Will trigger a commit message
2526 void TextInput::CursorUpdate()
2530 ImfManager imfManager = ImfManager::Get();
2533 std::string text( GetText() );
2534 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2535 imfManager.SetCursorPosition ( mCursorPosition );
2536 imfManager.NotifyCursorPosition();
2540 /* Delete highlighted characters redisplay*/
2541 void TextInput::DeleteHighlightedText( bool inheritStyle )
2543 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2545 if(mHighlightMeshActor)
2547 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2549 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2550 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2552 // Get the styled text of the characters to be deleted as it may be needed if
2553 // the "exceed the text-input's boundaries" option is disabled.
2554 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2556 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2558 mStyledText.erase( start, end ); // erase range of characters
2560 // Remove text from TextView.
2562 if( mStyledText.empty() )
2564 // Styled text is empty, so set the placeholder text.
2565 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2566 mPlaceHolderSet = true;
2570 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2572 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2574 // It may happen than after removing a white space or a new line character,
2575 // two words merge, this new word could be big enough to not fit in its
2576 // current line, so moved to the next one, and make some part of the text to
2577 // exceed the text-input's boundary.
2578 if( !mExceedEnabled )
2580 // Get the new text layout after removing some characters.
2581 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2583 // Get text-input's size.
2584 const Vector3& size = GetControlSize();
2586 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2587 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2589 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2591 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2592 styledCharactersToDelete.begin(),
2593 styledCharactersToDelete.end() );
2597 GetTextLayoutInfo();
2603 const TextStyle oldInputStyle( mInputStyle );
2605 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2607 if( oldInputStyle != mInputStyle )
2609 // Updates the line height accordingly with the input style.
2612 EmitStyleChangedSignal();
2618 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2620 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2621 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2623 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2626 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2628 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2629 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2631 mStyledText.erase(itStart, itEnd);
2633 // update the selection handles if they are visible.
2634 if( mHighlightMeshActor )
2636 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2637 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2639 if( minHandle >= start + ncharacters )
2641 minHandle -= ncharacters;
2643 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2648 if( maxHandle >= start + ncharacters )
2650 maxHandle -= ncharacters;
2652 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2658 // 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.
2661 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2663 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2664 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2665 // Mean we do not re-draw the text more than we have too.
2668 /* Delete character at current cursor position and redisplay*/
2669 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2671 // Ensure positionToDelete is not out of bounds.
2672 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2673 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2674 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2676 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2679 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2681 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2683 // Get the styled text of the character to be deleted as it may be needed if
2684 // the "exceed the text-input's boundaries" option is disabled.
2685 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2687 mStyledText.erase(it); // erase the character left of positionToDelete
2689 if( mStyledText.empty() )
2691 // Styled text is empty, so set the placeholder text.
2692 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2693 mPlaceHolderSet = true;
2697 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2699 const Character characterToDelete = styledCharacterToDelete.mText[0];
2701 // It may happen than after removing a white space or a new line character,
2702 // two words merge, this new word could be big enough to not fit in its
2703 // current line, so moved to the next one, and make some part of the text to
2704 // exceed the text-input's boundary.
2705 if( !mExceedEnabled )
2707 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2709 // Get the new text layout after removing one character.
2710 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2712 // Get text-input's size.
2713 const Vector3& size = GetControlSize();
2715 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2716 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2718 MarkupProcessor::StyledTextArray array;
2719 array.push_back( styledCharacterToDelete );
2720 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2722 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2727 GetTextLayoutInfo();
2729 ShowGrabHandleAndSetVisibility( false );
2731 mCursorPosition = positionToDelete -1;
2733 const TextStyle oldInputStyle( mInputStyle );
2735 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2737 if( oldInputStyle != mInputStyle )
2739 // Updates the line height accordingly with the input style.
2742 EmitStyleChangedSignal();
2747 /*Insert new character into the string and (optionally) redisplay text-input*/
2748 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2750 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2752 // Ensure insertionPosition is not out of bounds.
2753 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2755 bool textExceedsMaximunNumberOfCharacters = false;
2756 bool textExceedsBoundary = false;
2757 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2759 ShowGrabHandleAndSetVisibility( false );
2761 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2765 mIgnoreCommitFlag = true;
2766 mPreEditFlag = false;
2767 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2768 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2771 if( textExceedsMaximunNumberOfCharacters )
2773 EmitMaxInputCharactersReachedSignal();
2776 if( textExceedsBoundary )
2778 EmitInputTextExceedsBoundariesSignal();
2779 PreEditReset( false );
2783 return insertedStringLength;
2786 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2792 cursor = ImageActor::New( cursorImage );
2796 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2799 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2800 cursor.SetNinePatchBorder( border );
2802 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2803 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2804 cursor.SetVisible(false);
2809 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2811 // As cursor is not moving due to grab handle, handle should be hidden.
2812 ShowGrabHandleAndSetVisibility( false );
2814 bool cursorPositionChanged = false;
2817 if ( mCursorPosition >= places )
2819 mCursorPosition = mCursorPosition - places;
2820 cursorPositionChanged = true;
2825 if ((mCursorPosition + places) <= mStyledText.size())
2827 mCursorPosition = mCursorPosition + places;
2828 cursorPositionChanged = true;
2832 if( cursorPositionChanged )
2834 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2836 const TextStyle oldInputStyle( mInputStyle );
2837 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2841 if( oldInputStyle != mInputStyle )
2843 // Updates the line height accordingly with the input style.
2846 EmitStyleChangedSignal();
2849 ImfManager imfManager = ImfManager::Get();
2852 imfManager.SetCursorPosition ( mCursorPosition );
2853 imfManager.NotifyCursorPosition();
2858 void TextInput::DrawCursor(const std::size_t nthChar)
2860 // Get height of cursor and set its size
2861 Size size( CURSOR_THICKNESS, 0.0f );
2862 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2864 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2868 // Measure Font so know how big text will be if no initial text to measure.
2869 size.height = mLineHeight;
2872 mCursor.SetSize(size);
2874 // If the character is italic then the cursor also tilts.
2875 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2877 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2879 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2881 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2882 bool altPositionValid; // Alternate cursor validity flag.
2883 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2884 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2886 SetAltCursorEnabled( altPositionValid );
2888 if(!altPositionValid)
2890 mCursor.SetPosition( position + UI_OFFSET );
2894 size.height *= 0.5f;
2895 mCursor.SetSize(size);
2896 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2898 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2899 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2900 size.height = rowSize.height * 0.5f;
2901 mCursorRTL.SetSize(size);
2902 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2905 if( IsScrollEnabled() )
2907 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2908 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2913 void TextInput::SetAltCursorEnabled( bool enabled )
2915 mCursorRTLEnabled = enabled;
2916 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2919 void TextInput::SetCursorVisibility( bool visible )
2921 mCursorVisibility = visible;
2922 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2923 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2926 void TextInput::CreateGrabHandle( Dali::Image image )
2932 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2936 mGrabHandleImage = image;
2939 mGrabHandle = ImageActor::New(mGrabHandleImage);
2940 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2941 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2943 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2945 ShowGrabHandleAndSetVisibility( false );
2947 CreateGrabArea( mGrabHandle );
2949 mActiveLayer.Add(mGrabHandle);
2953 void TextInput::CreateGrabArea( Actor& parent )
2955 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2956 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2957 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2958 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2959 mTapDetector.Attach( mGrabArea );
2960 mPanGestureDetector.Attach( mGrabArea );
2962 parent.Add(mGrabArea);
2965 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2967 Vector3 actualHandlePosition;
2971 mActualGrabHandlePosition.x += displacement.x;
2972 mActualGrabHandlePosition.y += displacement.y;
2974 // Grab handle should jump to the nearest character and take cursor with it
2975 std::size_t newCursorPosition = 0;
2976 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2978 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2980 bool handleVisible = true;
2982 if( IsScrollEnabled() )
2984 const Vector3 controlSize = GetControlSize();
2985 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
2986 // Scrolls the text if the handle is not in a visible position
2987 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2994 mCurrentHandlePosition = actualHandlePosition;
2995 mScrollDisplacement = Vector2::ZERO;
2999 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3001 mScrollDisplacement.x = -SCROLL_SPEED;
3003 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3005 mScrollDisplacement.x = SCROLL_SPEED;
3007 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3009 mScrollDisplacement.y = -SCROLL_SPEED;
3011 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3013 mScrollDisplacement.y = SCROLL_SPEED;
3019 if( handleVisible && // Only redraw cursor and do updates if position changed
3020 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3022 mCursorPosition = newCursorPosition;
3024 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3026 const TextStyle oldInputStyle( mInputStyle );
3028 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3030 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3032 if( oldInputStyle != mInputStyle )
3034 // Updates the line height accordingly with the input style.
3037 EmitStyleChangedSignal();
3042 return actualHandlePosition;
3045 void TextInput::ShowGrabHandle( bool visible )
3047 if ( IsGrabHandleEnabled() )
3051 mGrabHandle.SetVisible( mGrabHandleVisibility );
3053 StartMonitoringStageForTouch();
3057 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3059 mGrabHandleVisibility = visible;
3060 ShowGrabHandle( visible );
3063 // Callbacks connected to be Property notifications for Boundary checking.
3065 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3067 mIsSelectionHandleOneFlipped = true;
3068 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3069 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3072 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3074 mIsSelectionHandleOneFlipped = false;
3075 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3076 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3079 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3081 mIsSelectionHandleTwoFlipped = true;
3082 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3083 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3086 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3088 mIsSelectionHandleTwoFlipped = false;
3089 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3090 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3093 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3094 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3096 mSelectionHandleOne.SetOpacity(0.0f);
3099 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3101 mSelectionHandleOne.SetOpacity(1.0f);
3104 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3106 mSelectionHandleTwo.SetOpacity(0.0f);
3109 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3111 mSelectionHandleTwo.SetOpacity(1.0f);
3114 // End of Callbacks connected to be Property notifications for Boundary checking.
3116 void TextInput::SetUpHandlePropertyNotifications()
3118 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3120 Vector3 handlesize = GetSelectionHandleSize();
3122 // Exceeding horizontal boundary
3123 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3124 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3126 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3127 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3129 // Within horizontal boundary
3130 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3131 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3133 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3134 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3136 // Exceeding vertical boundary
3137 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3138 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3139 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3140 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3142 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3143 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3144 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3145 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3147 // Within vertical boundary
3148 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3149 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3150 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3151 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3153 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3154 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3155 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3156 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3159 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3161 mSelectionHandleOnePosition = start;
3162 mSelectionHandleTwoPosition = end;
3164 if ( !mSelectionHandleOne )
3166 // create normal and pressed images
3167 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3168 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3170 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3171 mSelectionHandleOne.SetName("SelectionHandleOne");
3172 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3173 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3174 mIsSelectionHandleOneFlipped = false;
3175 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3177 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3178 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3180 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3181 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3183 mTapDetector.Attach( mHandleOneGrabArea );
3184 mPanGestureDetector.Attach( mHandleOneGrabArea );
3186 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3188 mSelectionHandleOne.Add( mHandleOneGrabArea );
3189 mActiveLayer.Add( mSelectionHandleOne );
3192 if ( !mSelectionHandleTwo )
3194 // create normal and pressed images
3195 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3196 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3198 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3199 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3200 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3201 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3202 mIsSelectionHandleTwoFlipped = false;
3203 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3205 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3206 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3207 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3208 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3210 mTapDetector.Attach( mHandleTwoGrabArea );
3211 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3213 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3215 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3217 mActiveLayer.Add( mSelectionHandleTwo );
3220 SetUpHandlePropertyNotifications();
3222 // update table as text may have changed.
3223 GetTextLayoutInfo();
3225 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3226 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3228 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3229 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3231 // Calculates and set the visibility if the scroll mode is enabled.
3232 bool isSelectionHandleOneVisible = true;
3233 bool isSelectionHandleTwoVisible = true;
3234 if( IsScrollEnabled() )
3236 const Vector3& controlSize( GetControlSize() );
3237 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3238 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3239 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3240 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3243 CreateHighlight(); // function will only create highlight if not already created.
3246 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3248 Vector3 actualHandlePosition;
3250 if ( mSelectionHandleOne && mSelectionHandleTwo )
3252 const Vector3& controlSize = GetControlSize();
3254 Size cursorSize( CURSOR_THICKNESS, 0.f );
3256 // Get a reference of the wanted selection handle (handle one or two).
3257 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3259 // Get a reference for the current position of the handle and a copy of its pair
3260 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3261 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3263 // Get a handle of the selection handle actor
3264 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3266 // Selection handles should jump to the nearest character
3267 std::size_t newHandlePosition = 0;
3268 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3270 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3272 bool handleVisible = true;
3274 if( IsScrollEnabled() )
3276 mCurrentSelectionId = handleId;
3278 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3279 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3280 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3287 mCurrentSelectionHandlePosition = actualHandlePosition;
3288 mScrollDisplacement = Vector2::ZERO;
3292 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3294 mScrollDisplacement.x = -SCROLL_SPEED;
3296 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3298 mScrollDisplacement.x = SCROLL_SPEED;
3300 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3302 mScrollDisplacement.y = -SCROLL_SPEED;
3304 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3306 mScrollDisplacement.y = SCROLL_SPEED;
3312 if ( handleVisible && // Ensure the handle is visible.
3313 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3314 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3316 currentSelectionHandlePosition = newHandlePosition;
3318 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3319 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3323 if ( handleId == HandleOne )
3325 const TextStyle oldInputStyle( mInputStyle );
3327 // Set Active Style to that of first character in selection
3328 if( mSelectionHandleOnePosition < mStyledText.size() )
3330 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3333 if( oldInputStyle != mInputStyle )
3335 // Updates the line height accordingly with the input style.
3338 EmitStyleChangedSignal();
3344 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3347 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3350 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3351 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3353 if ( selectionHandleActor )
3355 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3356 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3357 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3359 if( IsScrollEnabled() )
3361 const Size cursorSize( CURSOR_THICKNESS,
3362 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3363 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3365 GetControlSize() ) );
3370 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3372 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3373 // For now the visual position of end of logical string will be end of visual string.
3374 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3376 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3379 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3381 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3382 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3383 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3384 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3386 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3388 // Deselect text prior to startSelectionIt
3389 for(;it!=startSelectionIt;++it)
3391 selectedVisualText[*it] = false;
3394 // Select text from startSelectionIt -> endSelectionIt
3395 for(;it!=endSelectionIt;++it)
3397 selectedVisualText[*it] = true;
3400 // Deselect text after endSelection
3403 selectedVisualText[*it] = false;
3406 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3409 // Calculate the dimensions of the quads they will make the highlight mesh
3410 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3412 // At the moment there is no public API to modify the block alignment option.
3413 const bool blockAlignEnabled = true;
3415 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3417 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3419 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3420 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3422 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3423 std::vector<bool> selectedVisualText;
3424 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3425 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3426 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3428 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3429 float rowLeft = 0.0f;
3430 float rowRight = 0.0f;
3431 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3432 float maxRowLeft = std::numeric_limits<float>::max();
3433 float maxRowRight = 0.0f;
3435 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3437 // Scan through entire text.
3440 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3442 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3443 bool charSelected( false );
3444 if( selectedIt != selectedEndIt )
3446 charSelected = *selectedIt++;
3449 if(selectionState == SelectionNone)
3453 selectionState = SelectionStarted;
3454 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3455 rowRight = rowLeft + charInfo.mSize.width;
3458 else if(selectionState == SelectionStarted)
3460 // break selection on:
3461 // 1. new line causing selection break. (\n or wordwrap)
3462 // 2. character not selected.
3463 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3466 // finished selection.
3467 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3468 // that it resides on. That way this enumeration is not necessary.
3470 if(lastIt->mIsNewLineChar)
3472 // 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.
3473 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3475 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3476 maxRowLeft = std::min(maxRowLeft, min.x);
3477 maxRowRight = std::max(maxRowRight, max.x);
3478 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3479 float rowTop = rowBottom - rowSize.height;
3481 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3482 if(charSelected && blockAlignEnabled)
3484 rowRight = std::numeric_limits<float>::max();
3486 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3488 selectionState = SelectionNone;
3490 // Still selected? start a new selection
3493 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3494 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3495 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3496 selectionState = SelectionStarted;
3501 // build up highlight(s) with this selection data.
3502 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3503 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3510 // If reached end, and still on selection, then close selection.
3513 if(selectionState == SelectionStarted)
3515 // finished selection.
3517 if(lastIt->mIsNewLineChar)
3519 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3521 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3522 maxRowLeft = std::min(maxRowLeft, min.x);
3523 maxRowRight = std::max(maxRowRight, max.x);
3524 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3525 float rowTop = rowBottom - rowSize.height;
3526 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3530 // Get the top left and bottom right corners.
3531 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3532 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3533 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3535 // Clamp quads so they appear to clip to borders of the whole text.
3536 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3538 // For block-align align Further Clamp quads to max left and right extents
3539 if(blockAlignEnabled)
3541 // BlockAlign: Will adjust highlight to block:
3543 // H[ello] (top row right = max of all rows right)
3544 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3545 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3546 // [text] (bottom row left = min of all rows left)
3547 // (common in SMS messaging selection)
3549 // As opposed to the default which is tight text highlighting.
3554 // (common in regular text editors/web browser selection)
3556 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3559 // Finally clamp quads again so they don't exceed the boundry of the control.
3560 const Vector3& controlSize = GetControlSize();
3561 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3564 return mNewHighlightInfo;
3567 void TextInput::UpdateHighlight()
3569 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3571 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3573 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3574 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3575 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3576 // [BOTTOM] [ MIDDLE ]
3579 // Each quad is created as 2 triangles.
3580 // Middle is just 1 quad regardless of its size.
3594 if ( mHighlightMeshActor )
3596 // vertex and triangle buffers should always be present if MeshActor is alive.
3597 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3598 MeshData::VertexContainer vertices;
3599 Dali::MeshData::FaceIndices faceIndices;
3601 if( !newHighlightInfo.mQuadList.empty() )
3603 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3604 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3606 // vertex position defaults to (0 0 0)
3607 MeshData::Vertex vertex;
3608 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3611 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3613 // Add each quad geometry (a sub-selection) to the mesh data.
3623 QuadCoordinates& quad = *iter;
3625 vertex.x = quad.min.x;
3626 vertex.y = quad.min.y;
3627 vertices.push_back( vertex );
3630 vertex.x = quad.max.x;
3631 vertex.y = quad.min.y;
3632 vertices.push_back( vertex );
3634 // bottom-left (v+2)
3635 vertex.x = quad.min.x;
3636 vertex.y = quad.max.y;
3637 vertices.push_back( vertex );
3639 // bottom-right (v+3)
3640 vertex.x = quad.max.x;
3641 vertex.y = quad.max.y;
3642 vertices.push_back( vertex );
3644 // triangle A (3, 1, 0)
3645 faceIndices.push_back( v + 3 );
3646 faceIndices.push_back( v + 1 );
3647 faceIndices.push_back( v );
3649 // triangle B (0, 2, 3)
3650 faceIndices.push_back( v );
3651 faceIndices.push_back( v + 2 );
3652 faceIndices.push_back( v + 3 );
3654 mMeshData.SetFaceIndices( faceIndices );
3657 BoneContainer bones(0); // passed empty as bones not required
3658 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3659 mHighlightMesh.UpdateMeshData(mMeshData);
3664 void TextInput::ClearPopup()
3666 mPopUpPanel.Clear();
3669 void TextInput::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
3671 mPopUpPanel.AddOption(name, caption, icon, finalOption);
3674 void TextInput::SetPopupPosition(const Vector3& position)
3676 mPopUpPanel.Self().SetPosition( position );
3679 void TextInput::HidePopup(bool animate, bool signalFinished )
3681 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3683 mPopUpPanel.Hide( animate );
3685 if( animate && signalFinished )
3687 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3692 void TextInput::ShowPopup(bool animate)
3696 if(mHighlightMeshActor && mState == StateEdit)
3700 // When text is selected, show popup above top handle (and text), or below bottom handle.
3701 // topHandle: referring to the top most point of the handle or the top line of selection.
3702 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3704 topHandle = mSelectionHandleOneActualPosition;
3705 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3709 topHandle = mSelectionHandleTwoActualPosition;
3710 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3712 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3713 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3715 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3716 Vector3 bottomHandle;
3717 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3718 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3719 mPopUpPanel.SetAlternativeOffset(Vector2(0.0f, bottomHandle.y - topHandle.y));
3723 // When no text is selected, show popup at world position of grab handle or cursor
3724 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3725 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3726 position.y -= rowSize.height;
3727 // if can't be positioned above, then position below row.
3728 Vector2 alternativePopUpPosition( 0.0f, position.y ); // default if no grab handle
3731 alternativePopUpPosition.y = rowSize.height + ( mGrabHandle.GetCurrentSize().height * DEFAULT_GRAB_HANDLE_RELATIVE_SIZE.y ) ;
3732 // If grab handle enabled then position pop-up below the grab handle.
3734 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3737 // reposition popup above the desired cursor posiiton.
3738 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3739 textViewSize.z = 0.0f;
3740 // World position = world position of ParentOrigin of cursor (i.e. top-left corner of TextView) + cursor position;
3741 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3743 SetPopupPosition( worldPosition );
3746 mPopUpPanel.Show(animate);
3747 StartMonitoringStageForTouch();
3749 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3752 void TextInput::ShowPopupCutCopyPaste()
3755 // Check the selected text is whole text or not.
3756 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3758 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3759 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3762 if ( !mStyledText.empty() )
3764 Image cutIcon = Image::New( DEFAULT_ICON_CUT );
3765 Image copyIcon = Image::New( DEFAULT_ICON_COPY );
3766 AddPopupOption( OPTION_CUT, GET_LOCALE_TEXT("IDS_COM_BODY_CUT"), cutIcon );
3767 AddPopupOption( OPTION_COPY, GET_LOCALE_TEXT("IDS_COM_BODY_COPY"), copyIcon, true );
3770 if(mClipboard.NumberOfItems())
3772 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3773 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3774 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon );
3775 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3778 mPopUpPanel.Hide(false);
3782 void TextInput::SetUpPopUpSelection()
3786 // If no text exists then don't offer to select
3787 if ( !mStyledText.empty() )
3789 Image selectIcon = Image::New( DEFAULT_ICON_SELECT );
3790 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3791 AddPopupOption( OPTION_SELECT_WORD, GET_LOCALE_TEXT("IDS_COM_SK_SELECT"), selectIcon );
3792 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3794 // if clipboard has valid contents then offer paste option
3795 if( mClipboard.NumberOfItems() )
3797 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3798 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3799 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon, true );
3800 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3803 mPopUpPanel.Hide(false);
3806 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3811 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3812 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3813 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3814 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3816 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3818 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3820 float closestYdifference = std::numeric_limits<float>::max();
3821 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3822 std::size_t numberOfMatchedCharacters = 0;
3824 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3825 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3827 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3829 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3830 float baselinePosition = info.mPosition.y - info.mDescender;
3832 if( info.mIsVisible )
3834 // store difference between source y point and the y position of the current character
3835 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3837 if( currentYdifference < closestYdifference )
3839 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3840 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3841 closestYdifference = currentYdifference;
3842 matchedCharacters.clear();
3843 numberOfMatchedCharacters = 0; // reset count
3846 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3847 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3849 // ignore new line character.
3850 if( !info.mIsNewLineChar )
3852 matchedCharacters.push_back( info );
3853 numberOfMatchedCharacters++;
3857 } // End of loop checking each character's y position in the character layout table
3859 // Check if last character is a newline, if it is
3860 // then need pretend there is an imaginary line afterwards,
3861 // and check if user is touching below previous line.
3862 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3864 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3866 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3870 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3871 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3873 bool matched( false );
3875 // 2 Iterate through matching list of y positions and find closest matching X position.
3876 for( ; it != endIt; ++it )
3878 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3880 if( info.mIsVisible )
3882 // stop when on left side of character's center.
3883 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3884 if( sourceScrollOffset.x < characterMidPointPosition )
3886 if(info.mIsRightToLeftCharacter)
3888 rightToLeftChar = true;
3890 glyphIntersection = info.mPosition.x;
3895 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3901 rightToLeftChar = lastRightToLeftChar;
3904 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3905 closestIndex = lineOffset + matchCharacterIndex;
3907 mClosestCursorPositionEOL = false; // reset
3908 if ( it == endIt && !matched )
3910 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3913 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3914 if( rightToLeftChar && lastRightToLeftChar )
3916 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3921 // closestIndex is the visual index, need to convert it to the logical index
3922 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3924 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3926 // Checks for situations where user is touching between LTR and RTL
3927 // characters. To identify if the user means the end of a LTR string
3928 // or the beginning of an RTL string, and vice versa.
3929 if( closestIndex > 0 )
3931 if( rightToLeftChar && !lastRightToLeftChar )
3936 // A: In this touch range, the user is indicating that they wish to place
3937 // the cursor at the end of the LTR text.
3938 // B: In this touch range, the user is indicating that they wish to place
3939 // the cursor at the end of the RTL text.
3941 // Result of touching A area:
3942 // [.....LTR]|[RTL......]+
3944 // |: primary cursor (for typing LTR chars)
3945 // +: secondary cursor (for typing RTL chars)
3947 // Result of touching B area:
3948 // [.....LTR]+[RTL......]|
3950 // |: primary cursor (for typing RTL chars)
3951 // +: secondary cursor (for typing LTR chars)
3953 if( sourceScrollOffset.x < glyphIntersection )
3958 else if( !rightToLeftChar && lastRightToLeftChar )
3960 if( sourceScrollOffset.x < glyphIntersection )
3967 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3968 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3969 // one further ahead
3970 if( rightToLeftChar && !lastRightToLeftChar )
3975 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3977 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3979 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3988 float TextInput::GetLineJustificationPosition() const
3990 const Vector3& size = mDisplayedTextView.GetCurrentSize();
3991 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
3992 float alignmentOffset = 0.f;
3994 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
3995 if( alignment & Toolkit::Alignment::HorizontalLeft )
3997 alignmentOffset = 0.f;
3999 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4001 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4003 else if( alignment & Toolkit::Alignment::HorizontalRight )
4005 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4008 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4009 float justificationOffset = 0.f;
4011 switch( justification )
4013 case Toolkit::TextView::Left:
4015 justificationOffset = 0.f;
4018 case Toolkit::TextView::Center:
4020 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4023 case Toolkit::TextView::Right:
4025 justificationOffset = mTextLayoutInfo.mTextSize.width;
4028 case Toolkit::TextView::Justified:
4030 justificationOffset = 0.f;
4035 DALI_ASSERT_ALWAYS( false );
4039 return alignmentOffset + justificationOffset;
4042 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4044 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4045 A newline character is not inserted in this case */
4047 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4049 Vector3 cursorPosition;
4051 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4053 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4055 // end character so use
4056 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4057 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4061 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4064 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4066 // If previous character on a different line then use current characters position
4067 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4069 if ( mClosestCursorPositionEOL )
4071 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4075 cursorPosition = Vector3(currentCharInfo.mPosition);
4080 // Previous character is on same line so use position of previous character plus it's width.
4081 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4084 return cursorPosition;
4087 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4089 bool direction(false);
4090 Vector3 alternatePosition;
4091 bool alternatePositionValid(false);
4093 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4096 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4098 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4100 alternatePositionValid = false;
4101 directionRTL = false;
4103 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4105 std::size_t visualCharacterPosition;
4107 // When cursor is not at beginning, consider possibility of
4108 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4109 if(characterPosition > 0)
4111 // Cursor position should be the end of the last character.
4112 // If the last character is LTR, then the end is on the right side of the glyph.
4113 // If the last character is RTL, then the end is on the left side of the glyph.
4114 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4116 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4118 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4121 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4122 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4124 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4125 const Vector3& size = GetControlSize();
4127 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4129 --visualCharacterPosition;
4131 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4134 if(!info.mIsNewLineChar)
4136 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4140 // When cursor points to first character on new line, position cursor at the start of this glyph.
4141 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4143 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4144 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4145 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4147 cursorPosition.x = infoNext.mPosition.x + start;
4148 cursorPosition.y = infoNext.mPosition.y;
4152 // If cursor points to the end of text, then can only position
4153 // cursor where the new line starts based on the line-justification position.
4154 cursorPosition.x = GetLineJustificationPosition();
4156 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4158 // If this is after the last character, then we can assume that the new cursor
4159 // should be exactly one row below the current row.
4161 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4162 cursorPosition.y = info.mPosition.y + rowRect.height;
4166 // If this is not after last character, then we can use this row's height.
4167 // should be exactly one row below the current row.
4169 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4170 cursorPosition.y = info.mPosition.y + rowRect.height;
4175 directionRTL = info.mIsRightToLeftCharacter;
4177 // 1. When the cursor is neither at the beginning or the end,
4178 // we can show multiple cursors under situations when the cursor is
4179 // between RTL and LTR text...
4180 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4182 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4184 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4185 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4187 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4189 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4190 // Text: [...LTR...]|[...RTL...]
4192 // Alternate cursor pos: ^
4193 // In which case we need to display an alternate cursor for the RTL text.
4195 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4196 alternatePosition.y = infoAlt.mPosition.y;
4197 alternatePositionValid = true;
4199 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4201 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4202 // Text: |[...RTL...] [...LTR....]
4204 // Alternate cursor pos: ^
4205 // In which case we need to display an alternate cursor for the RTL text.
4207 alternatePosition.x = infoAlt.mPosition.x;
4208 alternatePosition.y = infoAlt.mPosition.y;
4209 alternatePositionValid = true;
4214 // 2. When the cursor is at the end of the text,
4215 // and we have multi-directional text,
4216 // we can also consider showing mulitple cursors.
4217 // The rule here is:
4218 // If first and last characters on row are different
4219 // Directions, then two cursors need to be displayed.
4221 // Get first logical glyph on row
4222 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4224 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4225 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4227 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4229 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4230 // Text: [...LTR...]|[...RTL...]
4232 // Alternate cursor pos: ^
4233 // In which case we need to display an alternate cursor for the RTL text, this cursor
4234 // should be at the end of the given line.
4236 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4237 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4238 alternatePosition.y = infoAlt.mPosition.y;
4239 alternatePositionValid = true;
4241 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4243 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4244 // Text: |[...RTL...] [...LTR....]
4246 // Alternate cursor pos: ^
4247 // In which case we need to display an alternate cursor for the RTL text.
4249 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4250 alternatePosition.x = infoAlt.mPosition.x;
4251 alternatePosition.y = infoAlt.mPosition.y;
4252 alternatePositionValid = true;
4255 } // characterPosition > 0
4256 else if(characterPosition == 0)
4258 // When the cursor position is at the beginning, it should be at the start of the current character.
4259 // If the current character is LTR, then the start is on the right side of the glyph.
4260 // If the current character is RTL, then the start is on the left side of the glyph.
4261 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4263 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4265 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4268 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4269 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4271 cursorPosition.x = info.mPosition.x + start;
4272 cursorPosition.y = info.mPosition.y;
4273 directionRTL = info.mIsRightToLeftCharacter;
4278 // If the character table is void, place the cursor accordingly the text alignment.
4279 const Vector3& size = GetControlSize();
4281 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4282 float alignmentOffset = 0.f;
4284 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4285 if( alignment & Toolkit::Alignment::HorizontalLeft )
4287 alignmentOffset = 0.f;
4289 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4291 alignmentOffset = 0.5f * ( size.width );
4293 else if( alignment & Toolkit::Alignment::HorizontalRight )
4295 alignmentOffset = size.width;
4298 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4299 cursorPosition.x = alignmentOffset;
4301 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4302 if( alignment & Toolkit::Alignment::VerticalTop )
4304 cursorPosition.y = mLineHeight;
4306 else if( alignment & Toolkit::Alignment::VerticalCenter )
4308 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4310 else if( alignment & Toolkit::Alignment::VerticalBottom )
4312 cursorPosition.y = size.height;
4316 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4317 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4318 if( alternatePositionValid )
4320 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4321 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4324 return cursorPosition;
4327 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4329 // scan string from current position to beginning of current line to note direction of line
4330 while(logicalPosition)
4333 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4334 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4341 return logicalPosition;
4344 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4348 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4351 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4353 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4354 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4356 min = Vector2::ZERO;
4357 max = Vector2(0.0f, mLineHeight);
4361 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4362 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4363 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4365 // If cursor is pointing to end of line, then start from last character.
4366 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4368 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4370 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4371 if( !it->mIsVisible )
4373 characterPosition = FindVisibleCharacter( Left, characterPosition );
4374 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4377 // Scan characters left and right of cursor, stopping when end of line/string reached or
4378 // y position greater than threshold of reference line.
4380 // 1. scan left until we reach the beginning or a different line.
4381 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4382 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4383 // min-x position is the left-most char's left (x)
4384 // max-x position is the right-most char's right (x)
4385 // min-y position is the minimum of all character's top (y)
4386 // max-y position is the maximum of all character's bottom (y+height)
4387 min.y = validCharIt->mPosition.y;
4388 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4393 min.y = std::min(min.y, validCharIt->mPosition.y);
4394 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4403 if( (it->mPosition.y < referenceLine) ||
4404 (it->mIsNewLineChar) ||
4411 // info refers to the first character on this line.
4412 min.x = validCharIt->mPosition.x;
4414 // 2. scan right until we reach end or a different line
4415 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4416 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4420 if( (it->mPosition.y > referenceLine) ||
4421 (it->mIsNewLineChar) ||
4428 min.y = std::min(min.y, validCharIt->mPosition.y);
4429 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4434 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4436 if ( validCharIt != end )
4438 // info refers to the last character on this line.
4439 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4442 return Size( max.x - min.x, max.y - min.y );
4445 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4447 Actor popUpPanel = mPopUpPanel.GetRootActor();
4449 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4455 Dali::Actor parent( touchedActor.GetParent() );
4459 return WasTouchedCheck( parent );
4466 void TextInput::StartMonitoringStageForTouch()
4468 Stage stage = Stage::GetCurrent();
4469 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4472 void TextInput::EndMonitoringStageForTouch()
4474 Stage stage = Stage::GetCurrent();
4475 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4478 void TextInput::OnStageTouched(const TouchEvent& event)
4480 if( event.GetPointCount() > 0 )
4482 if ( TouchPoint::Down == event.GetPoint(0).state )
4484 const Actor touchedActor(event.GetPoint(0).hitActor);
4486 bool popUpShown( false );
4488 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4493 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4495 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4497 EndMonitoringStageForTouch();
4498 HidePopup( true, false );
4501 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4503 EndMonitoringStageForTouch();
4504 ShowGrabHandleAndSetVisibility( false );
4510 void TextInput::SelectText(std::size_t start, std::size_t end)
4512 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4513 IsGrabHandleEnabled()?"true":"false",
4514 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4515 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4516 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4518 StartMonitoringStageForTouch();
4520 if ( mEditModeActive ) // Only allow text selection when in edit mode
4522 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4523 mSelectingText = true;
4525 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4527 ImfManager imfManager = ImfManager::Get();
4530 imfManager.SetCursorPosition ( mCursorPosition );
4531 imfManager.SetSurroundingText( GetText() );
4532 imfManager.NotifyCursorPosition();
4534 // 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.
4536 // Hide grab handle when selecting.
4537 ShowGrabHandleAndSetVisibility( false );
4539 if( start != end ) // something to select
4541 SetCursorVisibility( false );
4542 StopCursorBlinkTimer();
4544 CreateSelectionHandles(start, end);
4547 const TextStyle oldInputStyle( mInputStyle );
4548 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4550 if( oldInputStyle != mInputStyle )
4552 // Updates the line height accordingly with the input style.
4555 EmitStyleChangedSignal();
4561 mSelectingText = false;
4565 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4567 MarkupProcessor::StyledTextArray currentSelectedText;
4569 if ( IsTextSelected() )
4571 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4572 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4574 for(; it != end; ++it)
4576 MarkupProcessor::StyledText& styledText( *it );
4577 currentSelectedText.push_back( styledText );
4580 return currentSelectedText;
4583 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4585 const std::size_t beginIndex = std::min( begin, end );
4586 const std::size_t endIndex = std::max( begin, end );
4589 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4591 // Create a styled text array used to replace the text into the text-view.
4592 MarkupProcessor::StyledTextArray text;
4593 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4595 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4596 GetTextLayoutInfo();
4598 if( IsScrollEnabled() )
4600 // Need to set the scroll position as the text's size may have changed.
4601 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4604 ShowGrabHandleAndSetVisibility( false );
4610 // Set Handle positioning as the new style may have repositioned the characters.
4611 SetSelectionHandlePosition(HandleOne);
4612 SetSelectionHandlePosition(HandleTwo);
4615 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4617 // Just hide the grab handle when keyboard is hidden.
4618 if (!keyboardShown )
4620 ShowGrabHandleAndSetVisibility( false );
4622 // If the keyboard is not now being shown, then hide the popup panel
4623 mPopUpPanel.Hide( true );
4627 // Removes highlight and resumes edit mode state
4628 void TextInput::RemoveHighlight()
4630 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4632 if ( mHighlightMeshActor )
4634 if ( mSelectionHandleOne )
4636 mActiveLayer.Remove( mSelectionHandleOne );
4637 mSelectionHandleOne.Reset();
4638 mSelectionHandleOneOffset.x = 0.0f;
4640 if ( mSelectionHandleTwo )
4642 mActiveLayer.Remove( mSelectionHandleTwo );
4643 mSelectionHandleTwo.Reset();
4644 mSelectionHandleTwoOffset.x = 0.0f;
4647 mNewHighlightInfo.mQuadList.clear();
4649 Self().Remove( mHighlightMeshActor );
4651 SetCursorVisibility( true );
4652 StartCursorBlinkTimer();
4654 mHighlightMeshActor.Reset();
4655 // NOTE: We cannot dereference mHighlightMesh, due
4656 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4661 mSelectionHandleOnePosition = 0;
4662 mSelectionHandleTwoPosition = 0;
4665 void TextInput::CreateHighlight()
4667 if ( !mHighlightMeshActor )
4669 mMeshData = MeshData( );
4670 mMeshData.SetHasNormals( true );
4672 mCustomMaterial = Material::New("CustomMaterial");
4673 mCustomMaterial.SetDiffuseColor( LIGHTBLUE );
4675 mMeshData.SetMaterial( mCustomMaterial );
4677 mHighlightMesh = Mesh::New( mMeshData );
4679 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4680 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4681 mHighlightMeshActor.SetInheritShaderEffect( false );
4682 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4683 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4684 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4685 mHighlightMeshActor.SetAffectedByLighting(false);
4687 Self().Add(mHighlightMeshActor);
4692 bool TextInput::CopySelectedTextToClipboard()
4694 mCurrentCopySelecton.clear();
4696 mCurrentCopySelecton = GetSelectedText();
4698 std::string stringToStore;
4700 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4701 * a marked up string.
4703 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4704 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4705 bool success = mClipboard.SetItem( stringToStore );
4709 void TextInput::PasteText( const Text& text )
4711 // Update Flag, indicates whether to update the text-input contents or not.
4712 // Any key stroke that results in a visual change of the text-input should
4713 // set this flag to true.
4714 bool update = false;
4715 if( mHighlightMeshActor )
4717 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4718 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4720 ImfManager imfManager = ImfManager::Get();
4723 imfManager.SetCursorPosition( mCursorPosition );
4724 imfManager.NotifyCursorPosition();
4726 DeleteHighlightedText( true );
4730 bool textExceedsMaximunNumberOfCharacters = false;
4731 bool textExceedsBoundary = false;
4733 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4735 mCursorPosition += insertedStringLength;
4736 ImfManager imfManager = ImfManager::Get();
4739 imfManager.SetCursorPosition ( mCursorPosition );
4740 imfManager.NotifyCursorPosition();
4743 update = update || ( insertedStringLength > 0 );
4749 if( insertedStringLength < text.GetLength() )
4751 EmitMaxInputCharactersReachedSignal();
4754 if( textExceedsBoundary )
4756 EmitInputTextExceedsBoundariesSignal();
4760 void TextInput::SetTextDirection()
4762 // Put the cursor to the right if we are empty and an RTL language is being used.
4763 if ( mStyledText.empty() )
4765 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4767 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4768 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4770 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4771 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4773 int alignment( mDisplayedTextView.GetTextAlignment() &
4774 ( Toolkit::Alignment::VerticalTop |
4775 Toolkit::Alignment::VerticalCenter |
4776 Toolkit::Alignment::VerticalBottom |
4777 Toolkit::Alignment::HorizontalCenter ) );
4778 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4780 // If our alignment is in the center, then do not change.
4781 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4783 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4786 // If our justification is in the center, then do not change.
4787 if ( justification != Toolkit::TextView::Center )
4789 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4792 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4793 mDisplayedTextView.SetLineJustification( justification );
4797 void TextInput::UpdateLineHeight()
4799 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4800 mLineHeight = font.GetLineHeight();
4802 // 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.
4804 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4806 if( !mExceedEnabled || shrink )
4808 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4812 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4814 std::size_t position = 0;
4816 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4822 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4824 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4826 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4832 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4833 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4835 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4841 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4846 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4853 void TextInput::SetSortModifier( float depthOffset )
4855 if(mDisplayedTextView)
4857 mDisplayedTextView.SetSortModifier(depthOffset);
4861 void TextInput::SetSnapshotModeEnabled( bool enable )
4863 if(mDisplayedTextView)
4865 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4869 bool TextInput::IsSnapshotModeEnabled() const
4871 bool snapshotEnabled = false;
4873 if(mDisplayedTextView)
4875 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4878 return snapshotEnabled;
4881 void TextInput::SetMarkupProcessingEnabled( bool enable )
4883 mMarkUpEnabled = enable;
4886 bool TextInput::IsMarkupProcessingEnabled() const
4888 return mMarkUpEnabled;
4891 void TextInput::SetScrollEnabled( bool enable )
4893 if( mDisplayedTextView )
4895 mDisplayedTextView.SetScrollEnabled( enable );
4900 // Don't set cursor's and handle's visibility to false if they are outside the
4901 // boundaries of the text-input.
4902 mIsCursorInScrollArea = true;
4903 mIsGrabHandleInScrollArea = true;
4904 if( mSelectionHandleOne && mSelectionHandleTwo )
4906 mSelectionHandleOne.SetVisible( true );
4907 mSelectionHandleTwo.SetVisible( true );
4909 if( mHighlightMeshActor )
4911 mHighlightMeshActor.SetVisible( true );
4917 bool TextInput::IsScrollEnabled() const
4919 bool scrollEnabled = false;
4921 if( mDisplayedTextView )
4923 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4926 return scrollEnabled;
4929 void TextInput::SetScrollPosition( const Vector2& position )
4931 if( mDisplayedTextView )
4933 mDisplayedTextView.SetScrollPosition( position );
4937 Vector2 TextInput::GetScrollPosition() const
4939 Vector2 scrollPosition;
4941 if( mDisplayedTextView )
4943 scrollPosition = mDisplayedTextView.GetScrollPosition();
4946 return scrollPosition;
4949 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4951 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4952 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4953 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4955 // Add style to the new input text.
4956 MarkupProcessor::StyledTextArray textToInsert;
4957 for( std::size_t i = 0; i < insertedStringLength; ++i )
4959 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4960 textToInsert.push_back( newStyledCharacter );
4963 //Insert text to the TextView.
4964 const bool emptyTextView = mStyledText.empty();
4965 if( emptyTextView && mPlaceHolderSet )
4967 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4968 mDisplayedTextView.SetText( textToInsert );
4972 if( 0 == numberOfCharactersToReplace )
4974 mDisplayedTextView.InsertTextAt( position, textToInsert );
4978 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4981 mPlaceHolderSet = false;
4983 if( textToInsert.empty() )
4985 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4986 GetTextLayoutInfo();
4990 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4991 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4994 textExceedsBoundary = false;
4996 if( !mExceedEnabled )
4998 const Vector3& size = GetControlSize();
5000 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5002 // If new text does not fit within TextView
5003 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
5004 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
5005 GetTextLayoutInfo();
5006 textExceedsBoundary = true;
5007 insertedStringLength = 0;
5010 if( textExceedsBoundary )
5012 // Add the part of the text which fits on the text-input.
5014 // Split the text which doesn't fit in two halves.
5015 MarkupProcessor::StyledTextArray firstHalf;
5016 MarkupProcessor::StyledTextArray secondHalf;
5017 SplitText( textToInsert, firstHalf, secondHalf );
5019 // Clear text. This text will be filled with the text inserted.
5020 textToInsert.clear();
5022 // Where to insert the text.
5023 std::size_t positionToInsert = position;
5025 bool end = text.GetLength() <= 1;
5028 // Insert text and check ...
5029 const std::size_t textLength = firstHalf.size();
5030 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5031 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5033 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5035 // Inserted text doesn't fit.
5037 // Remove inserted text
5038 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5039 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5041 // The iteration finishes when only one character doesn't fit.
5042 end = textLength <= 1;
5046 // Prepare next two halves for next iteration.
5047 MarkupProcessor::StyledTextArray copyText = firstHalf;
5048 SplitText( copyText, firstHalf, secondHalf );
5055 // store text to be inserted in mStyledText.
5056 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5058 // Increase the inserted characters counter.
5059 insertedStringLength += textLength;
5061 // Prepare next two halves for next iteration.
5062 MarkupProcessor::StyledTextArray copyText = secondHalf;
5063 SplitText( copyText, firstHalf, secondHalf );
5065 // Update where next text has to be inserted
5066 positionToInsert += textLength;
5072 if( textToInsert.empty() && emptyTextView )
5074 // No character has been added and the text-view was empty.
5075 // Set the placeholder text.
5076 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5077 mPlaceHolderSet = true;
5081 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5082 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5083 mPlaceHolderSet = false;
5086 return insertedStringLength;
5089 void TextInput::GetTextLayoutInfo()
5091 if( mStyledText.empty() )
5093 // The text-input has no text, clear the text-view's layout info.
5094 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5098 if( mDisplayedTextView )
5100 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5104 // There is no text-view.
5105 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5110 void TextInput::EmitStyleChangedSignal()
5112 // emit signal if input style changes.
5113 Toolkit::TextInput handle( GetOwner() );
5114 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5117 void TextInput::EmitTextModified()
5119 // emit signal when text changes.
5120 Toolkit::TextInput handle( GetOwner() );
5121 mTextModifiedSignal.Emit( handle );
5125 void TextInput::EmitMaxInputCharactersReachedSignal()
5127 // emit signal if max characters is reached during text input.
5128 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5130 Toolkit::TextInput handle( GetOwner() );
5131 mMaxInputCharactersReachedSignalV2.Emit( handle );
5134 void TextInput::EmitInputTextExceedsBoundariesSignal()
5136 // Emit a signal when the input text exceeds the boundaries of the text input.
5138 Toolkit::TextInput handle( GetOwner() );
5139 mInputTextExceedBoundariesSignalV2.Emit( handle );
5142 } // namespace Internal
5144 } // namespace Toolkit