2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 // Licensed under the Flora License, Version 1.0 (the License);
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
8 // http://floralicense.org/license/
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an AS IS BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
17 #include <dali/dali.h>
19 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
20 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
21 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
22 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
24 #include <dali/integration-api/debug.h>
31 #define GET_LOCALE_TEXT(string) dgettext("sys_string", string)
40 #if defined(DEBUG_ENABLED)
41 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
44 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
45 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
46 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
47 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
48 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
49 const Vector4 LIGHTBLUE( 10.0f/255.0f, 140.0f/255.0f, 210.0f/255.0f, 1.0f ); // Used for Selection highlight
51 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
52 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
53 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
54 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
55 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
56 const char* DEFAULT_CURSOR( DALI_IMAGE_DIR "cursor.png" );
58 const char* DEFAULT_ICON_CLIPBOARD( DALI_IMAGE_DIR "copy_paste_icon_clipboard.png" );
59 const char* DEFAULT_ICON_COPY( DALI_IMAGE_DIR "copy_paste_icon_copy.png" );
60 const char* DEFAULT_ICON_CUT( DALI_IMAGE_DIR "copy_paste_icon_cut.png" );
61 const char* DEFAULT_ICON_PASTE( DALI_IMAGE_DIR "copy_paste_icon_paste.png" );
62 const char* DEFAULT_ICON_SELECT( DALI_IMAGE_DIR "copy_paste_icon_select.png" );
63 const char* DEFAULT_ICON_SELECT_ALL( DALI_IMAGE_DIR "copy_paste_icon_select_all.png" );
65 const Vector4 DEFAULT_CURSOR_IMAGE_9_BORDER( 2.0f, 2.0f, 2.0f, 2.0f );
67 const std::string OPTION_SELECT_WORD("select_word"); ///< "Select Word" popup option.
68 const std::string OPTION_SELECT_ALL("select_all"); ///< "Select All" popup option.
69 const std::string OPTION_CUT("cut"); ///< "Cut" popup option.
70 const std::string OPTION_COPY("copy"); ///< "Copy" popup option.
71 const std::string OPTION_PASTE("paste"); ///< "Paste" popup option.
72 const std::string OPTION_CLIPBOARD("clipboard"); ///< "Clipboard" popup option.
74 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
75 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
76 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.0f ); ///< 1. Highlight rendered (z-offset).
77 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.1f ); ///< 2. Text rendered (z-offset).
78 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
80 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
81 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
82 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
83 const float TOP_HANDLE_TOP_OFFSET(-1.5f); ///< Offset between top handle and cutCopyPaste pop-up
84 const float BOTTOM_HANDLE_BOTTOM_OFFSET(1.5f); ///< Offset between bottom handle and cutCopyPaste pop-up
85 const float CURSOR_THICKNESS(6.0f);
86 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
88 const std::string NEWLINE( "\n" );
90 const TextStyle DEFAULT_TEXT_STYLE;
92 const unsigned int SCROLL_TICK_INTERVAL = 50u;
93 const float SCROLL_THRESHOLD = 10.f;
94 const float SCROLL_SPEED = 15.f;
97 * Whether the given style is the default style or not.
98 * @param[in] style The given style.
99 * @return \e true if the given style is the default. Otherwise it returns \e false.
101 bool IsDefaultStyle( const TextStyle& style )
103 return DEFAULT_TEXT_STYLE == style;
107 * Whether the given styled text is using the default style or not.
108 * @param[in] textArray The given text.
109 * @return \e true if the given styled text is using the default style. Otherwise it returns \e false.
111 bool IsTextDefaultStyle( const Toolkit::MarkupProcessor::StyledTextArray& textArray )
113 for( Toolkit::MarkupProcessor::StyledTextArray::const_iterator it = textArray.begin(), endIt = textArray.end(); it != endIt; ++it )
115 const TextStyle& style( (*it).mStyle );
117 if( !IsDefaultStyle( style ) )
127 * Selection state enumeration (FSM)
131 SelectionNone, ///< Currently not encountered selected section.
132 SelectionStarted, ///< Encountered selected section
133 SelectionFinished ///< Finished selected section
136 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
138 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
142 if( ( *it ).mIsVisible )
144 return --cursorPosition;
153 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
155 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
157 if( ( *it ).mIsVisible )
159 return cursorPosition;
165 return cursorPosition;
169 * Whether the given position plus the cursor size offset is inside the given boundary.
171 * @param[in] position The given position.
172 * @param[in] cursorSize The cursor size.
173 * @param[in] controlSize The given boundary.
175 * @return whether the given position is inside the given boundary.
177 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
179 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
180 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
181 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
182 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
186 * Splits a text in two halves.
188 * If the text's number of characters is odd, firstHalf has one more character.
190 * @param[in] text The text to be split.
191 * @param[out] firstHalf The first half of the text.
192 * @param[out] secondHalf The second half of the text.
194 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
195 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
196 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
201 const std::size_t textLength = text.size();
202 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
204 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
205 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
208 } // end of namespace
224 return Toolkit::TextInput::New();
227 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
229 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
230 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
231 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
232 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
233 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
234 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
238 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
240 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
242 QuadCoordinates quad(x1, y1, x2, y2);
243 mQuadList.push_back( quad );
246 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
248 for(std::size_t i = 0;i < mQuadList.size(); i++)
250 QuadCoordinates& quad = mQuadList[i];
252 quad.min.Clamp(min, max);
253 quad.max.Clamp(min, max);
257 // [TextInput] ////////////////////////////////////////////////////////////////
259 Dali::Toolkit::TextInput TextInput::New()
261 // Create the implementation
262 TextInputPtr textInput(new TextInput());
263 // Pass ownership to CustomActor via derived handle
264 Dali::Toolkit::TextInput handle(*textInput);
266 textInput->Initialize();
271 TextInput::TextInput()
272 :ControlImpl( true ),
277 mDisplayedTextView(),
278 mStyledPlaceHolderText(),
279 mMaxStringLength( DEFAULT_MAX_SIZE ),
280 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
281 mCursorPosition( 0 ),
282 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
283 mIsSelectionHandleOneFlipped( false ),
284 mIsSelectionHandleTwoFlipped( false ),
285 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
286 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
287 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
288 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
289 mSelectionHandleOnePosition( 0 ),
290 mSelectionHandleTwoPosition( 0 ),
292 mPreEditStartPosition( 0 ),
293 mPreEditLength ( 0 ),
294 mNumberOfSurroundingCharactersDeleted( 0 ),
295 mTouchStartTime( 0 ),
297 mCurrentCopySelecton(),
299 mScrollDisplacement(),
300 mCurrentHandlePosition(),
301 mCurrentSelectionId(),
302 mCurrentSelectionHandlePosition(),
303 mRequestedSelection( 0, 0 ),
304 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
305 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
307 mOverrideAutomaticAlignment( false ),
308 mCursorRTLEnabled( false ),
309 mClosestCursorPositionEOL ( false ),
310 mCursorBlinkStatus( true ),
311 mCursorVisibility( false ),
312 mGrabHandleVisibility( false ),
313 mIsCursorInScrollArea( true ),
314 mIsGrabHandleInScrollArea( true ),
315 mEditModeActive( false ),
316 mEditOnTouch( true ),
317 mTextSelection( true ),
318 mExceedEnabled( true ),
319 mGrabHandleEnabled( true ),
320 mIsSelectionHandleFlipEnabled( true ),
321 mPreEditFlag( false ),
322 mIgnoreCommitFlag( false ),
323 mIgnoreFirstCommitFlag( false ),
324 mSelectingText( false ),
325 mPreserveCursorPosition( false ),
326 mSelectTextOnCommit( false ),
327 mUnderlinedPriorToPreEdit ( false ),
328 mCommitByKeyInput( false ),
329 mPlaceHolderSet( false )
331 // Updates the line height accordingly with the input style.
335 TextInput::~TextInput()
337 StopCursorBlinkTimer();
342 std::string TextInput::GetText() const
346 // Return text-view's text only if the text-input's text is not empty
347 // in order to not to return the placeholder text.
348 if( !mStyledText.empty() )
350 text = mDisplayedTextView.GetText();
356 std::string TextInput::GetMarkupText() const
358 std::string markupString;
359 MarkupProcessor::GetMarkupString( mStyledText, markupString );
364 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
366 // Get the placeholder styled text array from the markup string.
367 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText );
369 if( mStyledText.empty() )
371 // Set the placeholder text only if the styled text is empty.
372 mDisplayedTextView.SetText( mStyledPlaceHolderText );
373 mPlaceHolderSet = true;
377 std::string TextInput::GetPlaceholderText()
379 // Traverses the styled placeholder array getting only the text.
380 // Note that for some languages a 'character' could be represented by more than one 'char'
382 std::string placeholderText;
383 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
385 placeholderText.append( (*it).mText.GetText() );
388 return placeholderText ;
391 void TextInput::SetInitialText(const std::string& initialText)
393 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
395 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
397 mPreEditFlag = false;
398 mIgnoreCommitFlag = true;
401 SetText( initialText );
402 PreEditReset( false ); // Reset keyboard as text changed
405 void TextInput::SetText(const std::string& initialText)
407 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
409 GetStyledTextArray( initialText, mStyledText );
411 if( mStyledText.empty() )
413 // If the initial text is empty, set the placeholder text.
414 mDisplayedTextView.SetText( mStyledPlaceHolderText );
415 mPlaceHolderSet = true;
419 mDisplayedTextView.SetText( mStyledText );
420 mPlaceHolderSet = false;
425 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
427 ImfManager imfManager = ImfManager::Get();
430 imfManager.SetCursorPosition( mCursorPosition );
431 imfManager.SetSurroundingText( initialText );
432 imfManager.NotifyCursorPosition();
435 if( IsScrollEnabled() )
437 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
440 ShowGrabHandleAndSetVisibility( false );
447 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
449 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
451 mDisplayedTextView.SetText( styleText );
452 mPlaceHolderSet = false;
454 // If text alignment hasn't been manually set by application developer, then we
455 // automatically determine the alignment based on the content of the text i.e. what
456 // language the text begins with.
457 // TODO: This should determine different alignments for each line (broken by '\n') of text.
458 if(!mOverrideAutomaticAlignment)
460 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
461 bool leftToRight(true);
463 if( !styleText.empty() )
465 bool breakOut(false);
467 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
469 const Text& text = textIter->mText;
471 for( std::size_t i = 0; i < text.GetLength(); ++i )
473 Character character( text[i] );
474 if( character.GetCharacterDirection() != Character::Neutral )
476 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
484 // Based on this direction, either left or right align text if not manually set by application developer.
485 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
486 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
487 Toolkit::Alignment::VerticalTop ) );
488 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
492 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
494 mMaxStringLength = maxChars;
497 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
499 DALI_ASSERT_DEBUG( maxLines > 0 )
503 mNumberOflinesLimit = maxLines;
507 std::size_t TextInput::GetNumberOfLinesLimit() const
509 return mNumberOflinesLimit;
512 std::size_t TextInput::GetNumberOfCharacters() const
514 return mStyledText.size();
517 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
519 return mInputStartedSignalV2;
522 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
524 return mInputFinishedSignalV2;
527 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
529 return mCutAndPasteToolBarDisplayedV2;
532 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
534 return mStyleChangedSignalV2;
537 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
539 return mMaxInputCharactersReachedSignalV2;
542 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
544 return mInputTextExceedBoundariesSignalV2;
547 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
549 Dali::BaseHandle handle( object );
551 bool connected( true );
552 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
554 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
556 textInput.InputStartedSignal().Connect( tracker, functor );
558 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
560 textInput.InputFinishedSignal().Connect( tracker, functor );
562 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
564 textInput.StyleChangedSignal().Connect( tracker, functor );
566 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
568 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
570 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
572 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
576 // signalName does not match any signal
583 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
587 // update line height before calculate the actual position.
592 if( setCursorOnTouchPoint )
594 // Sets the cursor position for the given touch point.
595 ReturnClosestIndex( touchPoint, mCursorPosition );
597 // Creates the grab handle.
598 if( IsGrabHandleEnabled() )
600 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
604 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
605 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
606 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
607 ShowGrabHandleAndSetVisibility( true );
609 // Scrolls the text-view if needed.
610 if( IsScrollEnabled() )
612 ScrollTextViewToMakeCursorVisible( cursorPosition );
618 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
630 bool TextInput::IsEditable() const
632 return mEditModeActive;
635 void TextInput::SetEditOnTouch( bool editOnTouch )
637 mEditOnTouch = editOnTouch;
640 bool TextInput::IsEditOnTouch() const
645 void TextInput::SetTextSelectable( bool textSelectable )
647 mTextSelection = textSelectable;
650 bool TextInput::IsTextSelectable() const
652 return mTextSelection;
655 bool TextInput::IsTextSelected() const
657 return mHighlightMeshActor;
660 void TextInput::DeSelectText()
667 void TextInput::SetGrabHandleImage(Dali::Image image )
671 CreateGrabHandle(image);
675 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
677 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
681 mCursor.SetImage( image );
682 mCursor.SetNinePatchBorder( border );
686 Vector3 TextInput::GetSelectionHandleSize()
688 return DEFAULT_SELECTION_HANDLE_SIZE;
691 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
693 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
697 mCursorRTL.SetImage( image);
698 mCursorRTL.SetNinePatchBorder( border );
702 void TextInput::EnableGrabHandle(bool toggle)
704 // enables grab handle with will in turn de-activate magnifier
705 mGrabHandleEnabled = toggle;
708 bool TextInput::IsGrabHandleEnabled()
710 // if false then magnifier will be shown instead.
711 return mGrabHandleEnabled;
714 void TextInput::EnableSelectionHandleFlip( bool toggle )
716 // Deprecated function. To be removed.
717 mIsSelectionHandleFlipEnabled = toggle;
720 bool TextInput::IsSelectionHandleFlipEnabled()
722 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
726 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
728 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
729 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
730 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
732 mSelectionHandleFlipMargin = margin;
735 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
737 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
738 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
740 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
741 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
743 const Vector4 boundary( originX,
745 originX + boundingRectangle.width,
746 originY + boundingRectangle.height );
748 mBoundingRectangleWorldCoordinates = boundary;
751 const Rect<float> TextInput::GetBoundingRectangle() const
753 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
755 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
756 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
758 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
763 const Vector4& TextInput::GetSelectionHandleFlipMargin()
765 return mSelectionHandleFlipMargin;
768 void TextInput::SetTextColor( const Vector4& color )
770 mDisplayedTextView.SetColor( color );
773 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
775 if( style != mInputStyle )
778 bool emitSignal = false;
780 // mask: modify style according to mask, if different emit signal.
781 const TextStyle oldInputStyle( mInputStyle );
783 // Copy the new style.
784 mInputStyle.Copy( style, mask );
786 // if style has changed, emit signal.
787 if( oldInputStyle != mInputStyle )
792 // Updates the line height accordingly with the input style.
795 // Changing font point size will require the cursor to be re-sized
800 EmitStyleChangedSignal();
805 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
807 if ( IsTextSelected() )
809 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
810 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
812 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
814 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
817 // Keeps the old style to be compared with the new one.
818 const TextStyle oldInputStyle( mInputStyle );
820 // Copy only those parameters from the style which are set in the mask.
821 mInputStyle.Copy( style, mask );
823 if( mInputStyle != oldInputStyle )
825 // Updates the line height accordingly with the input style.
828 EmitStyleChangedSignal();
833 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
835 if( !mStyledText.empty() )
837 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
841 TextStyle TextInput::GetStyleAtCursor() const
845 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
847 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
849 style = mStyledText.at( mCursorPosition-1 ).mStyle;
855 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
857 Dali::Font defaultFont = Dali::Font::New();
858 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
865 TextStyle TextInput::GetStyleAt( std::size_t position ) const
867 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
869 if( position >= mStyledText.size() )
871 position = mStyledText.size() - 1;
874 return mStyledText.at( position ).mStyle;
877 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
879 mDisplayedTextView.SetTextAlignment( align );
880 mOverrideAutomaticAlignment = true;
883 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
885 mDisplayedTextView.SetLineJustification( justification );
886 mOverrideAutomaticAlignment = true;
889 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
891 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
894 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
896 return mDisplayedTextView.GetFadeBoundary();
899 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
901 return mDisplayedTextView.GetTextAlignment();
904 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
906 mDisplayedTextView.SetMultilinePolicy( policy );
909 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
911 return mDisplayedTextView.GetMultilinePolicy();
914 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
916 mDisplayedTextView.SetWidthExceedPolicy( policy );
919 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
921 return mDisplayedTextView.GetWidthExceedPolicy();
924 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
926 mDisplayedTextView.SetHeightExceedPolicy( policy );
929 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
931 return mDisplayedTextView.GetHeightExceedPolicy();
934 void TextInput::SetExceedEnabled( bool enable )
936 mExceedEnabled = enable;
939 bool TextInput::GetExceedEnabled() const
941 return mExceedEnabled;
944 void TextInput::SetBackground(Dali::Image image )
946 // TODO Should add this function and add public api to match.
949 bool TextInput::OnTouchEvent(const TouchEvent& event)
954 bool TextInput::OnKeyEvent(const KeyEvent& event)
956 switch( event.state )
960 return OnKeyDownEvent(event);
966 return OnKeyUpEvent(event);
978 void TextInput::OnKeyInputFocusGained()
980 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
982 mEditModeActive = true;
984 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
986 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
988 // Updates the line height accordingly with the input style.
991 // Connect the signals to use in text input.
992 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
993 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
995 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1001 SetCursorVisibility( true );
1002 StartCursorBlinkTimer();
1004 Toolkit::TextInput handle( GetOwner() );
1005 mInputStartedSignalV2.Emit( handle );
1007 ImfManager imfManager = ImfManager::Get();
1011 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1013 // Notify that the text editing start.
1014 imfManager.Activate();
1016 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1017 imfManager.SetRestoreAferFocusLost( true );
1019 imfManager.SetCursorPosition( mCursorPosition );
1020 imfManager.NotifyCursorPosition();
1023 mClipboard = Clipboard::Get(); // Store handle to clipboard
1025 // Now in edit mode we can accept string to paste from clipboard
1026 if( Adaptor::IsAvailable() )
1028 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1031 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1036 void TextInput::OnKeyInputFocusLost()
1038 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1042 // If key input focus is lost, it removes the
1043 // underline from the last pre-edit text.
1044 RemovePreEditStyle();
1045 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1046 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1049 ImfManager imfManager = ImfManager::Get();
1052 // The text editing is finished. Therefore the imf manager don't have restore activation.
1053 imfManager.SetRestoreAferFocusLost( false );
1055 // Notify that the text editing finish.
1056 imfManager.Deactivate();
1058 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1060 // Disconnect signal used the text input.
1061 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1063 Toolkit::TextInput handle( GetOwner() );
1064 mInputFinishedSignalV2.Emit( handle );
1065 mEditModeActive = false;
1066 mPreEditFlag = false;
1068 SetCursorVisibility( false );
1069 StopCursorBlinkTimer();
1071 ShowGrabHandleAndSetVisibility( false );
1074 // No longer in edit mode so do not want to receive string from clipboard
1075 if( Adaptor::IsAvailable() )
1077 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1080 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1082 Clipboard clipboard = Clipboard::Get();
1086 clipboard.HideClipboard();
1091 void TextInput::OnControlStageConnection()
1093 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1095 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1097 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1101 void TextInput::CreateActiveLayer()
1103 Actor self = Self();
1104 mActiveLayer = Layer::New();
1106 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1107 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1108 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1110 self.Add( mActiveLayer );
1111 mActiveLayer.RaiseToTop();
1114 void TextInput::OnInitialize()
1116 CreateTextViewActor();
1120 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1121 // different positions depending on language)
1122 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1123 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1124 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1126 Actor self = Self();
1127 self.Add( mCursor );
1128 self.Add( mCursorRTL );
1130 mCursorVisibility = false;
1132 CreateActiveLayer(); // todo move this so layer only created when needed.
1134 // Assign names to image actors
1135 mCursor.SetName("mainCursor");
1136 mCursorRTL.SetName("rtlCursor");
1139 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1141 mDisplayedTextView.SetSize( targetSize );
1142 GetTextLayoutInfo();
1143 mActiveLayer.SetSize(targetSize);
1146 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1148 Relayout( mDisplayedTextView, size, container );
1149 GetTextLayoutInfo();
1154 Vector3 TextInput::GetNaturalSize()
1156 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1158 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1160 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1161 naturalSize.height = mLineHeight;
1167 float TextInput::GetHeightForWidth( float width )
1169 float height = mDisplayedTextView.GetHeightForWidth( width );
1171 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1173 // If the height is zero, it means there is no text. Let's return the cursor height.
1174 height = mLineHeight;
1180 /*end of Virtual methods from parent*/
1182 // Private Internal methods
1184 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1186 switch (gesture.state)
1188 case Gesture::Started:
1189 // fall through so code not duplicated
1190 case Gesture::Continuing:
1192 if (actor == mGrabArea)
1194 SetCursorVisibility( true );
1195 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1196 MoveGrabHandle( gesture.displacement );
1197 HidePopup(); // Do not show popup whilst handle is moving
1199 else if (actor == mHandleOneGrabArea)
1201 // the displacement in PanGesture is affected by the actor's rotation.
1202 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1203 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1205 MoveSelectionHandle( HandleOne, gesture.displacement );
1207 mState = StateDraggingHandle;
1210 else if (actor == mHandleTwoGrabArea)
1212 // the displacement in PanGesture is affected by the actor's rotation.
1213 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1214 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1216 MoveSelectionHandle( HandleTwo, gesture.displacement );
1218 mState = StateDraggingHandle;
1224 case Gesture::Finished:
1226 // Revert back to non-pressed selection handle images
1227 if (actor == mGrabArea)
1229 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1230 SetCursorVisibility( true );
1231 SetUpPopUpSelection();
1234 if (actor == mHandleOneGrabArea)
1236 // the displacement in PanGesture is affected by the actor's rotation.
1237 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1238 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1240 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1242 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1244 ShowPopupCutCopyPaste();
1246 if (actor == mHandleTwoGrabArea)
1248 // the displacement in PanGesture is affected by the actor's rotation.
1249 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1250 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1252 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1254 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1256 ShowPopupCutCopyPaste();
1265 // Stop the flashing animation so easy to see when moved.
1266 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1268 if (touch.GetPoint(0).state == TouchPoint::Down)
1270 SetCursorVisibility( true );
1271 StopCursorBlinkTimer();
1273 else if (touch.GetPoint(0).state == TouchPoint::Up)
1275 SetCursorVisibility( true );
1276 StartCursorBlinkTimer();
1281 // selection handle one
1282 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1284 if (touch.GetPoint(0).state == TouchPoint::Down)
1286 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1288 else if (touch.GetPoint(0).state == TouchPoint::Up)
1290 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1295 // selection handle two
1296 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1298 if (touch.GetPoint(0).state == TouchPoint::Down)
1300 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1302 else if (touch.GetPoint(0).state == TouchPoint::Up)
1304 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1309 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1311 // If text exists then select nearest word.
1312 if ( !mStyledText.empty())
1316 ShowGrabHandleAndSetVisibility( false );
1321 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1322 // converts the pre-edit word being displayed to a committed word.
1323 if ( !mUnderlinedPriorToPreEdit )
1326 style.SetUnderline( false );
1327 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1329 mPreEditFlag = false;
1330 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1331 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1332 PreEditReset( false );
1334 mCursorPosition = 0;
1336 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1337 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1339 ImfManager imfManager = ImfManager::Get();
1342 imfManager.SetCursorPosition ( mCursorPosition );
1343 imfManager.NotifyCursorPosition();
1346 std::size_t start = 0;
1347 std::size_t end = 0;
1348 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1350 SelectText( start, end );
1352 // if no text but clipboard has content then show paste option
1353 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1355 ShowPopupCutCopyPaste();
1358 // If no text and clipboard empty then do nothing
1361 // TODO: Change the function name to be more general.
1362 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1364 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1365 , (mEditOnTouch)?"true":"false"
1366 , (mEditModeActive)?"true":"false");
1368 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1373 if( mGrabArea == actor )
1375 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1377 SetUpPopUpSelection();
1387 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1389 // Initially don't create the grab handle.
1390 bool createGrabHandle = false;
1392 if ( !mEditModeActive )
1394 // update line height before calculate the actual position.
1397 // Only start edit mode if TextInput configured to edit on touch
1400 // Set the initial cursor position in the tap point.
1401 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1403 // Create the grab handle.
1404 // TODO Make this a re-usable function.
1405 if ( IsGrabHandleEnabled() )
1407 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1411 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1412 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1413 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1414 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1418 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1419 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1420 // otherwise the Grab handle will be shown when selecting.
1427 // Show the keyboard if it was hidden.
1428 if (!VirtualKeyboard::IsVisible())
1430 VirtualKeyboard::Show();
1433 // Reset keyboard as tap event has occurred.
1434 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1435 PreEditReset( true );
1437 GetTextLayoutInfo();
1439 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1441 // 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.
1443 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1445 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1447 // Notify keyboard so it can 're-capture' word for predictive text.
1448 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1449 ImfManager imfManager = ImfManager::Get();
1452 imfManager.SetCursorPosition ( mCursorPosition );
1453 imfManager.NotifyCursorPosition();
1455 const TextStyle oldInputStyle( mInputStyle );
1457 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1461 // Create the grab handle.
1462 // Grab handle is created later.
1463 createGrabHandle = true;
1465 if( oldInputStyle != mInputStyle )
1467 // Updates the line height accordingly with the input style.
1470 EmitStyleChangedSignal();
1475 if ( createGrabHandle && IsGrabHandleEnabled() )
1477 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1481 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1482 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1483 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1484 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1489 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1491 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1493 if(longPress.state == Dali::Gesture::Started)
1495 // Start edit mode on long press
1496 if ( !mEditModeActive )
1501 // If text exists then select nearest word.
1502 if ( !mStyledText.empty())
1506 ShowGrabHandleAndSetVisibility( false );
1511 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1512 // converts the pre-edit word being displayed to a committed word.
1513 if ( !mUnderlinedPriorToPreEdit )
1516 style.SetUnderline( false );
1517 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1519 mPreEditFlag = false;
1520 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1521 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1522 PreEditReset( false );
1524 mCursorPosition = 0;
1526 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1527 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1529 ImfManager imfManager = ImfManager::Get();
1532 imfManager.SetCursorPosition ( mCursorPosition );
1533 imfManager.NotifyCursorPosition();
1535 std::size_t start = 0;
1536 std::size_t end = 0;
1537 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1539 SelectText( start, end );
1542 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1543 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1545 ShowPopupCutCopyPaste();
1550 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1552 const Text clipboardText( notifier.GetContent() );
1553 PasteText( clipboardText );
1555 SetCursorVisibility( true );
1556 StartCursorBlinkTimer();
1558 ShowGrabHandleAndSetVisibility( false );
1564 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1566 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1568 const std::string& name = button.GetName();
1570 if(name == OPTION_SELECT_WORD)
1572 std::size_t start = 0;
1573 std::size_t end = 0;
1574 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1576 SelectText( start, end );
1578 else if(name == OPTION_SELECT_ALL)
1580 SetCursorVisibility(false);
1581 StopCursorBlinkTimer();
1583 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1584 std::size_t start = 0;
1586 SelectText( start, end );
1588 else if(name == OPTION_CUT)
1590 bool ret = CopySelectedTextToClipboard();
1594 DeleteHighlightedText( true );
1598 SetCursorVisibility( true );
1599 StartCursorBlinkTimer();
1603 else if(name == OPTION_COPY)
1605 CopySelectedTextToClipboard();
1609 SetCursorVisibility( true );
1610 StartCursorBlinkTimer();
1614 else if(name == OPTION_PASTE)
1616 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1618 PasteText(retrievedString);
1620 SetCursorVisibility( true );
1621 StartCursorBlinkTimer();
1623 ShowGrabHandleAndSetVisibility( false );
1628 else if(name == OPTION_CLIPBOARD)
1630 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1631 // Hence pass the false parameter for signalFinished.
1632 HidePopup( true, false );
1633 mClipboard.ShowClipboard();
1639 bool TextInput::OnCursorBlinkTimerTick()
1642 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1643 if ( mCursorRTLEnabled )
1645 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1647 mCursorBlinkStatus = !mCursorBlinkStatus;
1652 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1654 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1656 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1657 if(mHighlightMeshActor && mState == StateEdit)
1659 ShowPopupCutCopyPaste();
1663 //FIXME this routine needs to be re-written as it contains too many branches.
1664 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1666 std::string keyName = event.keyPressedName;
1667 std::string keyString = event.keyPressed;
1669 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1671 // Do not consume "Tab" and "Escape" keys.
1672 if(keyName == "Tab" || keyName == "Escape")
1674 // Escape key to end the edit mode
1680 HidePopup(); // If Pop-up shown then hides it as editing text.
1682 // Update Flag, indicates whether to update the text-input contents or not.
1683 // Any key stroke that results in a visual change of the text-input should
1684 // set this flag to true.
1687 // Whether to scroll text to cursor position.
1688 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1689 bool scroll = false;
1691 if (keyName == "Return")
1693 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1695 bool preEditFlagPreviouslySet( mPreEditFlag );
1697 if (mHighlightMeshActor)
1699 // replaces highlighted text with new line
1700 DeleteHighlightedText( false );
1702 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1704 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1705 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1708 mCommitByKeyInput = true;
1711 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1712 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1714 mPreEditFlag = true;
1715 mIgnoreCommitFlag = false;
1725 else if ( keyName == "space" )
1727 if ( mHighlightMeshActor )
1729 // Some text is selected so erase it before adding space.
1730 DeleteHighlightedText( true );
1734 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1736 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1737 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1740 mCommitByKeyInput = true;
1745 else if (keyName == "BackSpace")
1747 if ( mHighlightMeshActor )
1749 // Some text is selected so erase it
1750 DeleteHighlightedText( true );
1755 if ( mCursorPosition > 0 )
1757 DeleteCharacter( mCursorPosition );
1762 else if (keyName == "Right")
1767 else if (keyName == "Left")
1769 AdvanceCursor(true);
1772 else // event is a character
1774 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1775 if ( !keyString.empty() )
1777 if ( mHighlightMeshActor )
1779 // replaces highlighted text with new character
1780 DeleteHighlightedText( false );
1784 // Received key String
1785 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1790 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1791 // as this is a costly operation.
1797 if(update || scroll)
1799 if( IsScrollEnabled() )
1801 // Calculates the new cursor position (in actor coordinates)
1802 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1804 ScrollTextViewToMakeCursorVisible( cursorPosition );
1811 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1813 std::string keyName = event.keyPressedName;
1814 std::string keyString = event.keyPressed;
1816 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1818 // The selected text become deselected when the key code is DALI_KEY_BACK.
1819 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1828 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1830 // Updates the stored scroll position.
1831 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1833 const Vector3& controlSize = GetControlSize();
1834 Size cursorSize( CURSOR_THICKNESS, 0.f );
1836 // Updates the cursor and grab handle position and visibility.
1837 if( mGrabHandle || mCursor )
1839 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1840 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1842 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1844 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1848 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1849 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1854 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1855 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1859 // Updates the selection handles and highlighted text position and visibility.
1860 if( mSelectionHandleOne && mSelectionHandleTwo )
1862 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1863 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1864 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1865 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1866 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1867 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1869 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1870 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1872 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1873 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1874 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1875 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1877 if( mHighlightMeshActor )
1879 mHighlightMeshActor.SetVisible( true );
1885 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1887 // Scroll the text to make the cursor visible.
1888 const Size cursorSize( CURSOR_THICKNESS,
1889 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1891 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1893 const Vector3& controlSize = GetControlSize();
1895 // Calculates the new scroll position.
1896 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1897 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1899 scrollOffset.x += cursorPosition.x;
1902 if( cursorPosition.y - cursorSize.height < 0.f )
1904 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1906 else if( cursorPosition.y > controlSize.height )
1908 scrollOffset.y += cursorPosition.y;
1911 // Sets the new scroll position.
1912 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1913 SetScrollPosition( scrollOffset );
1916 void TextInput::StartScrollTimer()
1920 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1921 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1924 if( !mScrollTimer.IsRunning() )
1926 mScrollTimer.Start();
1930 void TextInput::StopScrollTimer()
1934 mScrollTimer.Stop();
1938 bool TextInput::OnScrollTimerTick()
1940 // TODO: need to set the new style accordingly the new handle position.
1942 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1944 // nothing to do if all handles are invisible or doesn't exist.
1950 // Choose between the grab handle or the selection handles.
1951 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1952 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1953 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1955 std::size_t newCursorPosition = 0;
1956 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1958 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1959 // the new selection handle's position needs to be different of the other one.
1960 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1961 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1962 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1964 if( differentSelectionHandles )
1966 handlePosition = newCursorPosition;
1968 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1970 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1972 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1973 scrollPosition += scrollDelta;
1974 SetScrollPosition( scrollPosition );
1976 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1981 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1984 actualHandlePosition.x += mScrollDisplacement.x;
1985 actualHandlePosition.y += mScrollDisplacement.y;
1990 // Public Internal Methods (public for testing purpose)
1992 void TextInput::SetUpTouchEvents()
1994 if ( !mTapDetector )
1996 mTapDetector = TapGestureDetector::New();
1997 // Attach the actors and connect the signal
1998 mTapDetector.Attach(Self());
2000 // As contains children which may register for tap the default control detector is not used.
2001 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2004 if ( !mDoubleTapDetector )
2006 mDoubleTapDetector = TapGestureDetector::New();
2007 mDoubleTapDetector.SetTapsRequired( 2 );
2008 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2010 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2011 // so that we do not, unnecessarily, have a double tap request all the time
2014 if ( !mPanGestureDetector )
2016 mPanGestureDetector = PanGestureDetector::New();
2017 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2020 if ( !mLongPressDetector )
2022 mLongPressDetector = LongPressGestureDetector::New();
2023 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2024 mLongPressDetector.Attach(Self());
2028 void TextInput::CreateTextViewActor()
2030 mDisplayedTextView = Toolkit::TextView::New();
2031 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2032 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2033 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2034 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2035 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2036 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2037 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2038 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2039 mDisplayedTextView.SetSizePolicy( Control::Fixed, Control::Fixed );
2041 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2043 Self().Add( mDisplayedTextView );
2046 // Start a timer to initiate, used by the cursor to blink.
2047 void TextInput::StartCursorBlinkTimer()
2049 if ( !mCursorBlinkTimer )
2051 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2052 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2055 if ( !mCursorBlinkTimer.IsRunning() )
2057 mCursorBlinkTimer.Start();
2061 // Start a timer to initiate, used by the cursor to blink.
2062 void TextInput::StopCursorBlinkTimer()
2064 if ( mCursorBlinkTimer )
2066 mCursorBlinkTimer.Stop();
2070 void TextInput::StartEditMode()
2072 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2074 if(!mEditModeActive)
2079 if ( mDoubleTapDetector )
2081 mDoubleTapDetector.Attach( Self() );
2085 void TextInput::EndEditMode()
2087 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2089 ClearKeyInputFocus();
2091 if ( mDoubleTapDetector )
2093 mDoubleTapDetector.Detach( Self() );
2097 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2099 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2101 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2103 style.SetUnderline( true );
2104 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2108 void TextInput::RemovePreEditStyle()
2110 if ( !mUnderlinedPriorToPreEdit )
2113 style.SetUnderline( false );
2114 SetActiveStyle( style, TextStyle::UNDERLINE );
2118 // IMF related methods
2121 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2123 bool update( false );
2124 bool preeditResetRequired ( false );
2126 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2128 HidePopup(); // If Pop-up shown then hides it as editing text.
2131 switch ( imfEvent.eventName )
2133 case ImfManager::PREEDIT:
2135 mIgnoreFirstCommitFlag = false;
2137 // 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
2138 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2140 // replaces highlighted text with new character
2141 DeleteHighlightedText( false );
2144 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2146 if( IsScrollEnabled() )
2148 // Calculates the new cursor position (in actor coordinates)
2149 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2150 ScrollTextViewToMakeCursorVisible( cursorPosition );
2157 case ImfManager::COMMIT:
2159 if( mIgnoreFirstCommitFlag )
2161 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2162 mIgnoreFirstCommitFlag = false;
2166 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2168 // 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
2169 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2171 // replaces highlighted text with new character
2172 DeleteHighlightedText( false );
2175 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2176 // not needed, one such scenario is when the pre-edit word is too long to fit.
2177 if ( !mIgnoreCommitFlag )
2179 update = CommitReceived( imfEvent.predictiveString );
2183 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2189 if( IsScrollEnabled() )
2191 // Calculates the new cursor position (in actor coordinates)
2192 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2194 ScrollTextViewToMakeCursorVisible( cursorPosition );
2199 case ImfManager::DELETESURROUNDING:
2201 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2202 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2204 mPreEditFlag = false;
2206 std::size_t toDelete = 0;
2207 std::size_t numberOfCharacters = 0;
2209 if( mHighlightMeshActor )
2211 // delete highlighted text.
2212 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2213 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2217 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2219 toDelete = mCursorPosition + imfEvent.cursorOffset;
2221 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2223 numberOfCharacters = mStyledText.size() - toDelete;
2227 numberOfCharacters = imfEvent.numberOfChars;
2230 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2231 DeleteRange( toDelete, numberOfCharacters );
2233 mCursorPosition = toDelete;
2234 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2236 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2239 case ImfManager::GETSURROUNDING:
2241 // 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
2242 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2243 if (! ( mHighlightMeshActor || mSelectingText ) )
2245 std::string text( GetText() );
2246 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2248 imfManager.SetCursorPosition( mCursorPosition );
2249 imfManager.SetSurroundingText( text );
2252 if( 0 != mNumberOfSurroundingCharactersDeleted )
2254 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2255 mNumberOfSurroundingCharactersDeleted = 0;
2257 if( mStyledText.empty() )
2259 // Styled text is empty, so set the placeholder text.
2260 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2261 mPlaceHolderSet = true;
2266 case ImfManager::VOID:
2268 DALI_ASSERT_DEBUG( false );
2272 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2274 return callbackData;
2277 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2279 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2281 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2282 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2284 bool preeditResetRequest ( false );
2286 if( mPreEditFlag ) // Already in pre-edit state.
2288 if( mStyledText.size() >= mMaxStringLength )
2290 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2291 // Cannot fit these characters into field, clear pre-edit.
2292 if ( !mUnderlinedPriorToPreEdit )
2295 style.SetUnderline( false );
2296 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2298 mIgnoreCommitFlag = true;
2299 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2300 mPreEditFlag = false;
2301 EmitMaxInputCharactersReachedSignal();
2305 // delete existing pre-edit string
2306 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2308 // Store new pre-edit string
2309 mPreEditString.SetText( keyString );
2311 if ( keyString.empty() )
2313 mPreEditFlag = false;
2314 mCursorPosition = mPreEditStartPosition;
2316 if( mStyledText.empty() )
2318 // Styled text is empty, so set the placeholder text.
2319 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2320 mPlaceHolderSet = true;
2324 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2326 GetTextLayoutInfo();
2330 // Insert new pre-edit string. InsertAt updates the size and position table.
2331 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2332 // 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.
2333 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2334 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2335 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2337 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2341 else // mPreEditFlag not set
2343 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2345 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2346 // new pre-edit so move into pre-edit state by setting flag
2347 mPreEditFlag = true;
2348 mPreEditString.SetText( keyString ); // store new pre-edit string
2349 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2350 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2351 // 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.
2352 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2353 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2354 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2356 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2361 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2365 return preeditResetRequest;
2368 bool TextInput::CommitReceived(const std::string& keyString )
2370 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2371 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2373 bool update( false );
2375 RemovePreEditStyle();
2377 const std::size_t styledTextSize( mStyledText.size() );
2378 if( styledTextSize >= mMaxStringLength )
2380 // Cannot fit these characters into field, clear pre-edit.
2383 mIgnoreCommitFlag = true;
2384 mPreEditFlag = false;
2386 EmitMaxInputCharactersReachedSignal();
2392 // delete existing pre-edit string
2393 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2394 mPreEditFlag = false;
2396 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2397 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2399 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2401 // No need to update cursor position as Cursor location given by touch.
2402 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2403 mPreserveCursorPosition = false;
2407 // Cursor not set by touch so needs to be re-positioned to input more text
2408 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2410 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2411 if ( mCommitByKeyInput )
2413 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2414 mCommitByKeyInput = false;
2418 if ( mSelectTextOnCommit )
2420 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2425 else // mPreEditFlag not set
2427 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2429 if( mStyledText.empty() && mPlaceHolderSet )
2431 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2432 mDisplayedTextView.SetText( "" );
2433 mNumberOfSurroundingCharactersDeleted = 0;
2434 mPlaceHolderSet = false;
2436 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2438 mNumberOfSurroundingCharactersDeleted = 0;
2442 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2447 mSelectTextOnCommit = false;
2449 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2450 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2455 // End of IMF related methods
2457 std::size_t TextInput::DeletePreEdit()
2459 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2461 DALI_ASSERT_DEBUG( mPreEditFlag );
2463 const std::size_t preEditStringLength = mPreEditString.GetLength();
2464 const std::size_t styledTextSize = mStyledText.size();
2466 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2468 // Prevents erase items outside mStyledText bounds.
2469 if( mPreEditStartPosition > styledTextSize )
2471 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2472 mPreEditStartPosition = styledTextSize;
2475 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2477 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2478 endPosition = styledTextSize;
2481 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2483 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2484 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2486 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2488 return preEditStringLength;
2491 void TextInput::PreEditReset( bool preserveCursorPosition )
2493 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2494 preserveCursorPosition, mCursorPosition);
2496 // 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.
2497 mPreserveCursorPosition = preserveCursorPosition;
2499 // Reset incase we are in a pre-edit state.
2500 ImfManager imfManager = ImfManager::Get();
2503 imfManager.Reset(); // Will trigger a commit message
2507 void TextInput::CursorUpdate()
2511 ImfManager imfManager = ImfManager::Get();
2514 std::string text( GetText() );
2515 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2516 imfManager.SetCursorPosition ( mCursorPosition );
2517 imfManager.NotifyCursorPosition();
2521 /* Delete highlighted characters redisplay*/
2522 void TextInput::DeleteHighlightedText( bool inheritStyle )
2524 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2526 if(mHighlightMeshActor)
2528 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2530 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2531 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2533 // Get the styled text of the characters to be deleted as it may be needed if
2534 // the "exceed the text-input's boundaries" option is disabled.
2535 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2537 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2539 mStyledText.erase( start, end ); // erase range of characters
2541 // Remove text from TextView.
2543 if( mStyledText.empty() )
2545 // Styled text is empty, so set the placeholder text.
2546 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2547 mPlaceHolderSet = true;
2551 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2553 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2555 // It may happen than after removing a white space or a new line character,
2556 // two words merge, this new word could be big enough to not fit in its
2557 // current line, so moved to the next one, and make some part of the text to
2558 // exceed the text-input's boundary.
2559 if( !mExceedEnabled )
2561 // Get the new text layout after removing some characters.
2562 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2564 // Get text-input's size.
2565 const Vector3& size = GetControlSize();
2567 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2568 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2570 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2572 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2573 styledCharactersToDelete.begin(),
2574 styledCharactersToDelete.end() );
2578 GetTextLayoutInfo();
2584 const TextStyle oldInputStyle( mInputStyle );
2586 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2588 if( oldInputStyle != mInputStyle )
2590 // Updates the line height accordingly with the input style.
2593 EmitStyleChangedSignal();
2599 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2601 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2602 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2604 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2607 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2609 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2610 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2612 mStyledText.erase(itStart, itEnd);
2614 // update the selection handles if they are visible.
2615 if( mHighlightMeshActor )
2617 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2618 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2620 if( minHandle >= start + ncharacters )
2622 minHandle -= ncharacters;
2624 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2629 if( maxHandle >= start + ncharacters )
2631 maxHandle -= ncharacters;
2633 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2639 // 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.
2642 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2644 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2645 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2646 // Mean we do not re-draw the text more than we have too.
2649 /* Delete character at current cursor position and redisplay*/
2650 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2652 // Ensure positionToDelete is not out of bounds.
2653 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2654 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2655 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2657 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2660 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2662 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2664 // Get the styled text of the character to be deleted as it may be needed if
2665 // the "exceed the text-input's boundaries" option is disabled.
2666 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2668 mStyledText.erase(it); // erase the character left of positionToDelete
2670 if( mStyledText.empty() )
2672 // Styled text is empty, so set the placeholder text.
2673 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2674 mPlaceHolderSet = true;
2678 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2680 const Character characterToDelete = styledCharacterToDelete.mText[0];
2682 // It may happen than after removing a white space or a new line character,
2683 // two words merge, this new word could be big enough to not fit in its
2684 // current line, so moved to the next one, and make some part of the text to
2685 // exceed the text-input's boundary.
2686 if( !mExceedEnabled )
2688 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2690 // Get the new text layout after removing one character.
2691 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2693 // Get text-input's size.
2694 const Vector3& size = GetControlSize();
2696 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2697 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2699 MarkupProcessor::StyledTextArray array;
2700 array.push_back( styledCharacterToDelete );
2701 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2703 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2708 GetTextLayoutInfo();
2710 ShowGrabHandleAndSetVisibility( false );
2712 mCursorPosition = positionToDelete -1;
2714 const TextStyle oldInputStyle( mInputStyle );
2716 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2718 if( oldInputStyle != mInputStyle )
2720 // Updates the line height accordingly with the input style.
2723 EmitStyleChangedSignal();
2728 /*Insert new character into the string and (optionally) redisplay text-input*/
2729 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2731 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2733 // Ensure insertionPosition is not out of bounds.
2734 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2736 bool textExceedsMaximunNumberOfCharacters = false;
2737 bool textExceedsBoundary = false;
2738 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2740 ShowGrabHandleAndSetVisibility( false );
2742 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2746 mIgnoreCommitFlag = true;
2747 mPreEditFlag = false;
2748 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2749 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2752 if( textExceedsMaximunNumberOfCharacters )
2754 EmitMaxInputCharactersReachedSignal();
2757 if( textExceedsBoundary )
2759 EmitInputTextExceedsBoundariesSignal();
2760 PreEditReset( false );
2764 return insertedStringLength;
2767 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2773 cursor = ImageActor::New( cursorImage );
2777 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2780 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2781 cursor.SetNinePatchBorder( border );
2783 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2784 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2785 cursor.SetVisible(false);
2790 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2792 // As cursor is not moving due to grab handle, handle should be hidden.
2793 ShowGrabHandleAndSetVisibility( false );
2795 bool cursorPositionChanged = false;
2798 if ( mCursorPosition >= places )
2800 mCursorPosition = mCursorPosition - places;
2801 cursorPositionChanged = true;
2806 if ((mCursorPosition + places) <= mStyledText.size())
2808 mCursorPosition = mCursorPosition + places;
2809 cursorPositionChanged = true;
2813 if( cursorPositionChanged )
2815 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2817 const TextStyle oldInputStyle( mInputStyle );
2818 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2822 if( oldInputStyle != mInputStyle )
2824 // Updates the line height accordingly with the input style.
2827 EmitStyleChangedSignal();
2830 ImfManager imfManager = ImfManager::Get();
2833 imfManager.SetCursorPosition ( mCursorPosition );
2834 imfManager.NotifyCursorPosition();
2839 void TextInput::DrawCursor(const std::size_t nthChar)
2841 // Get height of cursor and set its size
2842 Size size( CURSOR_THICKNESS, 0.0f );
2843 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2845 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2849 // Measure Font so know how big text will be if no initial text to measure.
2850 size.height = mLineHeight;
2853 mCursor.SetSize(size);
2855 // If the character is italic then the cursor also tilts.
2856 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2858 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2860 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2862 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2863 bool altPositionValid; // Alternate cursor validity flag.
2864 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2865 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2867 SetAltCursorEnabled( altPositionValid );
2869 if(!altPositionValid)
2871 mCursor.SetPosition( position + UI_OFFSET );
2875 size.height *= 0.5f;
2876 mCursor.SetSize(size);
2877 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2879 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2880 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2881 size.height = rowSize.height * 0.5f;
2882 mCursorRTL.SetSize(size);
2883 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2886 if( IsScrollEnabled() )
2888 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2889 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2894 void TextInput::SetAltCursorEnabled( bool enabled )
2896 mCursorRTLEnabled = enabled;
2897 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2900 void TextInput::SetCursorVisibility( bool visible )
2902 mCursorVisibility = visible;
2903 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2904 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2907 void TextInput::CreateGrabHandle( Dali::Image image )
2913 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2917 mGrabHandleImage = image;
2920 mGrabHandle = ImageActor::New(mGrabHandleImage);
2921 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2922 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2924 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2926 ShowGrabHandleAndSetVisibility( false );
2928 CreateGrabArea( mGrabHandle );
2930 mActiveLayer.Add(mGrabHandle);
2934 void TextInput::CreateGrabArea( Actor& parent )
2936 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2937 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2938 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2939 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2940 mTapDetector.Attach( mGrabArea );
2941 mPanGestureDetector.Attach( mGrabArea );
2943 parent.Add(mGrabArea);
2946 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2948 Vector3 actualHandlePosition;
2952 mActualGrabHandlePosition.x += displacement.x;
2953 mActualGrabHandlePosition.y += displacement.y;
2955 // Grab handle should jump to the nearest character and take cursor with it
2956 std::size_t newCursorPosition = 0;
2957 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2959 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2961 bool handleVisible = true;
2963 if( IsScrollEnabled() )
2965 const Vector3 controlSize = GetControlSize();
2966 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
2967 // Scrolls the text if the handle is not in a visible position
2968 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2975 mCurrentHandlePosition = actualHandlePosition;
2976 mScrollDisplacement = Vector2::ZERO;
2980 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
2982 mScrollDisplacement.x = -SCROLL_SPEED;
2984 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
2986 mScrollDisplacement.x = SCROLL_SPEED;
2988 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
2990 mScrollDisplacement.y = -SCROLL_SPEED;
2992 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
2994 mScrollDisplacement.y = SCROLL_SPEED;
3000 if( handleVisible && // Only redraw cursor and do updates if position changed
3001 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3003 mCursorPosition = newCursorPosition;
3005 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3007 const TextStyle oldInputStyle( mInputStyle );
3009 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3011 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3013 if( oldInputStyle != mInputStyle )
3015 // Updates the line height accordingly with the input style.
3018 EmitStyleChangedSignal();
3023 return actualHandlePosition;
3026 void TextInput::ShowGrabHandle( bool visible )
3028 if ( IsGrabHandleEnabled() )
3032 mGrabHandle.SetVisible( mGrabHandleVisibility );
3034 StartMonitoringStageForTouch();
3038 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3040 mGrabHandleVisibility = visible;
3041 ShowGrabHandle( visible );
3044 // Callbacks connected to be Property notifications for Boundary checking.
3046 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3048 mIsSelectionHandleOneFlipped = true;
3049 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3050 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3053 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3055 mIsSelectionHandleOneFlipped = false;
3056 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3057 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3060 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3062 mIsSelectionHandleTwoFlipped = true;
3063 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3064 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3067 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3069 mIsSelectionHandleTwoFlipped = false;
3070 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3071 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3074 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3075 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3077 mSelectionHandleOne.SetOpacity(0.0f);
3080 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3082 mSelectionHandleOne.SetOpacity(1.0f);
3085 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3087 mSelectionHandleTwo.SetOpacity(0.0f);
3090 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3092 mSelectionHandleTwo.SetOpacity(1.0f);
3095 // End of Callbacks connected to be Property notifications for Boundary checking.
3097 void TextInput::SetUpHandlePropertyNotifications()
3099 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3101 Vector3 handlesize = GetSelectionHandleSize();
3103 // Exceeding horizontal boundary
3104 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3105 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3107 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3108 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3110 // Within horizontal boundary
3111 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3112 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3114 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3115 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3117 // Exceeding vertical boundary
3118 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3119 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3120 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3121 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3123 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3124 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3125 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3126 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3128 // Within vertical boundary
3129 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3130 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3131 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3132 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3134 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3135 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3136 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3137 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3140 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3142 mSelectionHandleOnePosition = start;
3143 mSelectionHandleTwoPosition = end;
3145 if ( !mSelectionHandleOne )
3147 // create normal and pressed images
3148 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3149 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3151 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3152 mSelectionHandleOne.SetName("SelectionHandleOne");
3153 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3154 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3155 mIsSelectionHandleOneFlipped = false;
3156 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3158 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3159 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3161 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3162 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3164 mTapDetector.Attach( mHandleOneGrabArea );
3165 mPanGestureDetector.Attach( mHandleOneGrabArea );
3167 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3169 mSelectionHandleOne.Add( mHandleOneGrabArea );
3170 mActiveLayer.Add( mSelectionHandleOne );
3173 if ( !mSelectionHandleTwo )
3175 // create normal and pressed images
3176 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3177 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3179 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3180 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3181 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3182 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3183 mIsSelectionHandleTwoFlipped = false;
3184 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3186 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3187 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3188 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3189 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3191 mTapDetector.Attach( mHandleTwoGrabArea );
3192 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3194 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3196 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3198 mActiveLayer.Add( mSelectionHandleTwo );
3201 SetUpHandlePropertyNotifications();
3203 // update table as text may have changed.
3204 GetTextLayoutInfo();
3206 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3207 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3209 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3210 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3212 // Calculates and set the visibility if the scroll mode is enabled.
3213 bool isSelectionHandleOneVisible = true;
3214 bool isSelectionHandleTwoVisible = true;
3215 if( IsScrollEnabled() )
3217 const Vector3& controlSize( GetControlSize() );
3218 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3219 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3220 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3221 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3224 CreateHighlight(); // function will only create highlight if not already created.
3227 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3229 Vector3 actualHandlePosition;
3231 if ( mSelectionHandleOne && mSelectionHandleTwo )
3233 const Vector3& controlSize = GetControlSize();
3235 Size cursorSize( CURSOR_THICKNESS, 0.f );
3237 // Get a reference of the wanted selection handle (handle one or two).
3238 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3240 // Get a reference for the current position of the handle and a copy of its pair
3241 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3242 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3244 // Get a handle of the selection handle actor
3245 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3247 // Selection handles should jump to the nearest character
3248 std::size_t newHandlePosition = 0;
3249 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3251 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3253 bool handleVisible = true;
3255 if( IsScrollEnabled() )
3257 mCurrentSelectionId = handleId;
3259 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3260 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3261 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3268 mCurrentSelectionHandlePosition = actualHandlePosition;
3269 mScrollDisplacement = Vector2::ZERO;
3273 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3275 mScrollDisplacement.x = -SCROLL_SPEED;
3277 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3279 mScrollDisplacement.x = SCROLL_SPEED;
3281 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3283 mScrollDisplacement.y = -SCROLL_SPEED;
3285 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3287 mScrollDisplacement.y = SCROLL_SPEED;
3293 if ( handleVisible && // Ensure the handle is visible.
3294 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3295 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3297 currentSelectionHandlePosition = newHandlePosition;
3299 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3300 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3304 if ( handleId == HandleOne )
3306 const TextStyle oldInputStyle( mInputStyle );
3308 // Set Active Style to that of first character in selection
3309 if( mSelectionHandleOnePosition < mStyledText.size() )
3311 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3314 if( oldInputStyle != mInputStyle )
3316 // Updates the line height accordingly with the input style.
3319 EmitStyleChangedSignal();
3325 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3328 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3331 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3332 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3334 if ( selectionHandleActor )
3336 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3337 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3338 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3340 if( IsScrollEnabled() )
3342 const Size cursorSize( CURSOR_THICKNESS,
3343 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3344 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3346 GetControlSize() ) );
3351 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3353 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3354 // For now the visual position of end of logical string will be end of visual string.
3355 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3357 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3360 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3362 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3363 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3364 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3365 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3367 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3369 // Deselect text prior to startSelectionIt
3370 for(;it!=startSelectionIt;++it)
3372 selectedVisualText[*it] = false;
3375 // Select text from startSelectionIt -> endSelectionIt
3376 for(;it!=endSelectionIt;++it)
3378 selectedVisualText[*it] = true;
3381 // Deselect text after endSelection
3384 selectedVisualText[*it] = false;
3387 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3390 // Calculate the dimensions of the quads they will make the highlight mesh
3391 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3393 // At the moment there is no public API to modify the block alignment option.
3394 const bool blockAlignEnabled = true;
3396 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3398 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3400 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3401 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3403 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3404 std::vector<bool> selectedVisualText;
3405 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3406 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3407 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3409 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3410 float rowLeft = 0.0f;
3411 float rowRight = 0.0f;
3412 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3413 float maxRowLeft = std::numeric_limits<float>::max();
3414 float maxRowRight = 0.0f;
3416 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3418 // Scan through entire text.
3421 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3423 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3424 bool charSelected( false );
3425 if( selectedIt != selectedEndIt )
3427 charSelected = *selectedIt++;
3430 if(selectionState == SelectionNone)
3434 selectionState = SelectionStarted;
3435 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3436 rowRight = rowLeft + charInfo.mSize.width;
3439 else if(selectionState == SelectionStarted)
3441 // break selection on:
3442 // 1. new line causing selection break. (\n or wordwrap)
3443 // 2. character not selected.
3444 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3447 // finished selection.
3448 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3449 // that it resides on. That way this enumeration is not necessary.
3451 if(lastIt->mIsNewLineChar)
3453 // 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.
3454 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3456 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3457 maxRowLeft = std::min(maxRowLeft, min.x);
3458 maxRowRight = std::max(maxRowRight, max.x);
3459 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3460 float rowTop = rowBottom - rowSize.height;
3462 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3463 if(charSelected && blockAlignEnabled)
3465 rowRight = std::numeric_limits<float>::max();
3467 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3469 selectionState = SelectionNone;
3471 // Still selected? start a new selection
3474 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3475 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3476 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3477 selectionState = SelectionStarted;
3482 // build up highlight(s) with this selection data.
3483 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3484 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3491 // If reached end, and still on selection, then close selection.
3494 if(selectionState == SelectionStarted)
3496 // finished selection.
3498 if(lastIt->mIsNewLineChar)
3500 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3502 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3503 maxRowLeft = std::min(maxRowLeft, min.x);
3504 maxRowRight = std::max(maxRowRight, max.x);
3505 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3506 float rowTop = rowBottom - rowSize.height;
3507 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3511 // Get the top left and bottom right corners.
3512 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3513 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3514 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3516 // Clamp quads so they appear to clip to borders of the whole text.
3517 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3519 // For block-align align Further Clamp quads to max left and right extents
3520 if(blockAlignEnabled)
3522 // BlockAlign: Will adjust highlight to block:
3524 // H[ello] (top row right = max of all rows right)
3525 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3526 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3527 // [text] (bottom row left = min of all rows left)
3528 // (common in SMS messaging selection)
3530 // As opposed to the default which is tight text highlighting.
3535 // (common in regular text editors/web browser selection)
3537 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3540 // Finally clamp quads again so they don't exceed the boundry of the control.
3541 const Vector3& controlSize = GetControlSize();
3542 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3545 return mNewHighlightInfo;
3548 void TextInput::UpdateHighlight()
3550 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3552 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3554 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3555 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3556 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3557 // [BOTTOM] [ MIDDLE ]
3560 // Each quad is created as 2 triangles.
3561 // Middle is just 1 quad regardless of its size.
3575 if ( mHighlightMeshActor )
3577 // vertex and triangle buffers should always be present if MeshActor is alive.
3578 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3579 MeshData::VertexContainer vertices;
3580 Dali::MeshData::FaceIndices faceIndices;
3582 if( !newHighlightInfo.mQuadList.empty() )
3584 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3585 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3587 // vertex position defaults to (0 0 0)
3588 MeshData::Vertex vertex;
3589 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3592 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3594 // Add each quad geometry (a sub-selection) to the mesh data.
3604 QuadCoordinates& quad = *iter;
3606 vertex.x = quad.min.x;
3607 vertex.y = quad.min.y;
3608 vertices.push_back( vertex );
3611 vertex.x = quad.max.x;
3612 vertex.y = quad.min.y;
3613 vertices.push_back( vertex );
3615 // bottom-left (v+2)
3616 vertex.x = quad.min.x;
3617 vertex.y = quad.max.y;
3618 vertices.push_back( vertex );
3620 // bottom-right (v+3)
3621 vertex.x = quad.max.x;
3622 vertex.y = quad.max.y;
3623 vertices.push_back( vertex );
3625 // triangle A (3, 1, 0)
3626 faceIndices.push_back( v + 3 );
3627 faceIndices.push_back( v + 1 );
3628 faceIndices.push_back( v );
3630 // triangle B (0, 2, 3)
3631 faceIndices.push_back( v );
3632 faceIndices.push_back( v + 2 );
3633 faceIndices.push_back( v + 3 );
3635 mMeshData.SetFaceIndices( faceIndices );
3638 BoneContainer bones(0); // passed empty as bones not required
3639 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3640 mHighlightMesh.UpdateMeshData(mMeshData);
3645 void TextInput::ClearPopup()
3647 mPopUpPanel.Clear();
3650 void TextInput::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
3652 mPopUpPanel.AddOption(name, caption, icon, finalOption);
3655 void TextInput::SetPopupPosition(const Vector3& position)
3657 mPopUpPanel.Self().SetPosition( position );
3660 void TextInput::HidePopup(bool animate, bool signalFinished )
3662 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3664 mPopUpPanel.Hide( animate );
3666 if( animate && signalFinished )
3668 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3673 void TextInput::ShowPopup(bool animate)
3677 if(mHighlightMeshActor && mState == StateEdit)
3681 // When text is selected, show popup above top handle (and text), or below bottom handle.
3682 // topHandle: referring to the top most point of the handle or the top line of selection.
3683 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3685 topHandle = mSelectionHandleOneActualPosition;
3686 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3690 topHandle = mSelectionHandleTwoActualPosition;
3691 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3693 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3694 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3696 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3697 Vector3 bottomHandle;
3698 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3699 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3700 mPopUpPanel.SetAlternativeOffset(Vector2(0.0f, bottomHandle.y - topHandle.y));
3704 // When no text is selected, show popup at world position of grab handle or cursor
3705 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3706 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3707 position.y -= rowSize.height;
3708 // if can't be positioned above, then position below row.
3709 Vector2 alternativePopUpPosition( 0.0f, position.y ); // default if no grab handle
3712 alternativePopUpPosition.y = rowSize.height + ( mGrabHandle.GetCurrentSize().height * DEFAULT_GRAB_HANDLE_RELATIVE_SIZE.y ) ;
3713 // If grab handle enabled then position pop-up below the grab handle.
3715 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3718 // reposition popup above the desired cursor posiiton.
3719 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3720 textViewSize.z = 0.0f;
3721 // World position = world position of ParentOrigin of cursor (i.e. top-left corner of TextView) + cursor position;
3722 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3724 SetPopupPosition( worldPosition );
3727 mPopUpPanel.Show(animate);
3728 StartMonitoringStageForTouch();
3730 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3733 void TextInput::ShowPopupCutCopyPaste()
3736 // Check the selected text is whole text or not.
3737 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3739 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3740 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3743 if ( !mStyledText.empty() )
3745 Image cutIcon = Image::New( DEFAULT_ICON_CUT );
3746 Image copyIcon = Image::New( DEFAULT_ICON_COPY );
3747 AddPopupOption( OPTION_CUT, GET_LOCALE_TEXT("IDS_COM_BODY_CUT"), cutIcon );
3748 AddPopupOption( OPTION_COPY, GET_LOCALE_TEXT("IDS_COM_BODY_COPY"), copyIcon, true );
3751 if(mClipboard.NumberOfItems())
3753 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3754 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3755 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon );
3756 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3759 mPopUpPanel.Hide(false);
3763 void TextInput::SetUpPopUpSelection()
3767 // If no text exists then don't offer to select
3768 if ( !mStyledText.empty() )
3770 Image selectIcon = Image::New( DEFAULT_ICON_SELECT );
3771 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3772 AddPopupOption( OPTION_SELECT_WORD, GET_LOCALE_TEXT("IDS_COM_SK_SELECT"), selectIcon );
3773 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3775 // if clipboard has valid contents then offer paste option
3776 if( mClipboard.NumberOfItems() )
3778 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3779 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3780 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon, true );
3781 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3784 mPopUpPanel.Hide(false);
3787 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3792 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3793 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3794 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3795 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3797 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3799 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3801 float closestYdifference = std::numeric_limits<float>::max();
3802 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3803 std::size_t numberOfMatchedCharacters = 0;
3805 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3806 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3808 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3810 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3811 float baselinePosition = info.mPosition.y - info.mDescender;
3813 if( info.mIsVisible )
3815 // store difference between source y point and the y position of the current character
3816 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3818 if( currentYdifference < closestYdifference )
3820 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3821 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3822 closestYdifference = currentYdifference;
3823 matchedCharacters.clear();
3824 numberOfMatchedCharacters = 0; // reset count
3827 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3828 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3830 // ignore new line character.
3831 if( !info.mIsNewLineChar )
3833 matchedCharacters.push_back( info );
3834 numberOfMatchedCharacters++;
3838 } // End of loop checking each character's y position in the character layout table
3840 // Check if last character is a newline, if it is
3841 // then need pretend there is an imaginary line afterwards,
3842 // and check if user is touching below previous line.
3843 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3845 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3847 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3851 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3852 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3854 bool matched( false );
3856 // 2 Iterate through matching list of y positions and find closest matching X position.
3857 for( ; it != endIt; ++it )
3859 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3861 if( info.mIsVisible )
3863 // stop when on left side of character's center.
3864 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3865 if( sourceScrollOffset.x < characterMidPointPosition )
3867 if(info.mIsRightToLeftCharacter)
3869 rightToLeftChar = true;
3871 glyphIntersection = info.mPosition.x;
3876 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3882 rightToLeftChar = lastRightToLeftChar;
3885 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3886 closestIndex = lineOffset + matchCharacterIndex;
3888 mClosestCursorPositionEOL = false; // reset
3889 if ( it == endIt && !matched )
3891 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3894 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3895 if( rightToLeftChar && lastRightToLeftChar )
3897 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3902 // closestIndex is the visual index, need to convert it to the logical index
3903 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3905 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3907 // Checks for situations where user is touching between LTR and RTL
3908 // characters. To identify if the user means the end of a LTR string
3909 // or the beginning of an RTL string, and vice versa.
3910 if( closestIndex > 0 )
3912 if( rightToLeftChar && !lastRightToLeftChar )
3917 // A: In this touch range, the user is indicating that they wish to place
3918 // the cursor at the end of the LTR text.
3919 // B: In this touch range, the user is indicating that they wish to place
3920 // the cursor at the end of the RTL text.
3922 // Result of touching A area:
3923 // [.....LTR]|[RTL......]+
3925 // |: primary cursor (for typing LTR chars)
3926 // +: secondary cursor (for typing RTL chars)
3928 // Result of touching B area:
3929 // [.....LTR]+[RTL......]|
3931 // |: primary cursor (for typing RTL chars)
3932 // +: secondary cursor (for typing LTR chars)
3934 if( sourceScrollOffset.x < glyphIntersection )
3939 else if( !rightToLeftChar && lastRightToLeftChar )
3941 if( sourceScrollOffset.x < glyphIntersection )
3948 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3949 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3950 // one further ahead
3951 if( rightToLeftChar && !lastRightToLeftChar )
3956 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3958 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3960 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3969 float TextInput::GetLineJustificationPosition() const
3971 const Vector3& size = mDisplayedTextView.GetCurrentSize();
3972 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
3973 float alignmentOffset = 0.f;
3975 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
3976 if( alignment & Toolkit::Alignment::HorizontalLeft )
3978 alignmentOffset = 0.f;
3980 else if( alignment & Toolkit::Alignment::HorizontalCenter )
3982 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
3984 else if( alignment & Toolkit::Alignment::HorizontalRight )
3986 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
3989 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
3990 float justificationOffset = 0.f;
3992 switch( justification )
3994 case Toolkit::TextView::Left:
3996 justificationOffset = 0.f;
3999 case Toolkit::TextView::Center:
4001 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4004 case Toolkit::TextView::Right:
4006 justificationOffset = mTextLayoutInfo.mTextSize.width;
4009 case Toolkit::TextView::Justified:
4011 justificationOffset = 0.f;
4016 DALI_ASSERT_ALWAYS( false );
4020 return alignmentOffset + justificationOffset;
4023 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4025 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4026 A newline character is not inserted in this case */
4028 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4030 Vector3 cursorPosition;
4032 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4034 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4036 // end character so use
4037 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4038 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4042 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4045 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4047 // If previous character on a different line then use current characters position
4048 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4050 if ( mClosestCursorPositionEOL )
4052 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4056 cursorPosition = Vector3(currentCharInfo.mPosition);
4061 // Previous character is on same line so use position of previous character plus it's width.
4062 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4065 return cursorPosition;
4068 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4070 bool direction(false);
4071 Vector3 alternatePosition;
4072 bool alternatePositionValid(false);
4074 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4077 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4079 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4081 alternatePositionValid = false;
4082 directionRTL = false;
4084 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4086 std::size_t visualCharacterPosition;
4088 // When cursor is not at beginning, consider possibility of
4089 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4090 if(characterPosition > 0)
4092 // Cursor position should be the end of the last character.
4093 // If the last character is LTR, then the end is on the right side of the glyph.
4094 // If the last character is RTL, then the end is on the left side of the glyph.
4095 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4097 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4099 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4102 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4103 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4105 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4106 const Vector3& size = GetControlSize();
4108 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4110 --visualCharacterPosition;
4112 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4115 if(!info.mIsNewLineChar)
4117 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4121 // When cursor points to first character on new line, position cursor at the start of this glyph.
4122 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4124 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4125 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4126 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4128 cursorPosition.x = infoNext.mPosition.x + start;
4129 cursorPosition.y = infoNext.mPosition.y;
4133 // If cursor points to the end of text, then can only position
4134 // cursor where the new line starts based on the line-justification position.
4135 cursorPosition.x = GetLineJustificationPosition();
4137 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4139 // If this is after the last character, then we can assume that the new cursor
4140 // should be exactly one row below the current row.
4142 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4143 cursorPosition.y = info.mPosition.y + rowRect.height;
4147 // If this is not after last character, then we can use this row's height.
4148 // should be exactly one row below the current row.
4150 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4151 cursorPosition.y = info.mPosition.y + rowRect.height;
4156 directionRTL = info.mIsRightToLeftCharacter;
4158 // 1. When the cursor is neither at the beginning or the end,
4159 // we can show multiple cursors under situations when the cursor is
4160 // between RTL and LTR text...
4161 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4163 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4165 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4166 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4168 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4170 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4171 // Text: [...LTR...]|[...RTL...]
4173 // Alternate cursor pos: ^
4174 // In which case we need to display an alternate cursor for the RTL text.
4176 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4177 alternatePosition.y = infoAlt.mPosition.y;
4178 alternatePositionValid = true;
4180 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4182 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4183 // Text: |[...RTL...] [...LTR....]
4185 // Alternate cursor pos: ^
4186 // In which case we need to display an alternate cursor for the RTL text.
4188 alternatePosition.x = infoAlt.mPosition.x;
4189 alternatePosition.y = infoAlt.mPosition.y;
4190 alternatePositionValid = true;
4195 // 2. When the cursor is at the end of the text,
4196 // and we have multi-directional text,
4197 // we can also consider showing mulitple cursors.
4198 // The rule here is:
4199 // If first and last characters on row are different
4200 // Directions, then two cursors need to be displayed.
4202 // Get first logical glyph on row
4203 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4205 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4206 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4208 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4210 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4211 // Text: [...LTR...]|[...RTL...]
4213 // Alternate cursor pos: ^
4214 // In which case we need to display an alternate cursor for the RTL text, this cursor
4215 // should be at the end of the given line.
4217 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4218 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4219 alternatePosition.y = infoAlt.mPosition.y;
4220 alternatePositionValid = true;
4222 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4224 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4225 // Text: |[...RTL...] [...LTR....]
4227 // Alternate cursor pos: ^
4228 // In which case we need to display an alternate cursor for the RTL text.
4230 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4231 alternatePosition.x = infoAlt.mPosition.x;
4232 alternatePosition.y = infoAlt.mPosition.y;
4233 alternatePositionValid = true;
4236 } // characterPosition > 0
4237 else if(characterPosition == 0)
4239 // When the cursor position is at the beginning, it should be at the start of the current character.
4240 // If the current character is LTR, then the start is on the right side of the glyph.
4241 // If the current character is RTL, then the start is on the left side of the glyph.
4242 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4244 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4246 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4249 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4250 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4252 cursorPosition.x = info.mPosition.x + start;
4253 cursorPosition.y = info.mPosition.y;
4254 directionRTL = info.mIsRightToLeftCharacter;
4259 // If the character table is void, place the cursor accordingly the text alignment.
4260 const Vector3& size = GetControlSize();
4262 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4263 float alignmentOffset = 0.f;
4265 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4266 if( alignment & Toolkit::Alignment::HorizontalLeft )
4268 alignmentOffset = 0.f;
4270 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4272 alignmentOffset = 0.5f * ( size.width );
4274 else if( alignment & Toolkit::Alignment::HorizontalRight )
4276 alignmentOffset = size.width;
4279 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4280 cursorPosition.x = alignmentOffset;
4282 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4283 if( alignment & Toolkit::Alignment::VerticalTop )
4285 cursorPosition.y = mLineHeight;
4287 else if( alignment & Toolkit::Alignment::VerticalCenter )
4289 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4291 else if( alignment & Toolkit::Alignment::VerticalBottom )
4293 cursorPosition.y = size.height;
4297 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4298 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4299 if( alternatePositionValid )
4301 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4302 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4305 return cursorPosition;
4308 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4310 // scan string from current position to beginning of current line to note direction of line
4311 while(logicalPosition)
4314 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4315 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4322 return logicalPosition;
4325 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4329 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4332 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4334 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4335 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4337 min = Vector2::ZERO;
4338 max = Vector2(0.0f, mLineHeight);
4342 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4343 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4344 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4346 // If cursor is pointing to end of line, then start from last character.
4347 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4349 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4351 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4352 if( !it->mIsVisible )
4354 characterPosition = FindVisibleCharacter( Left, characterPosition );
4355 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4358 // Scan characters left and right of cursor, stopping when end of line/string reached or
4359 // y position greater than threshold of reference line.
4361 // 1. scan left until we reach the beginning or a different line.
4362 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4363 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4364 // min-x position is the left-most char's left (x)
4365 // max-x position is the right-most char's right (x)
4366 // min-y position is the minimum of all character's top (y)
4367 // max-y position is the maximum of all character's bottom (y+height)
4368 min.y = validCharIt->mPosition.y;
4369 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4374 min.y = std::min(min.y, validCharIt->mPosition.y);
4375 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4384 if( (it->mPosition.y < referenceLine) ||
4385 (it->mIsNewLineChar) ||
4392 // info refers to the first character on this line.
4393 min.x = validCharIt->mPosition.x;
4395 // 2. scan right until we reach end or a different line
4396 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4397 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4401 if( (it->mPosition.y > referenceLine) ||
4402 (it->mIsNewLineChar) ||
4409 min.y = std::min(min.y, validCharIt->mPosition.y);
4410 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4415 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4417 if ( validCharIt != end )
4419 // info refers to the last character on this line.
4420 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4423 return Size( max.x - min.x, max.y - min.y );
4426 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4428 Actor popUpPanel = mPopUpPanel.GetRootActor();
4430 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4436 Dali::Actor parent( touchedActor.GetParent() );
4440 return WasTouchedCheck( parent );
4447 void TextInput::StartMonitoringStageForTouch()
4449 Stage stage = Stage::GetCurrent();
4450 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4453 void TextInput::EndMonitoringStageForTouch()
4455 Stage stage = Stage::GetCurrent();
4456 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4459 void TextInput::OnStageTouched(const TouchEvent& event)
4461 if( event.GetPointCount() > 0 )
4463 if ( TouchPoint::Down == event.GetPoint(0).state )
4465 const Actor touchedActor(event.GetPoint(0).hitActor);
4467 bool popUpShown( false );
4469 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4474 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4476 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4478 EndMonitoringStageForTouch();
4479 HidePopup( true, false );
4482 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4484 EndMonitoringStageForTouch();
4485 ShowGrabHandleAndSetVisibility( false );
4491 void TextInput::SelectText(std::size_t start, std::size_t end)
4493 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4494 IsGrabHandleEnabled()?"true":"false",
4495 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4496 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4497 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4499 StartMonitoringStageForTouch();
4501 if ( mEditModeActive ) // Only allow text selection when in edit mode
4503 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4504 mSelectingText = true;
4506 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4508 ImfManager imfManager = ImfManager::Get();
4511 imfManager.SetCursorPosition ( mCursorPosition );
4512 imfManager.SetSurroundingText( GetText() );
4513 imfManager.NotifyCursorPosition();
4515 // 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.
4517 // Hide grab handle when selecting.
4518 ShowGrabHandleAndSetVisibility( false );
4520 if( start != end ) // something to select
4522 SetCursorVisibility( false );
4523 StopCursorBlinkTimer();
4525 CreateSelectionHandles(start, end);
4528 const TextStyle oldInputStyle( mInputStyle );
4529 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4531 if( oldInputStyle != mInputStyle )
4533 // Updates the line height accordingly with the input style.
4536 EmitStyleChangedSignal();
4542 mSelectingText = false;
4546 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4548 MarkupProcessor::StyledTextArray currentSelectedText;
4550 if ( IsTextSelected() )
4552 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4553 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4555 for(; it != end; ++it)
4557 MarkupProcessor::StyledText& styledText( *it );
4558 currentSelectedText.push_back( styledText );
4561 return currentSelectedText;
4564 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4566 const std::size_t beginIndex = std::min( begin, end );
4567 const std::size_t endIndex = std::max( begin, end );
4570 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4572 // Create a styled text array used to replace the text into the text-view.
4573 MarkupProcessor::StyledTextArray text;
4574 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4576 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4577 GetTextLayoutInfo();
4579 if( IsScrollEnabled() )
4581 // Need to set the scroll position as the text's size may have changed.
4582 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4585 ShowGrabHandleAndSetVisibility( false );
4591 // Set Handle positioning as the new style may have repositioned the characters.
4592 SetSelectionHandlePosition(HandleOne);
4593 SetSelectionHandlePosition(HandleTwo);
4596 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4598 // Just hide the grab handle when keyboard is hidden.
4599 if (!keyboardShown )
4601 ShowGrabHandleAndSetVisibility( false );
4603 // If the keyboard is not now being shown, then hide the popup panel
4604 mPopUpPanel.Hide( true );
4608 // Removes highlight and resumes edit mode state
4609 void TextInput::RemoveHighlight()
4611 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4613 if ( mHighlightMeshActor )
4615 if ( mSelectionHandleOne )
4617 mActiveLayer.Remove( mSelectionHandleOne );
4618 mSelectionHandleOne.Reset();
4619 mSelectionHandleOneOffset.x = 0.0f;
4621 if ( mSelectionHandleTwo )
4623 mActiveLayer.Remove( mSelectionHandleTwo );
4624 mSelectionHandleTwo.Reset();
4625 mSelectionHandleTwoOffset.x = 0.0f;
4628 mNewHighlightInfo.mQuadList.clear();
4630 Self().Remove( mHighlightMeshActor );
4632 SetCursorVisibility( true );
4633 StartCursorBlinkTimer();
4635 mHighlightMeshActor.Reset();
4636 // NOTE: We cannot dereference mHighlightMesh, due
4637 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4642 mSelectionHandleOnePosition = 0;
4643 mSelectionHandleTwoPosition = 0;
4646 void TextInput::CreateHighlight()
4648 if ( !mHighlightMeshActor )
4650 mMeshData = MeshData( );
4651 mMeshData.SetHasNormals( true );
4653 mCustomMaterial = Material::New("CustomMaterial");
4654 mCustomMaterial.SetDiffuseColor( LIGHTBLUE );
4656 mMeshData.SetMaterial( mCustomMaterial );
4658 mHighlightMesh = Mesh::New( mMeshData );
4660 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4661 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4662 mHighlightMeshActor.SetInheritShaderEffect( false );
4663 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4664 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4665 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4666 mHighlightMeshActor.SetAffectedByLighting(false);
4668 Self().Add(mHighlightMeshActor);
4673 bool TextInput::CopySelectedTextToClipboard()
4675 mCurrentCopySelecton.clear();
4677 mCurrentCopySelecton = GetSelectedText();
4679 std::string stringToStore;
4681 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4682 * a marked up string.
4684 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4685 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4686 bool success = mClipboard.SetItem( stringToStore );
4690 void TextInput::PasteText( const Text& text )
4692 // Update Flag, indicates whether to update the text-input contents or not.
4693 // Any key stroke that results in a visual change of the text-input should
4694 // set this flag to true.
4695 bool update = false;
4696 if( mHighlightMeshActor )
4698 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4699 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4701 ImfManager imfManager = ImfManager::Get();
4704 imfManager.SetCursorPosition( mCursorPosition );
4705 imfManager.NotifyCursorPosition();
4707 DeleteHighlightedText( true );
4711 bool textExceedsMaximunNumberOfCharacters = false;
4712 bool textExceedsBoundary = false;
4714 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4716 mCursorPosition += insertedStringLength;
4717 ImfManager imfManager = ImfManager::Get();
4720 imfManager.SetCursorPosition ( mCursorPosition );
4721 imfManager.NotifyCursorPosition();
4724 update = update || ( insertedStringLength > 0 );
4730 if( insertedStringLength < text.GetLength() )
4732 EmitMaxInputCharactersReachedSignal();
4735 if( textExceedsBoundary )
4737 EmitInputTextExceedsBoundariesSignal();
4741 void TextInput::SetTextDirection()
4743 // Put the cursor to the right if we are empty and an RTL language is being used.
4744 if ( mStyledText.empty() )
4746 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4748 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4749 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4751 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4752 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4754 int alignment( mDisplayedTextView.GetTextAlignment() &
4755 ( Toolkit::Alignment::VerticalTop |
4756 Toolkit::Alignment::VerticalCenter |
4757 Toolkit::Alignment::VerticalBottom |
4758 Toolkit::Alignment::HorizontalCenter ) );
4759 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4761 // If our alignment is in the center, then do not change.
4762 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4764 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4767 // If our justification is in the center, then do not change.
4768 if ( justification != Toolkit::TextView::Center )
4770 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4773 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4774 mDisplayedTextView.SetLineJustification( justification );
4778 void TextInput::UpdateLineHeight()
4780 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4781 mLineHeight = font.GetLineHeight();
4783 // 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.
4785 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4787 if( !mExceedEnabled || shrink )
4789 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4793 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4795 std::size_t position = 0;
4797 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4803 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4805 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4807 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4813 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4814 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4816 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4822 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4827 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4834 void TextInput::SetSortModifier( float depthOffset )
4836 if(mDisplayedTextView)
4838 mDisplayedTextView.SetSortModifier(depthOffset);
4842 void TextInput::SetSnapshotModeEnabled( bool enable )
4844 if(mDisplayedTextView)
4846 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4850 bool TextInput::IsSnapshotModeEnabled() const
4852 bool snapshotEnabled = false;
4854 if(mDisplayedTextView)
4856 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4859 return snapshotEnabled;
4862 void TextInput::SetScrollEnabled( bool enable )
4864 if( mDisplayedTextView )
4866 mDisplayedTextView.SetScrollEnabled( enable );
4871 // Don't set cursor's and handle's visibility to false if they are outside the
4872 // boundaries of the text-input.
4873 mIsCursorInScrollArea = true;
4874 mIsGrabHandleInScrollArea = true;
4875 if( mSelectionHandleOne && mSelectionHandleTwo )
4877 mSelectionHandleOne.SetVisible( true );
4878 mSelectionHandleTwo.SetVisible( true );
4880 if( mHighlightMeshActor )
4882 mHighlightMeshActor.SetVisible( true );
4888 bool TextInput::IsScrollEnabled() const
4890 bool scrollEnabled = false;
4892 if( mDisplayedTextView )
4894 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4897 return scrollEnabled;
4900 void TextInput::SetScrollPosition( const Vector2& position )
4902 if( mDisplayedTextView )
4904 mDisplayedTextView.SetScrollPosition( position );
4908 Vector2 TextInput::GetScrollPosition() const
4910 Vector2 scrollPosition;
4912 if( mDisplayedTextView )
4914 scrollPosition = mDisplayedTextView.GetScrollPosition();
4917 return scrollPosition;
4920 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4922 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4923 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4924 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4926 // Add style to the new input text.
4927 MarkupProcessor::StyledTextArray textToInsert;
4928 for( std::size_t i = 0; i < insertedStringLength; ++i )
4930 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4931 textToInsert.push_back( newStyledCharacter );
4934 //Insert text to the TextView.
4935 const bool emptyTextView = mStyledText.empty();
4936 if( emptyTextView && mPlaceHolderSet )
4938 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4939 mDisplayedTextView.SetText( textToInsert );
4943 if( 0 == numberOfCharactersToReplace )
4945 mDisplayedTextView.InsertTextAt( position, textToInsert );
4949 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4952 mPlaceHolderSet = false;
4954 if( textToInsert.empty() )
4956 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4957 GetTextLayoutInfo();
4961 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4962 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4965 textExceedsBoundary = false;
4967 if( !mExceedEnabled )
4969 const Vector3& size = GetControlSize();
4971 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
4973 // If new text does not fit within TextView
4974 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
4975 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
4976 GetTextLayoutInfo();
4977 textExceedsBoundary = true;
4978 insertedStringLength = 0;
4981 if( textExceedsBoundary )
4983 // Add the part of the text which fits on the text-input.
4985 // Split the text which doesn't fit in two halves.
4986 MarkupProcessor::StyledTextArray firstHalf;
4987 MarkupProcessor::StyledTextArray secondHalf;
4988 SplitText( textToInsert, firstHalf, secondHalf );
4990 // Clear text. This text will be filled with the text inserted.
4991 textToInsert.clear();
4993 // Where to insert the text.
4994 std::size_t positionToInsert = position;
4996 bool end = text.GetLength() <= 1;
4999 // Insert text and check ...
5000 const std::size_t textLength = firstHalf.size();
5001 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5002 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5004 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5006 // Inserted text doesn't fit.
5008 // Remove inserted text
5009 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5010 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5012 // The iteration finishes when only one character doesn't fit.
5013 end = textLength <= 1;
5017 // Prepare next two halves for next iteration.
5018 MarkupProcessor::StyledTextArray copyText = firstHalf;
5019 SplitText( copyText, firstHalf, secondHalf );
5026 // store text to be inserted in mStyledText.
5027 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5029 // Increase the inserted characters counter.
5030 insertedStringLength += textLength;
5032 // Prepare next two halves for next iteration.
5033 MarkupProcessor::StyledTextArray copyText = secondHalf;
5034 SplitText( copyText, firstHalf, secondHalf );
5036 // Update where next text has to be inserted
5037 positionToInsert += textLength;
5043 if( textToInsert.empty() && emptyTextView )
5045 // No character has been added and the text-view was empty.
5046 // Set the placeholder text.
5047 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5048 mPlaceHolderSet = true;
5052 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5053 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5054 mPlaceHolderSet = false;
5057 return insertedStringLength;
5060 void TextInput::GetTextLayoutInfo()
5062 if( mStyledText.empty() )
5064 // The text-input has no text, clear the text-view's layout info.
5065 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5069 if( mDisplayedTextView )
5071 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5075 // There is no text-view.
5076 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5081 void TextInput::EmitStyleChangedSignal()
5083 // emit signal if input style changes.
5085 Toolkit::TextInput handle( GetOwner() );
5086 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5089 void TextInput::EmitMaxInputCharactersReachedSignal()
5091 // emit signal if max characters is reached during text input.
5092 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5094 Toolkit::TextInput handle( GetOwner() );
5095 mMaxInputCharactersReachedSignalV2.Emit( handle );
5098 void TextInput::EmitInputTextExceedsBoundariesSignal()
5100 // Emit a signal when the input text exceeds the boundaries of the text input.
5102 Toolkit::TextInput handle( GetOwner() );
5103 mInputTextExceedBoundariesSignalV2.Emit( handle );
5106 } // namespace Internal
5108 } // namespace Toolkit