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();
428 imfManager.SetCursorPosition( mCursorPosition );
429 imfManager.SetSurroundingText( initialText );
430 imfManager.NotifyCursorPosition();
432 if( IsScrollEnabled() )
434 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
437 ShowGrabHandleAndSetVisibility( false );
444 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
446 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
448 mDisplayedTextView.SetText( styleText );
449 mPlaceHolderSet = false;
451 // If text alignment hasn't been manually set by application developer, then we
452 // automatically determine the alignment based on the content of the text i.e. what
453 // language the text begins with.
454 // TODO: This should determine different alignments for each line (broken by '\n') of text.
455 if(!mOverrideAutomaticAlignment)
457 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
458 bool leftToRight(true);
460 if( !styleText.empty() )
462 bool breakOut(false);
464 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
466 const Text& text = textIter->mText;
468 for( std::size_t i = 0; i < text.GetLength(); ++i )
470 Character character( text[i] );
471 if( character.GetCharacterDirection() != Character::Neutral )
473 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
481 // Based on this direction, either left or right align text if not manually set by application developer.
482 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
483 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
484 Toolkit::Alignment::VerticalTop ) );
485 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
489 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
491 mMaxStringLength = maxChars;
494 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
496 DALI_ASSERT_DEBUG( maxLines > 0 )
500 mNumberOflinesLimit = maxLines;
504 std::size_t TextInput::GetNumberOfLinesLimit() const
506 return mNumberOflinesLimit;
509 std::size_t TextInput::GetNumberOfCharacters() const
511 return mStyledText.size();
514 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
516 return mInputStartedSignalV2;
519 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
521 return mInputFinishedSignalV2;
524 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
526 return mCutAndPasteToolBarDisplayedV2;
529 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
531 return mStyleChangedSignalV2;
534 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
536 return mMaxInputCharactersReachedSignalV2;
539 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
541 return mInputTextExceedBoundariesSignalV2;
544 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
546 Dali::BaseHandle handle( object );
548 bool connected( true );
549 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
551 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
553 textInput.InputStartedSignal().Connect( tracker, functor );
555 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
557 textInput.InputFinishedSignal().Connect( tracker, functor );
559 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
561 textInput.StyleChangedSignal().Connect( tracker, functor );
563 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
565 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
567 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
569 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
573 // signalName does not match any signal
580 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
584 // update line height before calculate the actual position.
589 if( setCursorOnTouchPoint )
591 // Sets the cursor position for the given touch point.
592 ReturnClosestIndex( touchPoint, mCursorPosition );
594 // Creates the grab handle.
595 if( IsGrabHandleEnabled() )
597 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
601 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
602 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
603 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
604 ShowGrabHandleAndSetVisibility( true );
606 // Scrolls the text-view if needed.
607 if( IsScrollEnabled() )
609 ScrollTextViewToMakeCursorVisible( cursorPosition );
615 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
627 bool TextInput::IsEditable() const
629 return mEditModeActive;
632 void TextInput::SetEditOnTouch( bool editOnTouch )
634 mEditOnTouch = editOnTouch;
637 bool TextInput::IsEditOnTouch() const
642 void TextInput::SetTextSelectable( bool textSelectable )
644 mTextSelection = textSelectable;
647 bool TextInput::IsTextSelectable() const
649 return mTextSelection;
652 bool TextInput::IsTextSelected() const
654 return mHighlightMeshActor;
657 void TextInput::DeSelectText()
664 void TextInput::SetGrabHandleImage(Dali::Image image )
668 CreateGrabHandle(image);
672 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
674 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
678 mCursor.SetImage( image );
679 mCursor.SetNinePatchBorder( border );
683 Vector3 TextInput::GetSelectionHandleSize()
685 return DEFAULT_SELECTION_HANDLE_SIZE;
688 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
690 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
694 mCursorRTL.SetImage( image);
695 mCursorRTL.SetNinePatchBorder( border );
699 void TextInput::EnableGrabHandle(bool toggle)
701 // enables grab handle with will in turn de-activate magnifier
702 mGrabHandleEnabled = toggle;
705 bool TextInput::IsGrabHandleEnabled()
707 // if false then magnifier will be shown instead.
708 return mGrabHandleEnabled;
711 void TextInput::EnableSelectionHandleFlip( bool toggle )
713 // Deprecated function. To be removed.
714 mIsSelectionHandleFlipEnabled = toggle;
717 bool TextInput::IsSelectionHandleFlipEnabled()
719 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
723 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
725 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
726 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
727 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
729 mSelectionHandleFlipMargin = margin;
732 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
734 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
735 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
737 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
738 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
740 const Vector4 boundary( originX,
742 originX + boundingRectangle.width,
743 originY + boundingRectangle.height );
745 mBoundingRectangleWorldCoordinates = boundary;
748 const Rect<float> TextInput::GetBoundingRectangle() const
750 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
752 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
753 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
755 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
760 const Vector4& TextInput::GetSelectionHandleFlipMargin()
762 return mSelectionHandleFlipMargin;
765 void TextInput::SetTextColor( const Vector4& color )
767 mDisplayedTextView.SetColor( color );
770 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
772 if( style != mInputStyle )
775 bool emitSignal = false;
777 // mask: modify style according to mask, if different emit signal.
778 const TextStyle oldInputStyle( mInputStyle );
780 // Copy the new style.
781 mInputStyle.Copy( style, mask );
783 // if style has changed, emit signal.
784 if( oldInputStyle != mInputStyle )
789 // Updates the line height accordingly with the input style.
792 // Changing font point size will require the cursor to be re-sized
797 EmitStyleChangedSignal();
802 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
804 if ( IsTextSelected() )
806 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
807 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
809 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
811 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
814 // Keeps the old style to be compared with the new one.
815 const TextStyle oldInputStyle( mInputStyle );
817 // Copy only those parameters from the style which are set in the mask.
818 mInputStyle.Copy( style, mask );
820 if( mInputStyle != oldInputStyle )
822 // Updates the line height accordingly with the input style.
825 EmitStyleChangedSignal();
830 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
832 if( !mStyledText.empty() )
834 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
838 TextStyle TextInput::GetStyleAtCursor() const
842 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
844 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
846 style = mStyledText.at( mCursorPosition-1 ).mStyle;
852 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
854 Dali::Font defaultFont = Dali::Font::New();
855 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
862 TextStyle TextInput::GetStyleAt( std::size_t position ) const
864 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
866 if( position >= mStyledText.size() )
868 position = mStyledText.size() - 1;
871 return mStyledText.at( position ).mStyle;
874 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
876 mDisplayedTextView.SetTextAlignment( align );
877 mOverrideAutomaticAlignment = true;
880 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
882 mDisplayedTextView.SetLineJustification( justification );
883 mOverrideAutomaticAlignment = true;
886 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
888 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
891 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
893 return mDisplayedTextView.GetFadeBoundary();
896 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
898 return mDisplayedTextView.GetTextAlignment();
901 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
903 mDisplayedTextView.SetMultilinePolicy( policy );
906 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
908 return mDisplayedTextView.GetMultilinePolicy();
911 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
913 mDisplayedTextView.SetWidthExceedPolicy( policy );
916 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
918 return mDisplayedTextView.GetWidthExceedPolicy();
921 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
923 mDisplayedTextView.SetHeightExceedPolicy( policy );
926 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
928 return mDisplayedTextView.GetHeightExceedPolicy();
931 void TextInput::SetExceedEnabled( bool enable )
933 mExceedEnabled = enable;
936 bool TextInput::GetExceedEnabled() const
938 return mExceedEnabled;
941 void TextInput::SetBackground(Dali::Image image )
943 // TODO Should add this function and add public api to match.
946 bool TextInput::OnTouchEvent(const TouchEvent& event)
951 bool TextInput::OnKeyEvent(const KeyEvent& event)
953 switch( event.state )
957 return OnKeyDownEvent(event);
963 return OnKeyUpEvent(event);
975 void TextInput::OnKeyInputFocusGained()
977 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
979 mEditModeActive = true;
981 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
983 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
985 // Updates the line height accordingly with the input style.
988 ImfManager imfManager = ImfManager::Get();
990 // Connect the signals to use in text input.
991 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
992 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
994 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1000 SetCursorVisibility( true );
1001 StartCursorBlinkTimer();
1003 Toolkit::TextInput handle( GetOwner() );
1004 mInputStartedSignalV2.Emit( handle );
1006 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1008 // Notify that the text editing start.
1009 imfManager.Activate();
1011 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1012 imfManager.SetRestoreAferFocusLost( true );
1014 imfManager.SetCursorPosition( mCursorPosition );
1015 imfManager.NotifyCursorPosition();
1017 mClipboard = Clipboard::Get(); // Store handle to clipboard
1019 // Now in edit mode we can accept string to paste from clipboard
1020 if( Adaptor::IsAvailable() )
1022 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1025 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1030 void TextInput::OnKeyInputFocusLost()
1032 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1036 // If key input focus is lost, it removes the
1037 // underline from the last pre-edit text.
1038 RemovePreEditStyle();
1039 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1040 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1043 ImfManager imfManager = ImfManager::Get();
1045 // The text editing is finished. Therefore the imf manager don't have restore activation.
1046 imfManager.SetRestoreAferFocusLost( false );
1048 // Notify that the text editing finish.
1049 imfManager.Deactivate();
1051 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1053 // Disconnect signal used the text input.
1054 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1056 Toolkit::TextInput handle( GetOwner() );
1057 mInputFinishedSignalV2.Emit( handle );
1058 mEditModeActive = false;
1059 mPreEditFlag = false;
1061 SetCursorVisibility( false );
1062 StopCursorBlinkTimer();
1064 ShowGrabHandleAndSetVisibility( false );
1067 // No longer in edit mode so do not want to receive string from clipboard
1068 if( Adaptor::IsAvailable() )
1070 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1073 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1075 Clipboard clipboard = Clipboard::Get();
1076 clipboard.HideClipboard();
1080 void TextInput::OnControlStageConnection()
1082 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1084 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1086 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1090 void TextInput::CreateActiveLayer()
1092 Actor self = Self();
1093 mActiveLayer = Layer::New();
1095 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1096 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1097 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1099 self.Add( mActiveLayer );
1100 mActiveLayer.RaiseToTop();
1103 void TextInput::OnInitialize()
1105 CreateTextViewActor();
1109 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1110 // different positions depending on language)
1111 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1112 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1113 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1115 Actor self = Self();
1116 self.Add( mCursor );
1117 self.Add( mCursorRTL );
1119 mCursorVisibility = false;
1121 CreateActiveLayer(); // todo move this so layer only created when needed.
1123 // Assign names to image actors
1124 mCursor.SetName("mainCursor");
1125 mCursorRTL.SetName("rtlCursor");
1128 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1130 mDisplayedTextView.SetSize( targetSize );
1131 GetTextLayoutInfo();
1132 mActiveLayer.SetSize(targetSize);
1135 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1137 Relayout( mDisplayedTextView, size, container );
1138 GetTextLayoutInfo();
1143 Vector3 TextInput::GetNaturalSize()
1145 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1147 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1149 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1150 naturalSize.height = mLineHeight;
1156 float TextInput::GetHeightForWidth( float width )
1158 float height = mDisplayedTextView.GetHeightForWidth( width );
1160 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1162 // If the height is zero, it means there is no text. Let's return the cursor height.
1163 height = mLineHeight;
1169 /*end of Virtual methods from parent*/
1171 // Private Internal methods
1173 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1175 switch (gesture.state)
1177 case Gesture::Started:
1178 // fall through so code not duplicated
1179 case Gesture::Continuing:
1181 if (actor == mGrabArea)
1183 SetCursorVisibility( true );
1184 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1185 MoveGrabHandle( gesture.displacement );
1186 HidePopup(); // Do not show popup whilst handle is moving
1188 else if (actor == mHandleOneGrabArea)
1190 // the displacement in PanGesture is affected by the actor's rotation.
1191 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1192 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1194 MoveSelectionHandle( HandleOne, gesture.displacement );
1196 mState = StateDraggingHandle;
1199 else if (actor == mHandleTwoGrabArea)
1201 // the displacement in PanGesture is affected by the actor's rotation.
1202 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1203 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1205 MoveSelectionHandle( HandleTwo, gesture.displacement );
1207 mState = StateDraggingHandle;
1213 case Gesture::Finished:
1215 // Revert back to non-pressed selection handle images
1216 if (actor == mGrabArea)
1218 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1219 SetCursorVisibility( true );
1220 SetUpPopUpSelection();
1223 if (actor == mHandleOneGrabArea)
1225 // the displacement in PanGesture is affected by the actor's rotation.
1226 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1227 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1229 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1231 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1233 ShowPopupCutCopyPaste();
1235 if (actor == mHandleTwoGrabArea)
1237 // the displacement in PanGesture is affected by the actor's rotation.
1238 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1239 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1241 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1243 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1245 ShowPopupCutCopyPaste();
1254 // Stop the flashing animation so easy to see when moved.
1255 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1257 if (touch.GetPoint(0).state == TouchPoint::Down)
1259 SetCursorVisibility( true );
1260 StopCursorBlinkTimer();
1262 else if (touch.GetPoint(0).state == TouchPoint::Up)
1264 SetCursorVisibility( true );
1265 StartCursorBlinkTimer();
1270 // selection handle one
1271 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1273 if (touch.GetPoint(0).state == TouchPoint::Down)
1275 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1277 else if (touch.GetPoint(0).state == TouchPoint::Up)
1279 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1284 // selection handle two
1285 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1287 if (touch.GetPoint(0).state == TouchPoint::Down)
1289 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1291 else if (touch.GetPoint(0).state == TouchPoint::Up)
1293 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1298 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1300 // If text exists then select nearest word.
1301 if ( !mStyledText.empty())
1305 ShowGrabHandleAndSetVisibility( false );
1310 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1311 // converts the pre-edit word being displayed to a committed word.
1312 if ( !mUnderlinedPriorToPreEdit )
1315 style.SetUnderline( false );
1316 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1318 mPreEditFlag = false;
1319 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1320 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1321 PreEditReset( false );
1323 mCursorPosition = 0;
1325 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1326 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1328 ImfManager imfManager = ImfManager::Get();
1329 imfManager.SetCursorPosition ( mCursorPosition );
1330 imfManager.NotifyCursorPosition();
1332 std::size_t start = 0;
1333 std::size_t end = 0;
1334 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1336 SelectText( start, end );
1338 // if no text but clipboard has content then show paste option
1339 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1341 ShowPopupCutCopyPaste();
1344 // If no text and clipboard empty then do nothing
1347 // TODO: Change the function name to be more general.
1348 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1350 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1351 , (mEditOnTouch)?"true":"false"
1352 , (mEditModeActive)?"true":"false");
1354 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1359 if( mGrabArea == actor )
1361 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1363 SetUpPopUpSelection();
1373 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1375 // Initially don't create the grab handle.
1376 bool createGrabHandle = false;
1378 if ( !mEditModeActive )
1380 // update line height before calculate the actual position.
1383 // Only start edit mode if TextInput configured to edit on touch
1386 // Set the initial cursor position in the tap point.
1387 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1389 // Create the grab handle.
1390 // TODO Make this a re-usable function.
1391 if ( IsGrabHandleEnabled() )
1393 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1397 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1398 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1399 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1400 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1404 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1405 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1406 // otherwise the Grab handle will be shown when selecting.
1413 // Show the keyboard if it was hidden.
1414 if (!VirtualKeyboard::IsVisible())
1416 VirtualKeyboard::Show();
1419 // Reset keyboard as tap event has occurred.
1420 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1421 PreEditReset( true );
1423 GetTextLayoutInfo();
1425 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1427 // 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.
1429 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1431 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1433 // Notify keyboard so it can 're-capture' word for predictive text.
1434 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1435 ImfManager imfManager = ImfManager::Get();
1436 imfManager.SetCursorPosition ( mCursorPosition );
1437 imfManager.NotifyCursorPosition();
1439 const TextStyle oldInputStyle( mInputStyle );
1441 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1445 // Create the grab handle.
1446 // Grab handle is created later.
1447 createGrabHandle = true;
1449 if( oldInputStyle != mInputStyle )
1451 // Updates the line height accordingly with the input style.
1454 EmitStyleChangedSignal();
1459 if ( createGrabHandle && IsGrabHandleEnabled() )
1461 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1465 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1466 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1467 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1468 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1473 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1475 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1477 if(longPress.state == Dali::Gesture::Started)
1479 // Start edit mode on long press
1480 if ( !mEditModeActive )
1485 // If text exists then select nearest word.
1486 if ( !mStyledText.empty())
1490 ShowGrabHandleAndSetVisibility( false );
1495 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1496 // converts the pre-edit word being displayed to a committed word.
1497 if ( !mUnderlinedPriorToPreEdit )
1500 style.SetUnderline( false );
1501 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1503 mPreEditFlag = false;
1504 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1505 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1506 PreEditReset( false );
1508 mCursorPosition = 0;
1510 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1511 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1513 ImfManager imfManager = ImfManager::Get();
1514 imfManager.SetCursorPosition ( mCursorPosition );
1515 imfManager.NotifyCursorPosition();
1517 std::size_t start = 0;
1518 std::size_t end = 0;
1519 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1521 SelectText( start, end );
1524 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1525 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1527 ShowPopupCutCopyPaste();
1532 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1534 const Text clipboardText( notifier.GetContent() );
1535 PasteText( clipboardText );
1537 SetCursorVisibility( true );
1538 StartCursorBlinkTimer();
1540 ShowGrabHandleAndSetVisibility( false );
1546 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1548 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1550 const std::string& name = button.GetName();
1552 if(name == OPTION_SELECT_WORD)
1554 std::size_t start = 0;
1555 std::size_t end = 0;
1556 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1558 SelectText( start, end );
1560 else if(name == OPTION_SELECT_ALL)
1562 SetCursorVisibility(false);
1563 StopCursorBlinkTimer();
1565 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1566 std::size_t start = 0;
1568 SelectText( start, end );
1570 else if(name == OPTION_CUT)
1572 bool ret = CopySelectedTextToClipboard();
1576 DeleteHighlightedText( true );
1580 SetCursorVisibility( true );
1581 StartCursorBlinkTimer();
1585 else if(name == OPTION_COPY)
1587 CopySelectedTextToClipboard();
1591 SetCursorVisibility( true );
1592 StartCursorBlinkTimer();
1596 else if(name == OPTION_PASTE)
1598 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1600 PasteText(retrievedString);
1602 SetCursorVisibility( true );
1603 StartCursorBlinkTimer();
1605 ShowGrabHandleAndSetVisibility( false );
1610 else if(name == OPTION_CLIPBOARD)
1612 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1613 // Hence pass the false parameter for signalFinished.
1614 HidePopup( true, false );
1615 mClipboard.ShowClipboard();
1621 bool TextInput::OnCursorBlinkTimerTick()
1624 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1625 if ( mCursorRTLEnabled )
1627 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1629 mCursorBlinkStatus = !mCursorBlinkStatus;
1634 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1636 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1638 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1639 if(mHighlightMeshActor && mState == StateEdit)
1641 ShowPopupCutCopyPaste();
1645 //FIXME this routine needs to be re-written as it contains too many branches.
1646 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1648 std::string keyName = event.keyPressedName;
1649 std::string keyString = event.keyPressed;
1651 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1653 // Do not consume "Tab" and "Escape" keys.
1654 if(keyName == "Tab" || keyName == "Escape")
1656 // Escape key to end the edit mode
1662 HidePopup(); // If Pop-up shown then hides it as editing text.
1664 // Update Flag, indicates whether to update the text-input contents or not.
1665 // Any key stroke that results in a visual change of the text-input should
1666 // set this flag to true.
1669 // Whether to scroll text to cursor position.
1670 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1671 bool scroll = false;
1673 if (keyName == "Return")
1675 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1677 bool preEditFlagPreviouslySet( mPreEditFlag );
1679 if (mHighlightMeshActor)
1681 // replaces highlighted text with new line
1682 DeleteHighlightedText( false );
1684 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1686 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1687 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1690 mCommitByKeyInput = true;
1693 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1694 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1696 mPreEditFlag = true;
1697 mIgnoreCommitFlag = false;
1707 else if ( keyName == "space" )
1709 if ( mHighlightMeshActor )
1711 // Some text is selected so erase it before adding space.
1712 DeleteHighlightedText( true );
1716 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1718 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1719 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1722 mCommitByKeyInput = true;
1727 else if (keyName == "BackSpace")
1729 if ( mHighlightMeshActor )
1731 // Some text is selected so erase it
1732 DeleteHighlightedText( true );
1737 if ( mCursorPosition > 0 )
1739 DeleteCharacter( mCursorPosition );
1744 else if (keyName == "Right")
1749 else if (keyName == "Left")
1751 AdvanceCursor(true);
1754 else // event is a character
1756 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1757 if ( !keyString.empty() )
1759 if ( mHighlightMeshActor )
1761 // replaces highlighted text with new character
1762 DeleteHighlightedText( false );
1766 // Received key String
1767 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1772 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1773 // as this is a costly operation.
1779 if(update || scroll)
1781 if( IsScrollEnabled() )
1783 // Calculates the new cursor position (in actor coordinates)
1784 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1786 ScrollTextViewToMakeCursorVisible( cursorPosition );
1793 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1795 std::string keyName = event.keyPressedName;
1796 std::string keyString = event.keyPressed;
1798 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1800 // The selected text become deselected when the key code is DALI_KEY_BACK.
1801 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1810 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1812 // Updates the stored scroll position.
1813 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1815 const Vector3& controlSize = GetControlSize();
1816 Size cursorSize( CURSOR_THICKNESS, 0.f );
1818 // Updates the cursor and grab handle position and visibility.
1819 if( mGrabHandle || mCursor )
1821 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1822 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1824 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1826 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1830 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1831 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1836 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1837 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1841 // Updates the selection handles and highlighted text position and visibility.
1842 if( mSelectionHandleOne && mSelectionHandleTwo )
1844 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1845 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1846 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1847 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1848 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1849 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1851 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1852 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1854 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1855 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1856 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1857 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1859 if( mHighlightMeshActor )
1861 mHighlightMeshActor.SetVisible( true );
1867 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1869 // Scroll the text to make the cursor visible.
1870 const Size cursorSize( CURSOR_THICKNESS,
1871 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1873 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1875 const Vector3& controlSize = GetControlSize();
1877 // Calculates the new scroll position.
1878 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1879 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1881 scrollOffset.x += cursorPosition.x;
1884 if( cursorPosition.y - cursorSize.height < 0.f )
1886 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1888 else if( cursorPosition.y > controlSize.height )
1890 scrollOffset.y += cursorPosition.y;
1893 // Sets the new scroll position.
1894 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1895 SetScrollPosition( scrollOffset );
1898 void TextInput::StartScrollTimer()
1902 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1903 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1906 if( !mScrollTimer.IsRunning() )
1908 mScrollTimer.Start();
1912 void TextInput::StopScrollTimer()
1916 mScrollTimer.Stop();
1920 bool TextInput::OnScrollTimerTick()
1922 // TODO: need to set the new style accordingly the new handle position.
1924 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1926 // nothing to do if all handles are invisible or doesn't exist.
1932 // Choose between the grab handle or the selection handles.
1933 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1934 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1935 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1937 std::size_t newCursorPosition = 0;
1938 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1940 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1941 // the new selection handle's position needs to be different of the other one.
1942 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1943 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1944 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1946 if( differentSelectionHandles )
1948 handlePosition = newCursorPosition;
1950 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1952 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1954 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1955 scrollPosition += scrollDelta;
1956 SetScrollPosition( scrollPosition );
1958 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1963 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1966 actualHandlePosition.x += mScrollDisplacement.x;
1967 actualHandlePosition.y += mScrollDisplacement.y;
1972 // Public Internal Methods (public for testing purpose)
1974 void TextInput::SetUpTouchEvents()
1976 if ( !mTapDetector )
1978 mTapDetector = TapGestureDetector::New();
1979 // Attach the actors and connect the signal
1980 mTapDetector.Attach(Self());
1982 // As contains children which may register for tap the default control detector is not used.
1983 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
1986 if ( !mDoubleTapDetector )
1988 mDoubleTapDetector = TapGestureDetector::New();
1989 mDoubleTapDetector.SetTapsRequired( 2 );
1990 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
1992 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
1993 // so that we do not, unnecessarily, have a double tap request all the time
1996 if ( !mPanGestureDetector )
1998 mPanGestureDetector = PanGestureDetector::New();
1999 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2002 if ( !mLongPressDetector )
2004 mLongPressDetector = LongPressGestureDetector::New();
2005 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2006 mLongPressDetector.Attach(Self());
2010 void TextInput::CreateTextViewActor()
2012 mDisplayedTextView = Toolkit::TextView::New();
2013 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2014 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2015 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2016 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2017 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2018 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2019 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2020 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2021 mDisplayedTextView.SetSizePolicy( Control::Fixed, Control::Fixed );
2023 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2025 Self().Add( mDisplayedTextView );
2028 // Start a timer to initiate, used by the cursor to blink.
2029 void TextInput::StartCursorBlinkTimer()
2031 if ( !mCursorBlinkTimer )
2033 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2034 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2037 if ( !mCursorBlinkTimer.IsRunning() )
2039 mCursorBlinkTimer.Start();
2043 // Start a timer to initiate, used by the cursor to blink.
2044 void TextInput::StopCursorBlinkTimer()
2046 if ( mCursorBlinkTimer )
2048 mCursorBlinkTimer.Stop();
2052 void TextInput::StartEditMode()
2054 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2056 if(!mEditModeActive)
2061 if ( mDoubleTapDetector )
2063 mDoubleTapDetector.Attach( Self() );
2067 void TextInput::EndEditMode()
2069 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2071 ClearKeyInputFocus();
2073 if ( mDoubleTapDetector )
2075 mDoubleTapDetector.Detach( Self() );
2079 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2081 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2083 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2085 style.SetUnderline( true );
2086 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2090 void TextInput::RemovePreEditStyle()
2092 if ( !mUnderlinedPriorToPreEdit )
2095 style.SetUnderline( false );
2096 SetActiveStyle( style, TextStyle::UNDERLINE );
2100 // IMF related methods
2103 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2105 bool update( false );
2106 bool preeditResetRequired ( false );
2108 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2110 HidePopup(); // If Pop-up shown then hides it as editing text.
2113 switch ( imfEvent.eventName )
2115 case ImfManager::PREEDIT:
2117 mIgnoreFirstCommitFlag = false;
2119 // 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
2120 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2122 // replaces highlighted text with new character
2123 DeleteHighlightedText( false );
2126 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2128 if( IsScrollEnabled() )
2130 // Calculates the new cursor position (in actor coordinates)
2131 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2132 ScrollTextViewToMakeCursorVisible( cursorPosition );
2139 case ImfManager::COMMIT:
2141 if( mIgnoreFirstCommitFlag )
2143 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2144 mIgnoreFirstCommitFlag = false;
2148 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2150 // 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
2151 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2153 // replaces highlighted text with new character
2154 DeleteHighlightedText( false );
2157 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2158 // not needed, one such scenario is when the pre-edit word is too long to fit.
2159 if ( !mIgnoreCommitFlag )
2161 update = CommitReceived( imfEvent.predictiveString );
2165 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2171 if( IsScrollEnabled() )
2173 // Calculates the new cursor position (in actor coordinates)
2174 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2176 ScrollTextViewToMakeCursorVisible( cursorPosition );
2181 case ImfManager::DELETESURROUNDING:
2183 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2184 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2186 mPreEditFlag = false;
2188 std::size_t toDelete = 0;
2189 std::size_t numberOfCharacters = 0;
2191 if( mHighlightMeshActor )
2193 // delete highlighted text.
2194 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2195 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2199 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2201 toDelete = mCursorPosition + imfEvent.cursorOffset;
2203 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2205 numberOfCharacters = mStyledText.size() - toDelete;
2209 numberOfCharacters = imfEvent.numberOfChars;
2212 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2213 DeleteRange( toDelete, numberOfCharacters );
2215 mCursorPosition = toDelete;
2216 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2218 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2221 case ImfManager::GETSURROUNDING:
2223 // 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
2224 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2225 if (! ( mHighlightMeshActor || mSelectingText ) )
2227 std::string text( GetText() );
2228 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2230 imfManager.SetCursorPosition( mCursorPosition );
2231 imfManager.SetSurroundingText( text );
2234 if( 0 != mNumberOfSurroundingCharactersDeleted )
2236 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2237 mNumberOfSurroundingCharactersDeleted = 0;
2239 if( mStyledText.empty() )
2241 // Styled text is empty, so set the placeholder text.
2242 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2243 mPlaceHolderSet = true;
2248 case ImfManager::VOID:
2250 DALI_ASSERT_DEBUG( false );
2254 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2256 return callbackData;
2259 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2261 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2263 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2264 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2266 bool preeditResetRequest ( false );
2268 if( mPreEditFlag ) // Already in pre-edit state.
2270 if( mStyledText.size() >= mMaxStringLength )
2272 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2273 // Cannot fit these characters into field, clear pre-edit.
2274 if ( !mUnderlinedPriorToPreEdit )
2277 style.SetUnderline( false );
2278 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2280 mIgnoreCommitFlag = true;
2281 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2282 mPreEditFlag = false;
2283 EmitMaxInputCharactersReachedSignal();
2287 // delete existing pre-edit string
2288 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2290 // Store new pre-edit string
2291 mPreEditString.SetText( keyString );
2293 if ( keyString.empty() )
2295 mPreEditFlag = false;
2296 mCursorPosition = mPreEditStartPosition;
2298 if( mStyledText.empty() )
2300 // Styled text is empty, so set the placeholder text.
2301 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2302 mPlaceHolderSet = true;
2306 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2308 GetTextLayoutInfo();
2312 // Insert new pre-edit string. InsertAt updates the size and position table.
2313 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2314 // 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.
2315 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2316 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2317 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2319 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2323 else // mPreEditFlag not set
2325 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2327 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2328 // new pre-edit so move into pre-edit state by setting flag
2329 mPreEditFlag = true;
2330 mPreEditString.SetText( keyString ); // store new pre-edit string
2331 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2332 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2333 // If word was too long to be inserted then cursorOffset would be out of range as keyboard assumes there is not limit. Hence use of std::min.
2334 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2335 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2336 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2338 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2343 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2347 return preeditResetRequest;
2350 bool TextInput::CommitReceived(const std::string& keyString )
2352 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2353 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2355 bool update( false );
2357 RemovePreEditStyle();
2359 const std::size_t styledTextSize( mStyledText.size() );
2360 if( styledTextSize >= mMaxStringLength )
2362 // Cannot fit these characters into field, clear pre-edit.
2365 mIgnoreCommitFlag = true;
2366 mPreEditFlag = false;
2368 EmitMaxInputCharactersReachedSignal();
2374 // delete existing pre-edit string
2375 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2376 mPreEditFlag = false;
2378 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2379 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2381 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2383 // No need to update cursor position as Cursor location given by touch.
2384 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2385 mPreserveCursorPosition = false;
2389 // Cursor not set by touch so needs to be re-positioned to input more text
2390 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2392 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2393 if ( mCommitByKeyInput )
2395 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2396 mCommitByKeyInput = false;
2400 if ( mSelectTextOnCommit )
2402 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2407 else // mPreEditFlag not set
2409 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2411 if( mStyledText.empty() && mPlaceHolderSet )
2413 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2414 mDisplayedTextView.SetText( "" );
2415 mNumberOfSurroundingCharactersDeleted = 0;
2416 mPlaceHolderSet = false;
2418 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2420 mNumberOfSurroundingCharactersDeleted = 0;
2424 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2429 mSelectTextOnCommit = false;
2431 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2432 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2437 // End of IMF related methods
2439 std::size_t TextInput::DeletePreEdit()
2441 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2443 DALI_ASSERT_DEBUG( mPreEditFlag );
2445 const std::size_t preEditStringLength = mPreEditString.GetLength();
2446 const std::size_t styledTextSize = mStyledText.size();
2448 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2450 // Prevents erase items outside mStyledText bounds.
2451 if( mPreEditStartPosition > styledTextSize )
2453 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2454 mPreEditStartPosition = styledTextSize;
2457 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2459 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2460 endPosition = styledTextSize;
2463 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2465 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2466 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2468 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2470 return preEditStringLength;
2473 void TextInput::PreEditReset( bool preserveCursorPosition )
2475 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2476 preserveCursorPosition, mCursorPosition);
2478 // 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.
2479 mPreserveCursorPosition = preserveCursorPosition;
2481 // Reset incase we are in a pre-edit state.
2482 ImfManager::Get().Reset(); // Will trigger a commit message
2485 void TextInput::CursorUpdate()
2489 std::string text( GetText() );
2490 ImfManager imfManager = ImfManager::Get();
2491 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2492 imfManager.SetCursorPosition ( mCursorPosition );
2493 imfManager.NotifyCursorPosition();
2496 /* Delete highlighted characters redisplay*/
2497 void TextInput::DeleteHighlightedText( bool inheritStyle )
2499 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2501 if(mHighlightMeshActor)
2503 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2505 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2506 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2508 // Get the styled text of the characters to be deleted as it may be needed if
2509 // the "exceed the text-input's boundaries" option is disabled.
2510 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2512 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2514 mStyledText.erase( start, end ); // erase range of characters
2516 // Remove text from TextView.
2518 if( mStyledText.empty() )
2520 // Styled text is empty, so set the placeholder text.
2521 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2522 mPlaceHolderSet = true;
2526 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2528 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2530 // It may happen than after removing a white space or a new line character,
2531 // two words merge, this new word could be big enough to not fit in its
2532 // current line, so moved to the next one, and make some part of the text to
2533 // exceed the text-input's boundary.
2534 if( !mExceedEnabled )
2536 // Get the new text layout after removing some characters.
2537 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2539 // Get text-input's size.
2540 const Vector3& size = GetControlSize();
2542 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2543 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2545 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2547 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2548 styledCharactersToDelete.begin(),
2549 styledCharactersToDelete.end() );
2553 GetTextLayoutInfo();
2559 const TextStyle oldInputStyle( mInputStyle );
2561 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2563 if( oldInputStyle != mInputStyle )
2565 // Updates the line height accordingly with the input style.
2568 EmitStyleChangedSignal();
2574 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2576 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2577 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2579 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2582 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2584 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2585 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2587 mStyledText.erase(itStart, itEnd);
2589 // update the selection handles if they are visible.
2590 if( mHighlightMeshActor )
2592 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2593 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2595 if( minHandle >= start + ncharacters )
2597 minHandle -= ncharacters;
2599 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2604 if( maxHandle >= start + ncharacters )
2606 maxHandle -= ncharacters;
2608 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2614 // 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.
2617 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2619 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2620 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2621 // Mean we do not re-draw the text more than we have too.
2624 /* Delete character at current cursor position and redisplay*/
2625 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2627 // Ensure positionToDelete is not out of bounds.
2628 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2629 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2630 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2632 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2635 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2637 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2639 // Get the styled text of the character to be deleted as it may be needed if
2640 // the "exceed the text-input's boundaries" option is disabled.
2641 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2643 mStyledText.erase(it); // erase the character left of positionToDelete
2645 if( mStyledText.empty() )
2647 // Styled text is empty, so set the placeholder text.
2648 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2649 mPlaceHolderSet = true;
2653 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2655 const Character characterToDelete = styledCharacterToDelete.mText[0];
2657 // It may happen than after removing a white space or a new line character,
2658 // two words merge, this new word could be big enough to not fit in its
2659 // current line, so moved to the next one, and make some part of the text to
2660 // exceed the text-input's boundary.
2661 if( !mExceedEnabled )
2663 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2665 // Get the new text layout after removing one character.
2666 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2668 // Get text-input's size.
2669 const Vector3& size = GetControlSize();
2671 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2672 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2674 MarkupProcessor::StyledTextArray array;
2675 array.push_back( styledCharacterToDelete );
2676 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2678 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2683 GetTextLayoutInfo();
2685 ShowGrabHandleAndSetVisibility( false );
2687 mCursorPosition = positionToDelete -1;
2689 const TextStyle oldInputStyle( mInputStyle );
2691 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2693 if( oldInputStyle != mInputStyle )
2695 // Updates the line height accordingly with the input style.
2698 EmitStyleChangedSignal();
2703 /*Insert new character into the string and (optionally) redisplay text-input*/
2704 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2706 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2708 // Ensure insertionPosition is not out of bounds.
2709 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2711 bool textExceedsMaximunNumberOfCharacters = false;
2712 bool textExceedsBoundary = false;
2713 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2715 ShowGrabHandleAndSetVisibility( false );
2717 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2721 mIgnoreCommitFlag = true;
2722 mPreEditFlag = false;
2723 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2724 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2727 if( textExceedsMaximunNumberOfCharacters )
2729 EmitMaxInputCharactersReachedSignal();
2732 if( textExceedsBoundary )
2734 EmitInputTextExceedsBoundariesSignal();
2735 PreEditReset( false );
2739 return insertedStringLength;
2742 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2748 cursor = ImageActor::New( cursorImage );
2752 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2755 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2756 cursor.SetNinePatchBorder( border );
2758 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2759 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2760 cursor.SetVisible(false);
2765 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2767 // As cursor is not moving due to grab handle, handle should be hidden.
2768 ShowGrabHandleAndSetVisibility( false );
2770 bool cursorPositionChanged = false;
2773 if ( mCursorPosition >= places )
2775 mCursorPosition = mCursorPosition - places;
2776 cursorPositionChanged = true;
2781 if ((mCursorPosition + places) <= mStyledText.size())
2783 mCursorPosition = mCursorPosition + places;
2784 cursorPositionChanged = true;
2788 if( cursorPositionChanged )
2790 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2792 const TextStyle oldInputStyle( mInputStyle );
2793 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2797 if( oldInputStyle != mInputStyle )
2799 // Updates the line height accordingly with the input style.
2802 EmitStyleChangedSignal();
2805 ImfManager imfManager = ImfManager::Get();
2806 imfManager.SetCursorPosition ( mCursorPosition );
2807 imfManager.NotifyCursorPosition();
2811 void TextInput::DrawCursor(const std::size_t nthChar)
2813 // Get height of cursor and set its size
2814 Size size( CURSOR_THICKNESS, 0.0f );
2815 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2817 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2821 // Measure Font so know how big text will be if no initial text to measure.
2822 size.height = mLineHeight;
2825 mCursor.SetSize(size);
2827 // If the character is italic then the cursor also tilts.
2828 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2830 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2832 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2834 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2835 bool altPositionValid; // Alternate cursor validity flag.
2836 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2837 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2839 SetAltCursorEnabled( altPositionValid );
2841 if(!altPositionValid)
2843 mCursor.SetPosition( position + UI_OFFSET );
2847 size.height *= 0.5f;
2848 mCursor.SetSize(size);
2849 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2851 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2852 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2853 size.height = rowSize.height * 0.5f;
2854 mCursorRTL.SetSize(size);
2855 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2858 if( IsScrollEnabled() )
2860 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2861 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2866 void TextInput::SetAltCursorEnabled( bool enabled )
2868 mCursorRTLEnabled = enabled;
2869 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2872 void TextInput::SetCursorVisibility( bool visible )
2874 mCursorVisibility = visible;
2875 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2876 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2879 void TextInput::CreateGrabHandle( Dali::Image image )
2885 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2889 mGrabHandleImage = image;
2892 mGrabHandle = ImageActor::New(mGrabHandleImage);
2893 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2894 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2896 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2898 ShowGrabHandleAndSetVisibility( false );
2900 CreateGrabArea( mGrabHandle );
2902 mActiveLayer.Add(mGrabHandle);
2906 void TextInput::CreateGrabArea( Actor& parent )
2908 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2909 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2910 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2911 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2912 mTapDetector.Attach( mGrabArea );
2913 mPanGestureDetector.Attach( mGrabArea );
2915 parent.Add(mGrabArea);
2918 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2920 Vector3 actualHandlePosition;
2924 mActualGrabHandlePosition.x += displacement.x;
2925 mActualGrabHandlePosition.y += displacement.y;
2927 // Grab handle should jump to the nearest character and take cursor with it
2928 std::size_t newCursorPosition = 0;
2929 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2931 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2933 bool handleVisible = true;
2935 if( IsScrollEnabled() )
2937 const Vector3 controlSize = GetControlSize();
2938 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
2939 // Scrolls the text if the handle is not in a visible position
2940 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2947 mCurrentHandlePosition = actualHandlePosition;
2948 mScrollDisplacement = Vector2::ZERO;
2952 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
2954 mScrollDisplacement.x = -SCROLL_SPEED;
2956 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
2958 mScrollDisplacement.x = SCROLL_SPEED;
2960 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
2962 mScrollDisplacement.y = -SCROLL_SPEED;
2964 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
2966 mScrollDisplacement.y = SCROLL_SPEED;
2972 if( handleVisible && // Only redraw cursor and do updates if position changed
2973 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
2975 mCursorPosition = newCursorPosition;
2977 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
2979 const TextStyle oldInputStyle( mInputStyle );
2981 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
2983 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
2985 if( oldInputStyle != mInputStyle )
2987 // Updates the line height accordingly with the input style.
2990 EmitStyleChangedSignal();
2995 return actualHandlePosition;
2998 void TextInput::ShowGrabHandle( bool visible )
3000 if ( IsGrabHandleEnabled() )
3004 mGrabHandle.SetVisible( mGrabHandleVisibility );
3006 StartMonitoringStageForTouch();
3010 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3012 mGrabHandleVisibility = visible;
3013 ShowGrabHandle( visible );
3016 // Callbacks connected to be Property notifications for Boundary checking.
3018 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3020 mIsSelectionHandleOneFlipped = true;
3021 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3022 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3025 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3027 mIsSelectionHandleOneFlipped = false;
3028 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3029 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3032 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3034 mIsSelectionHandleTwoFlipped = true;
3035 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3036 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3039 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3041 mIsSelectionHandleTwoFlipped = false;
3042 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3043 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3046 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3047 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3049 mSelectionHandleOne.SetOpacity(0.0f);
3052 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3054 mSelectionHandleOne.SetOpacity(1.0f);
3057 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3059 mSelectionHandleTwo.SetOpacity(0.0f);
3062 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3064 mSelectionHandleTwo.SetOpacity(1.0f);
3067 // End of Callbacks connected to be Property notifications for Boundary checking.
3069 void TextInput::SetUpHandlePropertyNotifications()
3071 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3073 Vector3 handlesize = GetSelectionHandleSize();
3075 // Exceeding horizontal boundary
3076 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3077 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3079 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3080 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3082 // Within horizontal boundary
3083 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3084 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3086 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3087 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3089 // Exceeding vertical boundary
3090 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3091 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3092 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3093 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3095 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3096 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3097 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3098 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3100 // Within vertical boundary
3101 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3102 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3103 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3104 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3106 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3107 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3108 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3109 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3112 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3114 mSelectionHandleOnePosition = start;
3115 mSelectionHandleTwoPosition = end;
3117 if ( !mSelectionHandleOne )
3119 // create normal and pressed images
3120 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3121 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3123 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3124 mSelectionHandleOne.SetName("SelectionHandleOne");
3125 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3126 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3127 mIsSelectionHandleOneFlipped = false;
3128 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3130 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3131 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3133 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3134 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3136 mTapDetector.Attach( mHandleOneGrabArea );
3137 mPanGestureDetector.Attach( mHandleOneGrabArea );
3139 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3141 mSelectionHandleOne.Add( mHandleOneGrabArea );
3142 mActiveLayer.Add( mSelectionHandleOne );
3145 if ( !mSelectionHandleTwo )
3147 // create normal and pressed images
3148 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3149 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3151 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3152 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3153 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3154 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3155 mIsSelectionHandleTwoFlipped = false;
3156 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3158 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3159 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3160 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3161 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3163 mTapDetector.Attach( mHandleTwoGrabArea );
3164 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3166 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3168 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3170 mActiveLayer.Add( mSelectionHandleTwo );
3173 SetUpHandlePropertyNotifications();
3175 // update table as text may have changed.
3176 GetTextLayoutInfo();
3178 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3179 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3181 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3182 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3184 // Calculates and set the visibility if the scroll mode is enabled.
3185 bool isSelectionHandleOneVisible = true;
3186 bool isSelectionHandleTwoVisible = true;
3187 if( IsScrollEnabled() )
3189 const Vector3& controlSize( GetControlSize() );
3190 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3191 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3192 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3193 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3196 CreateHighlight(); // function will only create highlight if not already created.
3199 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3201 Vector3 actualHandlePosition;
3203 if ( mSelectionHandleOne && mSelectionHandleTwo )
3205 const Vector3& controlSize = GetControlSize();
3207 Size cursorSize( CURSOR_THICKNESS, 0.f );
3209 // Get a reference of the wanted selection handle (handle one or two).
3210 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3212 // Get a reference for the current position of the handle and a copy of its pair
3213 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3214 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3216 // Get a handle of the selection handle actor
3217 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3219 // Selection handles should jump to the nearest character
3220 std::size_t newHandlePosition = 0;
3221 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3223 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3225 bool handleVisible = true;
3227 if( IsScrollEnabled() )
3229 mCurrentSelectionId = handleId;
3231 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3232 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3233 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3240 mCurrentSelectionHandlePosition = actualHandlePosition;
3241 mScrollDisplacement = Vector2::ZERO;
3245 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3247 mScrollDisplacement.x = -SCROLL_SPEED;
3249 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3251 mScrollDisplacement.x = SCROLL_SPEED;
3253 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3255 mScrollDisplacement.y = -SCROLL_SPEED;
3257 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3259 mScrollDisplacement.y = SCROLL_SPEED;
3265 if ( handleVisible && // Ensure the handle is visible.
3266 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3267 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3269 currentSelectionHandlePosition = newHandlePosition;
3271 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3272 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3276 if ( handleId == HandleOne )
3278 const TextStyle oldInputStyle( mInputStyle );
3280 // Set Active Style to that of first character in selection
3281 if( mSelectionHandleOnePosition < mStyledText.size() )
3283 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3286 if( oldInputStyle != mInputStyle )
3288 // Updates the line height accordingly with the input style.
3291 EmitStyleChangedSignal();
3297 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3300 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3303 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3304 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3306 if ( selectionHandleActor )
3308 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3309 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3310 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3312 if( IsScrollEnabled() )
3314 const Size cursorSize( CURSOR_THICKNESS,
3315 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3316 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3318 GetControlSize() ) );
3323 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3325 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3326 // For now the visual position of end of logical string will be end of visual string.
3327 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3329 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3332 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3334 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3335 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3336 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3337 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3339 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3341 // Deselect text prior to startSelectionIt
3342 for(;it!=startSelectionIt;++it)
3344 selectedVisualText[*it] = false;
3347 // Select text from startSelectionIt -> endSelectionIt
3348 for(;it!=endSelectionIt;++it)
3350 selectedVisualText[*it] = true;
3353 // Deselect text after endSelection
3356 selectedVisualText[*it] = false;
3359 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3362 // Calculate the dimensions of the quads they will make the highlight mesh
3363 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3365 // At the moment there is no public API to modify the block alignment option.
3366 const bool blockAlignEnabled = true;
3368 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3370 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3372 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3373 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3375 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3376 std::vector<bool> selectedVisualText;
3377 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3378 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3379 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3381 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3382 float rowLeft = 0.0f;
3383 float rowRight = 0.0f;
3384 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3385 float maxRowLeft = std::numeric_limits<float>::max();
3386 float maxRowRight = 0.0f;
3388 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3390 // Scan through entire text.
3393 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3395 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3396 bool charSelected( false );
3397 if( selectedIt != selectedEndIt )
3399 charSelected = *selectedIt++;
3402 if(selectionState == SelectionNone)
3406 selectionState = SelectionStarted;
3407 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3408 rowRight = rowLeft + charInfo.mSize.width;
3411 else if(selectionState == SelectionStarted)
3413 // break selection on:
3414 // 1. new line causing selection break. (\n or wordwrap)
3415 // 2. character not selected.
3416 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3419 // finished selection.
3420 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3421 // that it resides on. That way this enumeration is not necessary.
3423 if(lastIt->mIsNewLineChar)
3425 // 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.
3426 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3428 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3429 maxRowLeft = std::min(maxRowLeft, min.x);
3430 maxRowRight = std::max(maxRowRight, max.x);
3431 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3432 float rowTop = rowBottom - rowSize.height;
3434 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3435 if(charSelected && blockAlignEnabled)
3437 rowRight = std::numeric_limits<float>::max();
3439 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3441 selectionState = SelectionNone;
3443 // Still selected? start a new selection
3446 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3447 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3448 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3449 selectionState = SelectionStarted;
3454 // build up highlight(s) with this selection data.
3455 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3456 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3463 // If reached end, and still on selection, then close selection.
3466 if(selectionState == SelectionStarted)
3468 // finished selection.
3470 if(lastIt->mIsNewLineChar)
3472 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3474 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3475 maxRowLeft = std::min(maxRowLeft, min.x);
3476 maxRowRight = std::max(maxRowRight, max.x);
3477 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3478 float rowTop = rowBottom - rowSize.height;
3479 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3483 // Get the top left and bottom right corners.
3484 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3485 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3486 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3488 // Clamp quads so they appear to clip to borders of the whole text.
3489 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3491 // For block-align align Further Clamp quads to max left and right extents
3492 if(blockAlignEnabled)
3494 // BlockAlign: Will adjust highlight to block:
3496 // H[ello] (top row right = max of all rows right)
3497 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3498 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3499 // [text] (bottom row left = min of all rows left)
3500 // (common in SMS messaging selection)
3502 // As opposed to the default which is tight text highlighting.
3507 // (common in regular text editors/web browser selection)
3509 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3512 // Finally clamp quads again so they don't exceed the boundry of the control.
3513 const Vector3& controlSize = GetControlSize();
3514 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3517 return mNewHighlightInfo;
3520 void TextInput::UpdateHighlight()
3522 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3524 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3526 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3527 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3528 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3529 // [BOTTOM] [ MIDDLE ]
3532 // Each quad is created as 2 triangles.
3533 // Middle is just 1 quad regardless of its size.
3547 if ( mHighlightMeshActor )
3549 // vertex and triangle buffers should always be present if MeshActor is alive.
3550 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3551 MeshData::VertexContainer vertices;
3552 Dali::MeshData::FaceIndices faceIndices;
3554 if( !newHighlightInfo.mQuadList.empty() )
3556 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3557 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3559 // vertex position defaults to (0 0 0)
3560 MeshData::Vertex vertex;
3561 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3564 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3566 // Add each quad geometry (a sub-selection) to the mesh data.
3576 QuadCoordinates& quad = *iter;
3578 vertex.x = quad.min.x;
3579 vertex.y = quad.min.y;
3580 vertices.push_back( vertex );
3583 vertex.x = quad.max.x;
3584 vertex.y = quad.min.y;
3585 vertices.push_back( vertex );
3587 // bottom-left (v+2)
3588 vertex.x = quad.min.x;
3589 vertex.y = quad.max.y;
3590 vertices.push_back( vertex );
3592 // bottom-right (v+3)
3593 vertex.x = quad.max.x;
3594 vertex.y = quad.max.y;
3595 vertices.push_back( vertex );
3597 // triangle A (3, 1, 0)
3598 faceIndices.push_back( v + 3 );
3599 faceIndices.push_back( v + 1 );
3600 faceIndices.push_back( v );
3602 // triangle B (0, 2, 3)
3603 faceIndices.push_back( v );
3604 faceIndices.push_back( v + 2 );
3605 faceIndices.push_back( v + 3 );
3607 mMeshData.SetFaceIndices( faceIndices );
3610 BoneContainer bones(0); // passed empty as bones not required
3611 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3612 mHighlightMesh.UpdateMeshData(mMeshData);
3617 void TextInput::ClearPopup()
3619 mPopUpPanel.Clear();
3622 void TextInput::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
3624 mPopUpPanel.AddOption(name, caption, icon, finalOption);
3627 void TextInput::SetPopupPosition(const Vector3& position)
3629 mPopUpPanel.Self().SetPosition( position );
3632 void TextInput::HidePopup(bool animate, bool signalFinished )
3634 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3636 mPopUpPanel.Hide( animate );
3638 if( animate && signalFinished )
3640 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3645 void TextInput::ShowPopup(bool animate)
3649 if(mHighlightMeshActor && mState == StateEdit)
3653 // When text is selected, show popup above top handle (and text), or below bottom handle.
3654 // topHandle: referring to the top most point of the handle or the top line of selection.
3655 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3657 topHandle = mSelectionHandleOneActualPosition;
3658 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3662 topHandle = mSelectionHandleTwoActualPosition;
3663 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3665 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3666 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3668 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3669 Vector3 bottomHandle;
3670 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3671 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3672 mPopUpPanel.SetAlternativeOffset(Vector2(0.0f, bottomHandle.y - topHandle.y));
3676 // When no text is selected, show popup at world position of grab handle or cursor
3677 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3678 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3679 position.y -= rowSize.height;
3680 // if can't be positioned above, then position below row.
3681 Vector2 alternativePopUpPosition( 0.0f, position.y ); // default if no grab handle
3684 alternativePopUpPosition.y = rowSize.height + ( mGrabHandle.GetCurrentSize().height * DEFAULT_GRAB_HANDLE_RELATIVE_SIZE.y ) ;
3685 // If grab handle enabled then position pop-up below the grab handle.
3687 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3690 // reposition popup above the desired cursor posiiton.
3691 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3692 textViewSize.z = 0.0f;
3693 // World position = world position of ParentOrigin of cursor (i.e. top-left corner of TextView) + cursor position;
3694 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3696 SetPopupPosition( worldPosition );
3699 mPopUpPanel.Show(animate);
3700 StartMonitoringStageForTouch();
3702 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3705 void TextInput::ShowPopupCutCopyPaste()
3708 // Check the selected text is whole text or not.
3709 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3711 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3712 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3715 if ( !mStyledText.empty() )
3717 Image cutIcon = Image::New( DEFAULT_ICON_CUT );
3718 Image copyIcon = Image::New( DEFAULT_ICON_COPY );
3719 AddPopupOption( OPTION_CUT, GET_LOCALE_TEXT("IDS_COM_BODY_CUT"), cutIcon );
3720 AddPopupOption( OPTION_COPY, GET_LOCALE_TEXT("IDS_COM_BODY_COPY"), copyIcon, true );
3723 if(mClipboard.NumberOfItems())
3725 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3726 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3727 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon );
3728 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3731 mPopUpPanel.Hide(false);
3735 void TextInput::SetUpPopUpSelection()
3739 // If no text exists then don't offer to select
3740 if ( !mStyledText.empty() )
3742 Image selectIcon = Image::New( DEFAULT_ICON_SELECT );
3743 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3744 AddPopupOption( OPTION_SELECT_WORD, GET_LOCALE_TEXT("IDS_COM_SK_SELECT"), selectIcon );
3745 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3747 // if clipboard has valid contents then offer paste option
3748 if( mClipboard.NumberOfItems() )
3750 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3751 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3752 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon, true );
3753 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3756 mPopUpPanel.Hide(false);
3759 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3764 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3765 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3766 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3767 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3769 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3771 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3773 float closestYdifference = std::numeric_limits<float>::max();
3774 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3775 std::size_t numberOfMatchedCharacters = 0;
3777 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3778 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3780 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3782 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3783 float baselinePosition = info.mPosition.y - info.mDescender;
3785 if( info.mIsVisible )
3787 // store difference between source y point and the y position of the current character
3788 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3790 if( currentYdifference < closestYdifference )
3792 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3793 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3794 closestYdifference = currentYdifference;
3795 matchedCharacters.clear();
3796 numberOfMatchedCharacters = 0; // reset count
3799 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3800 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3802 // ignore new line character.
3803 if( !info.mIsNewLineChar )
3805 matchedCharacters.push_back( info );
3806 numberOfMatchedCharacters++;
3810 } // End of loop checking each character's y position in the character layout table
3812 // Check if last character is a newline, if it is
3813 // then need pretend there is an imaginary line afterwards,
3814 // and check if user is touching below previous line.
3815 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3817 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3819 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3823 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3824 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3826 bool matched( false );
3828 // 2 Iterate through matching list of y positions and find closest matching X position.
3829 for( ; it != endIt; ++it )
3831 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3833 if( info.mIsVisible )
3835 // stop when on left side of character's center.
3836 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3837 if( sourceScrollOffset.x < characterMidPointPosition )
3839 if(info.mIsRightToLeftCharacter)
3841 rightToLeftChar = true;
3843 glyphIntersection = info.mPosition.x;
3848 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3854 rightToLeftChar = lastRightToLeftChar;
3857 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3858 closestIndex = lineOffset + matchCharacterIndex;
3860 mClosestCursorPositionEOL = false; // reset
3861 if ( it == endIt && !matched )
3863 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3866 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3867 if( rightToLeftChar && lastRightToLeftChar )
3869 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3874 // closestIndex is the visual index, need to convert it to the logical index
3875 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3877 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3879 // Checks for situations where user is touching between LTR and RTL
3880 // characters. To identify if the user means the end of a LTR string
3881 // or the beginning of an RTL string, and vice versa.
3882 if( closestIndex > 0 )
3884 if( rightToLeftChar && !lastRightToLeftChar )
3889 // A: In this touch range, the user is indicating that they wish to place
3890 // the cursor at the end of the LTR text.
3891 // B: In this touch range, the user is indicating that they wish to place
3892 // the cursor at the end of the RTL text.
3894 // Result of touching A area:
3895 // [.....LTR]|[RTL......]+
3897 // |: primary cursor (for typing LTR chars)
3898 // +: secondary cursor (for typing RTL chars)
3900 // Result of touching B area:
3901 // [.....LTR]+[RTL......]|
3903 // |: primary cursor (for typing RTL chars)
3904 // +: secondary cursor (for typing LTR chars)
3906 if( sourceScrollOffset.x < glyphIntersection )
3911 else if( !rightToLeftChar && lastRightToLeftChar )
3913 if( sourceScrollOffset.x < glyphIntersection )
3920 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3921 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3922 // one further ahead
3923 if( rightToLeftChar && !lastRightToLeftChar )
3928 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3930 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3932 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3941 float TextInput::GetLineJustificationPosition() const
3943 const Vector3& size = mDisplayedTextView.GetCurrentSize();
3944 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
3945 float alignmentOffset = 0.f;
3947 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
3948 if( alignment & Toolkit::Alignment::HorizontalLeft )
3950 alignmentOffset = 0.f;
3952 else if( alignment & Toolkit::Alignment::HorizontalCenter )
3954 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
3956 else if( alignment & Toolkit::Alignment::HorizontalRight )
3958 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
3961 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
3962 float justificationOffset = 0.f;
3964 switch( justification )
3966 case Toolkit::TextView::Left:
3968 justificationOffset = 0.f;
3971 case Toolkit::TextView::Center:
3973 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
3976 case Toolkit::TextView::Right:
3978 justificationOffset = mTextLayoutInfo.mTextSize.width;
3981 case Toolkit::TextView::Justified:
3983 justificationOffset = 0.f;
3988 DALI_ASSERT_ALWAYS( false );
3992 return alignmentOffset + justificationOffset;
3995 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
3997 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
3998 A newline character is not inserted in this case */
4000 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4002 Vector3 cursorPosition;
4004 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4006 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4008 // end character so use
4009 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4010 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4014 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4017 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4019 // If previous character on a different line then use current characters position
4020 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4022 if ( mClosestCursorPositionEOL )
4024 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4028 cursorPosition = Vector3(currentCharInfo.mPosition);
4033 // Previous character is on same line so use position of previous character plus it's width.
4034 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4037 return cursorPosition;
4040 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4042 bool direction(false);
4043 Vector3 alternatePosition;
4044 bool alternatePositionValid(false);
4046 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4049 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4051 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4053 alternatePositionValid = false;
4054 directionRTL = false;
4056 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4058 std::size_t visualCharacterPosition;
4060 // When cursor is not at beginning, consider possibility of
4061 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4062 if(characterPosition > 0)
4064 // Cursor position should be the end of the last character.
4065 // If the last character is LTR, then the end is on the right side of the glyph.
4066 // If the last character is RTL, then the end is on the left side of the glyph.
4067 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4069 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4071 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4074 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4075 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4077 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4078 const Vector3& size = GetControlSize();
4080 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4082 --visualCharacterPosition;
4084 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4087 if(!info.mIsNewLineChar)
4089 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4093 // When cursor points to first character on new line, position cursor at the start of this glyph.
4094 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4096 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4097 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4098 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4100 cursorPosition.x = infoNext.mPosition.x + start;
4101 cursorPosition.y = infoNext.mPosition.y;
4105 // If cursor points to the end of text, then can only position
4106 // cursor where the new line starts based on the line-justification position.
4107 cursorPosition.x = GetLineJustificationPosition();
4109 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4111 // If this is after the last character, then we can assume that the new cursor
4112 // should be exactly one row below the current row.
4114 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4115 cursorPosition.y = info.mPosition.y + rowRect.height;
4119 // If this is not after last character, then we can use this row's height.
4120 // should be exactly one row below the current row.
4122 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4123 cursorPosition.y = info.mPosition.y + rowRect.height;
4128 directionRTL = info.mIsRightToLeftCharacter;
4130 // 1. When the cursor is neither at the beginning or the end,
4131 // we can show multiple cursors under situations when the cursor is
4132 // between RTL and LTR text...
4133 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4135 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4137 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4138 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4140 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4142 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4143 // Text: [...LTR...]|[...RTL...]
4145 // Alternate cursor pos: ^
4146 // In which case we need to display an alternate cursor for the RTL text.
4148 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4149 alternatePosition.y = infoAlt.mPosition.y;
4150 alternatePositionValid = true;
4152 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4154 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4155 // Text: |[...RTL...] [...LTR....]
4157 // Alternate cursor pos: ^
4158 // In which case we need to display an alternate cursor for the RTL text.
4160 alternatePosition.x = infoAlt.mPosition.x;
4161 alternatePosition.y = infoAlt.mPosition.y;
4162 alternatePositionValid = true;
4167 // 2. When the cursor is at the end of the text,
4168 // and we have multi-directional text,
4169 // we can also consider showing mulitple cursors.
4170 // The rule here is:
4171 // If first and last characters on row are different
4172 // Directions, then two cursors need to be displayed.
4174 // Get first logical glyph on row
4175 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4177 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4178 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4180 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4182 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4183 // Text: [...LTR...]|[...RTL...]
4185 // Alternate cursor pos: ^
4186 // In which case we need to display an alternate cursor for the RTL text, this cursor
4187 // should be at the end of the given line.
4189 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4190 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4191 alternatePosition.y = infoAlt.mPosition.y;
4192 alternatePositionValid = true;
4194 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4196 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4197 // Text: |[...RTL...] [...LTR....]
4199 // Alternate cursor pos: ^
4200 // In which case we need to display an alternate cursor for the RTL text.
4202 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4203 alternatePosition.x = infoAlt.mPosition.x;
4204 alternatePosition.y = infoAlt.mPosition.y;
4205 alternatePositionValid = true;
4208 } // characterPosition > 0
4209 else if(characterPosition == 0)
4211 // When the cursor position is at the beginning, it should be at the start of the current character.
4212 // If the current character is LTR, then the start is on the right side of the glyph.
4213 // If the current character is RTL, then the start is on the left side of the glyph.
4214 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4216 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4218 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4221 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4222 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4224 cursorPosition.x = info.mPosition.x + start;
4225 cursorPosition.y = info.mPosition.y;
4226 directionRTL = info.mIsRightToLeftCharacter;
4231 // If the character table is void, place the cursor accordingly the text alignment.
4232 const Vector3& size = GetControlSize();
4234 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4235 float alignmentOffset = 0.f;
4237 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4238 if( alignment & Toolkit::Alignment::HorizontalLeft )
4240 alignmentOffset = 0.f;
4242 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4244 alignmentOffset = 0.5f * ( size.width );
4246 else if( alignment & Toolkit::Alignment::HorizontalRight )
4248 alignmentOffset = size.width;
4251 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4252 cursorPosition.x = alignmentOffset;
4254 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4255 if( alignment & Toolkit::Alignment::VerticalTop )
4257 cursorPosition.y = mLineHeight;
4259 else if( alignment & Toolkit::Alignment::VerticalCenter )
4261 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4263 else if( alignment & Toolkit::Alignment::VerticalBottom )
4265 cursorPosition.y = size.height;
4269 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4270 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4271 if( alternatePositionValid )
4273 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4274 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4277 return cursorPosition;
4280 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4282 // scan string from current position to beginning of current line to note direction of line
4283 while(logicalPosition)
4286 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4287 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4294 return logicalPosition;
4297 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4301 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4304 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4306 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4307 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4309 min = Vector2::ZERO;
4310 max = Vector2(0.0f, mLineHeight);
4314 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4315 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4316 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4318 // If cursor is pointing to end of line, then start from last character.
4319 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4321 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4323 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4324 if( !it->mIsVisible )
4326 characterPosition = FindVisibleCharacter( Left, characterPosition );
4327 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4330 // Scan characters left and right of cursor, stopping when end of line/string reached or
4331 // y position greater than threshold of reference line.
4333 // 1. scan left until we reach the beginning or a different line.
4334 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4335 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4336 // min-x position is the left-most char's left (x)
4337 // max-x position is the right-most char's right (x)
4338 // min-y position is the minimum of all character's top (y)
4339 // max-y position is the maximum of all character's bottom (y+height)
4340 min.y = validCharIt->mPosition.y;
4341 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4346 min.y = std::min(min.y, validCharIt->mPosition.y);
4347 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4356 if( (it->mPosition.y < referenceLine) ||
4357 (it->mIsNewLineChar) ||
4364 // info refers to the first character on this line.
4365 min.x = validCharIt->mPosition.x;
4367 // 2. scan right until we reach end or a different line
4368 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4369 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4373 if( (it->mPosition.y > referenceLine) ||
4374 (it->mIsNewLineChar) ||
4381 min.y = std::min(min.y, validCharIt->mPosition.y);
4382 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4387 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4389 if ( validCharIt != end )
4391 // info refers to the last character on this line.
4392 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4395 return Size( max.x - min.x, max.y - min.y );
4398 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4400 Actor popUpPanel = mPopUpPanel.GetRootActor();
4402 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4408 Dali::Actor parent( touchedActor.GetParent() );
4412 return WasTouchedCheck( parent );
4419 void TextInput::StartMonitoringStageForTouch()
4421 Stage stage = Stage::GetCurrent();
4422 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4425 void TextInput::EndMonitoringStageForTouch()
4427 Stage stage = Stage::GetCurrent();
4428 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4431 void TextInput::OnStageTouched(const TouchEvent& event)
4433 if( event.GetPointCount() > 0 )
4435 if ( TouchPoint::Down == event.GetPoint(0).state )
4437 const Actor touchedActor(event.GetPoint(0).hitActor);
4439 bool popUpShown( false );
4441 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4446 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4448 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4450 EndMonitoringStageForTouch();
4451 HidePopup( true, false );
4454 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4456 EndMonitoringStageForTouch();
4457 ShowGrabHandleAndSetVisibility( false );
4463 void TextInput::SelectText(std::size_t start, std::size_t end)
4465 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4466 IsGrabHandleEnabled()?"true":"false",
4467 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4468 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4469 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4471 StartMonitoringStageForTouch();
4473 if ( mEditModeActive ) // Only allow text selection when in edit mode
4475 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4476 mSelectingText = true;
4478 ImfManager imfManager = ImfManager::Get();
4479 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4480 imfManager.SetCursorPosition ( mCursorPosition );
4481 imfManager.SetSurroundingText( GetText() );
4482 imfManager.NotifyCursorPosition();
4483 // 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.
4485 // Hide grab handle when selecting.
4486 ShowGrabHandleAndSetVisibility( false );
4488 if( start != end ) // something to select
4490 SetCursorVisibility( false );
4491 StopCursorBlinkTimer();
4493 CreateSelectionHandles(start, end);
4496 const TextStyle oldInputStyle( mInputStyle );
4497 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4499 if( oldInputStyle != mInputStyle )
4501 // Updates the line height accordingly with the input style.
4504 EmitStyleChangedSignal();
4510 mSelectingText = false;
4514 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4516 MarkupProcessor::StyledTextArray currentSelectedText;
4518 if ( IsTextSelected() )
4520 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4521 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4523 for(; it != end; ++it)
4525 MarkupProcessor::StyledText& styledText( *it );
4526 currentSelectedText.push_back( styledText );
4529 return currentSelectedText;
4532 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4534 const std::size_t beginIndex = std::min( begin, end );
4535 const std::size_t endIndex = std::max( begin, end );
4538 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4540 // Create a styled text array used to replace the text into the text-view.
4541 MarkupProcessor::StyledTextArray text;
4542 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4544 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4545 GetTextLayoutInfo();
4547 if( IsScrollEnabled() )
4549 // Need to set the scroll position as the text's size may have changed.
4550 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4553 ShowGrabHandleAndSetVisibility( false );
4559 // Set Handle positioning as the new style may have repositioned the characters.
4560 SetSelectionHandlePosition(HandleOne);
4561 SetSelectionHandlePosition(HandleTwo);
4564 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4566 // Just hide the grab handle when keyboard is hidden.
4567 if (!keyboardShown )
4569 ShowGrabHandleAndSetVisibility( false );
4571 // If the keyboard is not now being shown, then hide the popup panel
4572 mPopUpPanel.Hide( true );
4576 // Removes highlight and resumes edit mode state
4577 void TextInput::RemoveHighlight()
4579 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4581 if ( mHighlightMeshActor )
4583 if ( mSelectionHandleOne )
4585 mActiveLayer.Remove( mSelectionHandleOne );
4586 mSelectionHandleOne.Reset();
4587 mSelectionHandleOneOffset.x = 0.0f;
4589 if ( mSelectionHandleTwo )
4591 mActiveLayer.Remove( mSelectionHandleTwo );
4592 mSelectionHandleTwo.Reset();
4593 mSelectionHandleTwoOffset.x = 0.0f;
4596 mNewHighlightInfo.mQuadList.clear();
4598 Self().Remove( mHighlightMeshActor );
4600 SetCursorVisibility( true );
4601 StartCursorBlinkTimer();
4603 mHighlightMeshActor.Reset();
4604 // NOTE: We cannot dereference mHighlightMesh, due
4605 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4610 mSelectionHandleOnePosition = 0;
4611 mSelectionHandleTwoPosition = 0;
4614 void TextInput::CreateHighlight()
4616 if ( !mHighlightMeshActor )
4618 mMeshData = MeshData( );
4619 mMeshData.SetHasNormals( true );
4621 mCustomMaterial = Material::New("CustomMaterial");
4622 mCustomMaterial.SetDiffuseColor( LIGHTBLUE );
4624 mMeshData.SetMaterial( mCustomMaterial );
4626 mHighlightMesh = Mesh::New( mMeshData );
4628 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4629 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4630 mHighlightMeshActor.SetInheritShaderEffect( false );
4631 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4632 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4633 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4634 mHighlightMeshActor.SetAffectedByLighting(false);
4636 Self().Add(mHighlightMeshActor);
4641 bool TextInput::CopySelectedTextToClipboard()
4643 mCurrentCopySelecton.clear();
4645 mCurrentCopySelecton = GetSelectedText();
4647 std::string stringToStore;
4649 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4650 * a marked up string.
4652 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4653 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4654 bool success = mClipboard.SetItem( stringToStore );
4658 void TextInput::PasteText( const Text& text )
4660 // Update Flag, indicates whether to update the text-input contents or not.
4661 // Any key stroke that results in a visual change of the text-input should
4662 // set this flag to true.
4663 bool update = false;
4664 if( mHighlightMeshActor )
4666 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4667 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4669 ImfManager imfManager = ImfManager::Get();
4670 imfManager.SetCursorPosition( mCursorPosition );
4671 imfManager.NotifyCursorPosition();
4672 DeleteHighlightedText( true );
4676 bool textExceedsMaximunNumberOfCharacters = false;
4677 bool textExceedsBoundary = false;
4679 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4681 mCursorPosition += insertedStringLength;
4682 ImfManager imfManager = ImfManager::Get();
4683 imfManager.SetCursorPosition ( mCursorPosition );
4684 imfManager.NotifyCursorPosition();
4686 update = update || ( insertedStringLength > 0 );
4692 if( insertedStringLength < text.GetLength() )
4694 EmitMaxInputCharactersReachedSignal();
4697 if( textExceedsBoundary )
4699 EmitInputTextExceedsBoundariesSignal();
4703 void TextInput::SetTextDirection()
4705 // Put the cursor to the right if we are empty and an RTL language is being used.
4706 if ( mStyledText.empty() )
4708 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4710 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4711 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4713 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4714 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4716 int alignment( mDisplayedTextView.GetTextAlignment() &
4717 ( Toolkit::Alignment::VerticalTop |
4718 Toolkit::Alignment::VerticalCenter |
4719 Toolkit::Alignment::VerticalBottom |
4720 Toolkit::Alignment::HorizontalCenter ) );
4721 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4723 // If our alignment is in the center, then do not change.
4724 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4726 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4729 // If our justification is in the center, then do not change.
4730 if ( justification != Toolkit::TextView::Center )
4732 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4735 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4736 mDisplayedTextView.SetLineJustification( justification );
4740 void TextInput::UpdateLineHeight()
4742 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4743 mLineHeight = font.GetLineHeight();
4745 // 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.
4747 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4749 if( !mExceedEnabled || shrink )
4751 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4755 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4757 std::size_t position = 0;
4759 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4765 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4767 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4769 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4775 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4776 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4778 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4784 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4789 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4796 void TextInput::SetSortModifier( float depthOffset )
4798 if(mDisplayedTextView)
4800 mDisplayedTextView.SetSortModifier(depthOffset);
4804 void TextInput::SetSnapshotModeEnabled( bool enable )
4806 if(mDisplayedTextView)
4808 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4812 bool TextInput::IsSnapshotModeEnabled() const
4814 bool snapshotEnabled = false;
4816 if(mDisplayedTextView)
4818 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4821 return snapshotEnabled;
4824 void TextInput::SetScrollEnabled( bool enable )
4826 if( mDisplayedTextView )
4828 mDisplayedTextView.SetScrollEnabled( enable );
4833 // Don't set cursor's and handle's visibility to false if they are outside the
4834 // boundaries of the text-input.
4835 mIsCursorInScrollArea = true;
4836 mIsGrabHandleInScrollArea = true;
4837 if( mSelectionHandleOne && mSelectionHandleTwo )
4839 mSelectionHandleOne.SetVisible( true );
4840 mSelectionHandleTwo.SetVisible( true );
4842 if( mHighlightMeshActor )
4844 mHighlightMeshActor.SetVisible( true );
4850 bool TextInput::IsScrollEnabled() const
4852 bool scrollEnabled = false;
4854 if( mDisplayedTextView )
4856 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4859 return scrollEnabled;
4862 void TextInput::SetScrollPosition( const Vector2& position )
4864 if( mDisplayedTextView )
4866 mDisplayedTextView.SetScrollPosition( position );
4870 Vector2 TextInput::GetScrollPosition() const
4872 Vector2 scrollPosition;
4874 if( mDisplayedTextView )
4876 scrollPosition = mDisplayedTextView.GetScrollPosition();
4879 return scrollPosition;
4882 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4884 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4885 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4886 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4888 // Add style to the new input text.
4889 MarkupProcessor::StyledTextArray textToInsert;
4890 for( std::size_t i = 0; i < insertedStringLength; ++i )
4892 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4893 textToInsert.push_back( newStyledCharacter );
4896 //Insert text to the TextView.
4897 const bool emptyTextView = mStyledText.empty();
4898 if( emptyTextView && mPlaceHolderSet )
4900 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4901 mDisplayedTextView.SetText( textToInsert );
4905 if( 0 == numberOfCharactersToReplace )
4907 mDisplayedTextView.InsertTextAt( position, textToInsert );
4911 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4914 mPlaceHolderSet = false;
4916 if( textToInsert.empty() )
4918 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4919 GetTextLayoutInfo();
4923 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4924 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4927 textExceedsBoundary = false;
4929 if( !mExceedEnabled )
4931 const Vector3& size = GetControlSize();
4933 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
4935 // If new text does not fit within TextView
4936 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
4937 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
4938 GetTextLayoutInfo();
4939 textExceedsBoundary = true;
4940 insertedStringLength = 0;
4943 if( textExceedsBoundary )
4945 // Add the part of the text which fits on the text-input.
4947 // Split the text which doesn't fit in two halves.
4948 MarkupProcessor::StyledTextArray firstHalf;
4949 MarkupProcessor::StyledTextArray secondHalf;
4950 SplitText( textToInsert, firstHalf, secondHalf );
4952 // Clear text. This text will be filled with the text inserted.
4953 textToInsert.clear();
4955 // Where to insert the text.
4956 std::size_t positionToInsert = position;
4958 bool end = text.GetLength() <= 1;
4961 // Insert text and check ...
4962 const std::size_t textLength = firstHalf.size();
4963 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
4964 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4966 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
4968 // Inserted text doesn't fit.
4970 // Remove inserted text
4971 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
4972 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4974 // The iteration finishes when only one character doesn't fit.
4975 end = textLength <= 1;
4979 // Prepare next two halves for next iteration.
4980 MarkupProcessor::StyledTextArray copyText = firstHalf;
4981 SplitText( copyText, firstHalf, secondHalf );
4988 // store text to be inserted in mStyledText.
4989 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
4991 // Increase the inserted characters counter.
4992 insertedStringLength += textLength;
4994 // Prepare next two halves for next iteration.
4995 MarkupProcessor::StyledTextArray copyText = secondHalf;
4996 SplitText( copyText, firstHalf, secondHalf );
4998 // Update where next text has to be inserted
4999 positionToInsert += textLength;
5005 if( textToInsert.empty() && emptyTextView )
5007 // No character has been added and the text-view was empty.
5008 // Set the placeholder text.
5009 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5010 mPlaceHolderSet = true;
5014 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5015 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5016 mPlaceHolderSet = false;
5019 return insertedStringLength;
5022 void TextInput::GetTextLayoutInfo()
5024 if( mStyledText.empty() )
5026 // The text-input has no text, clear the text-view's layout info.
5027 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5031 if( mDisplayedTextView )
5033 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5037 // There is no text-view.
5038 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5043 void TextInput::EmitStyleChangedSignal()
5045 // emit signal if input style changes.
5047 Toolkit::TextInput handle( GetOwner() );
5048 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5051 void TextInput::EmitMaxInputCharactersReachedSignal()
5053 // emit signal if max characters is reached during text input.
5054 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5056 Toolkit::TextInput handle( GetOwner() );
5057 mMaxInputCharactersReachedSignalV2.Emit( handle );
5060 void TextInput::EmitInputTextExceedsBoundariesSignal()
5062 // Emit a signal when the input text exceeds the boundaries of the text input.
5064 Toolkit::TextInput handle( GetOwner() );
5065 mInputTextExceedBoundariesSignalV2.Emit( handle );
5068 } // namespace Internal
5070 } // namespace Toolkit