2 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 // Licensed under the Flora License, Version 1.0 (the License);
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
8 // http://floralicense.org/license/
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an AS IS BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
17 #include <dali/dali.h>
19 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
20 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
21 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
22 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
24 #include <dali/integration-api/debug.h>
31 #define GET_LOCALE_TEXT(string) dgettext("sys_string", string)
40 #if defined(DEBUG_ENABLED)
41 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
44 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
45 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
46 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
47 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
48 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
49 const Vector4 LIGHTBLUE( 10.0f/255.0f, 140.0f/255.0f, 210.0f/255.0f, 1.0f ); // Used for Selection highlight
51 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
52 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
53 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
54 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
55 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
56 const char* DEFAULT_CURSOR( DALI_IMAGE_DIR "cursor.png" );
58 const char* DEFAULT_ICON_CLIPBOARD( DALI_IMAGE_DIR "copy_paste_icon_clipboard.png" );
59 const char* DEFAULT_ICON_COPY( DALI_IMAGE_DIR "copy_paste_icon_copy.png" );
60 const char* DEFAULT_ICON_CUT( DALI_IMAGE_DIR "copy_paste_icon_cut.png" );
61 const char* DEFAULT_ICON_PASTE( DALI_IMAGE_DIR "copy_paste_icon_paste.png" );
62 const char* DEFAULT_ICON_SELECT( DALI_IMAGE_DIR "copy_paste_icon_select.png" );
63 const char* DEFAULT_ICON_SELECT_ALL( DALI_IMAGE_DIR "copy_paste_icon_select_all.png" );
65 const Vector4 DEFAULT_CURSOR_IMAGE_9_BORDER( 2.0f, 2.0f, 2.0f, 2.0f );
67 const std::string OPTION_SELECT_WORD("select_word"); ///< "Select Word" popup option.
68 const std::string OPTION_SELECT_ALL("select_all"); ///< "Select All" popup option.
69 const std::string OPTION_CUT("cut"); ///< "Cut" popup option.
70 const std::string OPTION_COPY("copy"); ///< "Copy" popup option.
71 const std::string OPTION_PASTE("paste"); ///< "Paste" popup option.
72 const std::string OPTION_CLIPBOARD("clipboard"); ///< "Clipboard" popup option.
74 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
75 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
76 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.0f ); ///< 1. Highlight rendered (z-offset).
77 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.1f ); ///< 2. Text rendered (z-offset).
78 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
80 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
81 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
82 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
83 const float TOP_HANDLE_TOP_OFFSET(-1.5f); ///< Offset between top handle and cutCopyPaste pop-up
84 const float BOTTOM_HANDLE_BOTTOM_OFFSET(1.5f); ///< Offset between bottom handle and cutCopyPaste pop-up
85 const float CURSOR_THICKNESS(6.0f);
86 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
88 const std::string NEWLINE( "\n" );
90 const TextStyle DEFAULT_TEXT_STYLE;
92 const unsigned int SCROLL_TICK_INTERVAL = 50u;
93 const float SCROLL_THRESHOLD = 10.f;
94 const float SCROLL_SPEED = 15.f;
97 * Whether the given style is the default style or not.
98 * @param[in] style The given style.
99 * @return \e true if the given style is the default. Otherwise it returns \e false.
101 bool IsDefaultStyle( const TextStyle& style )
103 return DEFAULT_TEXT_STYLE == style;
107 * Whether the given styled text is using the default style or not.
108 * @param[in] textArray The given text.
109 * @return \e true if the given styled text is using the default style. Otherwise it returns \e false.
111 bool IsTextDefaultStyle( const Toolkit::MarkupProcessor::StyledTextArray& textArray )
113 for( Toolkit::MarkupProcessor::StyledTextArray::const_iterator it = textArray.begin(), endIt = textArray.end(); it != endIt; ++it )
115 const TextStyle& style( (*it).mStyle );
117 if( !IsDefaultStyle( style ) )
127 * Selection state enumeration (FSM)
131 SelectionNone, ///< Currently not encountered selected section.
132 SelectionStarted, ///< Encountered selected section
133 SelectionFinished ///< Finished selected section
136 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
138 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
142 if( ( *it ).mIsVisible )
144 return --cursorPosition;
153 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
155 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
157 if( ( *it ).mIsVisible )
159 return cursorPosition;
165 return cursorPosition;
169 * Whether the given position plus the cursor size offset is inside the given boundary.
171 * @param[in] position The given position.
172 * @param[in] cursorSize The cursor size.
173 * @param[in] controlSize The given boundary.
175 * @return whether the given position is inside the given boundary.
177 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
179 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
180 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
181 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
182 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
186 * Splits a text in two halves.
188 * If the text's number of characters is odd, firstHalf has one more character.
190 * @param[in] text The text to be split.
191 * @param[out] firstHalf The first half of the text.
192 * @param[out] secondHalf The second half of the text.
194 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
195 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
196 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
201 const std::size_t textLength = text.size();
202 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
204 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
205 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
208 } // end of namespace
224 return Toolkit::TextInput::New();
227 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
229 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
230 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
231 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
232 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
233 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
234 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
238 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
240 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
242 QuadCoordinates quad(x1, y1, x2, y2);
243 mQuadList.push_back( quad );
246 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
248 for(std::size_t i = 0;i < mQuadList.size(); i++)
250 QuadCoordinates& quad = mQuadList[i];
252 quad.min.Clamp(min, max);
253 quad.max.Clamp(min, max);
257 // [TextInput] ////////////////////////////////////////////////////////////////
259 Dali::Toolkit::TextInput TextInput::New()
261 // Create the implementation
262 TextInputPtr textInput(new TextInput());
263 // Pass ownership to CustomActor via derived handle
264 Dali::Toolkit::TextInput handle(*textInput);
266 textInput->Initialize();
271 TextInput::TextInput()
272 :ControlImpl( true ),
277 mDisplayedTextView(),
278 mStyledPlaceHolderText(),
279 mMaxStringLength( DEFAULT_MAX_SIZE ),
280 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
281 mCursorPosition( 0 ),
282 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
283 mIsSelectionHandleOneFlipped( false ),
284 mIsSelectionHandleTwoFlipped( false ),
285 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
286 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
287 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
288 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
289 mSelectionHandleOnePosition( 0 ),
290 mSelectionHandleTwoPosition( 0 ),
292 mPreEditStartPosition( 0 ),
293 mPreEditLength ( 0 ),
294 mNumberOfSurroundingCharactersDeleted( 0 ),
295 mTouchStartTime( 0 ),
297 mCurrentCopySelecton(),
299 mScrollDisplacement(),
300 mCurrentHandlePosition(),
301 mCurrentSelectionId(),
302 mCurrentSelectionHandlePosition(),
303 mRequestedSelection( 0, 0 ),
304 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
305 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
307 mOverrideAutomaticAlignment( false ),
308 mCursorRTLEnabled( false ),
309 mClosestCursorPositionEOL ( false ),
310 mCursorBlinkStatus( true ),
311 mCursorVisibility( false ),
312 mGrabHandleVisibility( false ),
313 mIsCursorInScrollArea( true ),
314 mIsGrabHandleInScrollArea( true ),
315 mEditModeActive( false ),
316 mEditOnTouch( true ),
317 mTextSelection( true ),
318 mExceedEnabled( true ),
319 mGrabHandleEnabled( true ),
320 mIsSelectionHandleFlipEnabled( true ),
321 mPreEditFlag( false ),
322 mIgnoreCommitFlag( false ),
323 mIgnoreFirstCommitFlag( false ),
324 mSelectingText( false ),
325 mPreserveCursorPosition( false ),
326 mSelectTextOnCommit( false ),
327 mUnderlinedPriorToPreEdit ( false ),
328 mCommitByKeyInput( false ),
329 mPlaceHolderSet( false ),
330 mMarkUpEnabled( true )
332 // Updates the line height accordingly with the input style.
336 TextInput::~TextInput()
338 StopCursorBlinkTimer();
343 std::string TextInput::GetText() const
347 // Return text-view's text only if the text-input's text is not empty
348 // in order to not to return the placeholder text.
349 if( !mStyledText.empty() )
351 text = mDisplayedTextView.GetText();
357 std::string TextInput::GetMarkupText() const
359 std::string markupString;
360 MarkupProcessor::GetMarkupString( mStyledText, markupString );
365 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
367 // Get the placeholder styled text array from the markup string.
368 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
370 if( mStyledText.empty() )
372 // Set the placeholder text only if the styled text is empty.
373 mDisplayedTextView.SetText( mStyledPlaceHolderText );
374 mPlaceHolderSet = true;
378 std::string TextInput::GetPlaceholderText()
380 // Traverses the styled placeholder array getting only the text.
381 // Note that for some languages a 'character' could be represented by more than one 'char'
383 std::string placeholderText;
384 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
386 placeholderText.append( (*it).mText.GetText() );
389 return placeholderText ;
392 void TextInput::SetInitialText(const std::string& initialText)
394 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
396 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
398 mPreEditFlag = false;
399 mIgnoreCommitFlag = true;
402 SetText( initialText );
403 PreEditReset( false ); // Reset keyboard as text changed
406 void TextInput::SetText(const std::string& initialText)
408 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
410 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
412 if( mStyledText.empty() )
414 // If the initial text is empty, set the placeholder text.
415 mDisplayedTextView.SetText( mStyledPlaceHolderText );
416 mPlaceHolderSet = true;
420 mDisplayedTextView.SetText( mStyledText );
421 mPlaceHolderSet = false;
426 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
428 ImfManager imfManager = ImfManager::Get();
431 imfManager.SetCursorPosition( mCursorPosition );
432 imfManager.SetSurroundingText( initialText );
433 imfManager.NotifyCursorPosition();
436 if( IsScrollEnabled() )
438 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
441 ShowGrabHandleAndSetVisibility( false );
448 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
450 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
452 mDisplayedTextView.SetText( styleText );
453 mPlaceHolderSet = false;
455 // If text alignment hasn't been manually set by application developer, then we
456 // automatically determine the alignment based on the content of the text i.e. what
457 // language the text begins with.
458 // TODO: This should determine different alignments for each line (broken by '\n') of text.
459 if(!mOverrideAutomaticAlignment)
461 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
462 bool leftToRight(true);
464 if( !styleText.empty() )
466 bool breakOut(false);
468 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
470 const Text& text = textIter->mText;
472 for( std::size_t i = 0; i < text.GetLength(); ++i )
474 Character character( text[i] );
475 if( character.GetCharacterDirection() != Character::Neutral )
477 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
485 // Based on this direction, either left or right align text if not manually set by application developer.
486 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
487 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
488 Toolkit::Alignment::VerticalTop ) );
489 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
493 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
495 mMaxStringLength = maxChars;
498 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
500 DALI_ASSERT_DEBUG( maxLines > 0 )
504 mNumberOflinesLimit = maxLines;
508 std::size_t TextInput::GetNumberOfLinesLimit() const
510 return mNumberOflinesLimit;
513 std::size_t TextInput::GetNumberOfCharacters() const
515 return mStyledText.size();
518 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
520 return mInputStartedSignalV2;
523 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
525 return mInputFinishedSignalV2;
528 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
530 return mCutAndPasteToolBarDisplayedV2;
533 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
535 return mStyleChangedSignalV2;
538 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
540 return mMaxInputCharactersReachedSignalV2;
543 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
545 return mInputTextExceedBoundariesSignalV2;
548 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
550 Dali::BaseHandle handle( object );
552 bool connected( true );
553 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
555 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
557 textInput.InputStartedSignal().Connect( tracker, functor );
559 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
561 textInput.InputFinishedSignal().Connect( tracker, functor );
563 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
565 textInput.StyleChangedSignal().Connect( tracker, functor );
567 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
569 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
571 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
573 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
577 // signalName does not match any signal
584 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
588 // update line height before calculate the actual position.
593 if( setCursorOnTouchPoint )
595 // Sets the cursor position for the given touch point.
596 ReturnClosestIndex( touchPoint, mCursorPosition );
598 // Creates the grab handle.
599 if( IsGrabHandleEnabled() )
601 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
605 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
606 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
607 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
608 ShowGrabHandleAndSetVisibility( true );
610 // Scrolls the text-view if needed.
611 if( IsScrollEnabled() )
613 ScrollTextViewToMakeCursorVisible( cursorPosition );
619 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
631 bool TextInput::IsEditable() const
633 return mEditModeActive;
636 void TextInput::SetEditOnTouch( bool editOnTouch )
638 mEditOnTouch = editOnTouch;
641 bool TextInput::IsEditOnTouch() const
646 void TextInput::SetTextSelectable( bool textSelectable )
648 mTextSelection = textSelectable;
651 bool TextInput::IsTextSelectable() const
653 return mTextSelection;
656 bool TextInput::IsTextSelected() const
658 return mHighlightMeshActor;
661 void TextInput::DeSelectText()
668 void TextInput::SetGrabHandleImage(Dali::Image image )
672 CreateGrabHandle(image);
676 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
678 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
682 mCursor.SetImage( image );
683 mCursor.SetNinePatchBorder( border );
687 Vector3 TextInput::GetSelectionHandleSize()
689 return DEFAULT_SELECTION_HANDLE_SIZE;
692 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
694 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
698 mCursorRTL.SetImage( image);
699 mCursorRTL.SetNinePatchBorder( border );
703 void TextInput::EnableGrabHandle(bool toggle)
705 // enables grab handle with will in turn de-activate magnifier
706 mGrabHandleEnabled = toggle;
709 bool TextInput::IsGrabHandleEnabled()
711 // if false then magnifier will be shown instead.
712 return mGrabHandleEnabled;
715 void TextInput::EnableSelectionHandleFlip( bool toggle )
717 // Deprecated function. To be removed.
718 mIsSelectionHandleFlipEnabled = toggle;
721 bool TextInput::IsSelectionHandleFlipEnabled()
723 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
727 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
729 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
730 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
731 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
733 mSelectionHandleFlipMargin = margin;
736 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
738 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
739 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
741 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
742 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
744 const Vector4 boundary( originX,
746 originX + boundingRectangle.width,
747 originY + boundingRectangle.height );
749 mBoundingRectangleWorldCoordinates = boundary;
752 const Rect<float> TextInput::GetBoundingRectangle() const
754 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
756 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
757 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
759 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
764 const Vector4& TextInput::GetSelectionHandleFlipMargin()
766 return mSelectionHandleFlipMargin;
769 void TextInput::SetTextColor( const Vector4& color )
771 mDisplayedTextView.SetColor( color );
774 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
776 if( style != mInputStyle )
779 bool emitSignal = false;
781 // mask: modify style according to mask, if different emit signal.
782 const TextStyle oldInputStyle( mInputStyle );
784 // Copy the new style.
785 mInputStyle.Copy( style, mask );
787 // if style has changed, emit signal.
788 if( oldInputStyle != mInputStyle )
793 // Updates the line height accordingly with the input style.
796 // Changing font point size will require the cursor to be re-sized
801 EmitStyleChangedSignal();
806 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
808 if ( IsTextSelected() )
810 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
811 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
813 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
815 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
818 // Keeps the old style to be compared with the new one.
819 const TextStyle oldInputStyle( mInputStyle );
821 // Copy only those parameters from the style which are set in the mask.
822 mInputStyle.Copy( style, mask );
824 if( mInputStyle != oldInputStyle )
826 // Updates the line height accordingly with the input style.
829 EmitStyleChangedSignal();
834 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
836 if( !mStyledText.empty() )
838 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
842 TextStyle TextInput::GetStyleAtCursor() const
846 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
848 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
849 style = mStyledText.at( mCursorPosition-1 ).mStyle;
855 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
857 Dali::Font defaultFont = Dali::Font::New();
858 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
865 TextStyle TextInput::GetStyleAt( std::size_t position ) const
867 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
869 if( position >= mStyledText.size() )
871 position = mStyledText.size() - 1;
874 return mStyledText.at( position ).mStyle;
877 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
879 mDisplayedTextView.SetTextAlignment( align );
880 mOverrideAutomaticAlignment = true;
883 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
885 mDisplayedTextView.SetLineJustification( justification );
886 mOverrideAutomaticAlignment = true;
889 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
891 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
894 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
896 return mDisplayedTextView.GetFadeBoundary();
899 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
901 return mDisplayedTextView.GetTextAlignment();
904 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
906 mDisplayedTextView.SetMultilinePolicy( policy );
909 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
911 return mDisplayedTextView.GetMultilinePolicy();
914 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
916 mDisplayedTextView.SetWidthExceedPolicy( policy );
919 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
921 return mDisplayedTextView.GetWidthExceedPolicy();
924 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
926 mDisplayedTextView.SetHeightExceedPolicy( policy );
929 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
931 return mDisplayedTextView.GetHeightExceedPolicy();
934 void TextInput::SetExceedEnabled( bool enable )
936 mExceedEnabled = enable;
939 bool TextInput::GetExceedEnabled() const
941 return mExceedEnabled;
944 void TextInput::SetBackground(Dali::Image image )
946 // TODO Should add this function and add public api to match.
949 bool TextInput::OnTouchEvent(const TouchEvent& event)
954 bool TextInput::OnKeyEvent(const KeyEvent& event)
956 switch( event.state )
960 return OnKeyDownEvent(event);
966 return OnKeyUpEvent(event);
978 void TextInput::OnKeyInputFocusGained()
980 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
982 mEditModeActive = true;
984 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
986 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
988 // Updates the line height accordingly with the input style.
991 // Connect the signals to use in text input.
992 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
993 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
995 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1001 SetCursorVisibility( true );
1002 StartCursorBlinkTimer();
1004 Toolkit::TextInput handle( GetOwner() );
1005 mInputStartedSignalV2.Emit( handle );
1007 ImfManager imfManager = ImfManager::Get();
1011 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1013 // Notify that the text editing start.
1014 imfManager.Activate();
1016 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1017 imfManager.SetRestoreAferFocusLost( true );
1019 imfManager.SetCursorPosition( mCursorPosition );
1020 imfManager.NotifyCursorPosition();
1023 mClipboard = Clipboard::Get(); // Store handle to clipboard
1025 // Now in edit mode we can accept string to paste from clipboard
1026 if( Adaptor::IsAvailable() )
1028 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1031 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1036 void TextInput::OnKeyInputFocusLost()
1038 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1042 // If key input focus is lost, it removes the
1043 // underline from the last pre-edit text.
1044 RemovePreEditStyle();
1045 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1046 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1049 ImfManager imfManager = ImfManager::Get();
1052 // The text editing is finished. Therefore the imf manager don't have restore activation.
1053 imfManager.SetRestoreAferFocusLost( false );
1055 // Notify that the text editing finish.
1056 imfManager.Deactivate();
1058 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1060 // Disconnect signal used the text input.
1061 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1063 Toolkit::TextInput handle( GetOwner() );
1064 mInputFinishedSignalV2.Emit( handle );
1065 mEditModeActive = false;
1066 mPreEditFlag = false;
1068 SetCursorVisibility( false );
1069 StopCursorBlinkTimer();
1071 ShowGrabHandleAndSetVisibility( false );
1074 // No longer in edit mode so do not want to receive string from clipboard
1075 if( Adaptor::IsAvailable() )
1077 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1080 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1082 Clipboard clipboard = Clipboard::Get();
1086 clipboard.HideClipboard();
1091 void TextInput::OnControlStageConnection()
1093 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1095 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1097 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1101 void TextInput::CreateActiveLayer()
1103 Actor self = Self();
1104 mActiveLayer = Layer::New();
1106 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1107 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1108 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1110 self.Add( mActiveLayer );
1111 mActiveLayer.RaiseToTop();
1114 void TextInput::OnInitialize()
1116 CreateTextViewActor();
1120 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1121 // different positions depending on language)
1122 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1123 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1124 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1126 Actor self = Self();
1127 self.Add( mCursor );
1128 self.Add( mCursorRTL );
1130 mCursorVisibility = false;
1132 CreateActiveLayer(); // todo move this so layer only created when needed.
1134 // Assign names to image actors
1135 mCursor.SetName("mainCursor");
1136 mCursorRTL.SetName("rtlCursor");
1139 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1141 mDisplayedTextView.SetSize( targetSize );
1142 GetTextLayoutInfo();
1143 mActiveLayer.SetSize(targetSize);
1146 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1148 Relayout( mDisplayedTextView, size, container );
1149 GetTextLayoutInfo();
1154 Vector3 TextInput::GetNaturalSize()
1156 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1158 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1160 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1161 naturalSize.height = mLineHeight;
1167 float TextInput::GetHeightForWidth( float width )
1169 float height = mDisplayedTextView.GetHeightForWidth( width );
1171 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1173 // If the height is zero, it means there is no text. Let's return the cursor height.
1174 height = mLineHeight;
1180 /*end of Virtual methods from parent*/
1182 // Private Internal methods
1184 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1186 switch (gesture.state)
1188 case Gesture::Started:
1189 // fall through so code not duplicated
1190 case Gesture::Continuing:
1192 if (actor == mGrabArea)
1194 SetCursorVisibility( true );
1195 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1196 MoveGrabHandle( gesture.displacement );
1197 HidePopup(); // Do not show popup whilst handle is moving
1199 else if (actor == mHandleOneGrabArea)
1201 // the displacement in PanGesture is affected by the actor's rotation.
1202 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1203 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1205 MoveSelectionHandle( HandleOne, gesture.displacement );
1207 mState = StateDraggingHandle;
1210 else if (actor == mHandleTwoGrabArea)
1212 // the displacement in PanGesture is affected by the actor's rotation.
1213 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1214 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1216 MoveSelectionHandle( HandleTwo, gesture.displacement );
1218 mState = StateDraggingHandle;
1224 case Gesture::Finished:
1226 // Revert back to non-pressed selection handle images
1227 if (actor == mGrabArea)
1229 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1230 SetCursorVisibility( true );
1231 SetUpPopUpSelection();
1234 if (actor == mHandleOneGrabArea)
1236 // the displacement in PanGesture is affected by the actor's rotation.
1237 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1238 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1240 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1242 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1244 ShowPopupCutCopyPaste();
1246 if (actor == mHandleTwoGrabArea)
1248 // the displacement in PanGesture is affected by the actor's rotation.
1249 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1250 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1252 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1254 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1256 ShowPopupCutCopyPaste();
1265 // Stop the flashing animation so easy to see when moved.
1266 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1268 if (touch.GetPoint(0).state == TouchPoint::Down)
1270 SetCursorVisibility( true );
1271 StopCursorBlinkTimer();
1273 else if (touch.GetPoint(0).state == TouchPoint::Up)
1275 SetCursorVisibility( true );
1276 StartCursorBlinkTimer();
1281 // selection handle one
1282 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1284 if (touch.GetPoint(0).state == TouchPoint::Down)
1286 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1288 else if (touch.GetPoint(0).state == TouchPoint::Up)
1290 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1295 // selection handle two
1296 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1298 if (touch.GetPoint(0).state == TouchPoint::Down)
1300 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1302 else if (touch.GetPoint(0).state == TouchPoint::Up)
1304 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1309 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1311 // If text exists then select nearest word.
1312 if ( !mStyledText.empty())
1316 ShowGrabHandleAndSetVisibility( false );
1321 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1322 // converts the pre-edit word being displayed to a committed word.
1323 if ( !mUnderlinedPriorToPreEdit )
1326 style.SetUnderline( false );
1327 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1329 mPreEditFlag = false;
1330 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1331 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1332 PreEditReset( false );
1334 mCursorPosition = 0;
1336 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1337 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1339 ImfManager imfManager = ImfManager::Get();
1342 imfManager.SetCursorPosition ( mCursorPosition );
1343 imfManager.NotifyCursorPosition();
1346 std::size_t start = 0;
1347 std::size_t end = 0;
1348 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1350 SelectText( start, end );
1352 // if no text but clipboard has content then show paste option
1353 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1355 ShowPopupCutCopyPaste();
1358 // If no text and clipboard empty then do nothing
1361 // TODO: Change the function name to be more general.
1362 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1364 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1365 , (mEditOnTouch)?"true":"false"
1366 , (mEditModeActive)?"true":"false");
1368 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1373 if( mGrabArea == actor )
1375 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1377 SetUpPopUpSelection();
1387 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1389 // Initially don't create the grab handle.
1390 bool createGrabHandle = false;
1392 if ( !mEditModeActive )
1394 // update line height before calculate the actual position.
1397 // Only start edit mode if TextInput configured to edit on touch
1400 // Set the initial cursor position in the tap point.
1401 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1403 // Create the grab handle.
1404 // TODO Make this a re-usable function.
1405 if ( IsGrabHandleEnabled() )
1407 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1411 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1412 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1413 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1414 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1418 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1419 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1420 // otherwise the Grab handle will be shown when selecting.
1427 // Show the keyboard if it was hidden.
1428 if (!VirtualKeyboard::IsVisible())
1430 VirtualKeyboard::Show();
1433 // Reset keyboard as tap event has occurred.
1434 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1435 PreEditReset( true );
1437 GetTextLayoutInfo();
1439 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1441 // As already in edit mode, reposition cursor near tap and show grab handle for cursor, if grab handle not enabled then magnifier will be used instead.
1443 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1445 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1447 // Notify keyboard so it can 're-capture' word for predictive text.
1448 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1449 ImfManager imfManager = ImfManager::Get();
1452 imfManager.SetCursorPosition ( mCursorPosition );
1453 imfManager.NotifyCursorPosition();
1455 const TextStyle oldInputStyle( mInputStyle );
1457 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1461 // Create the grab handle.
1462 // Grab handle is created later.
1463 createGrabHandle = true;
1465 if( oldInputStyle != mInputStyle )
1467 // Updates the line height accordingly with the input style.
1470 EmitStyleChangedSignal();
1475 if ( createGrabHandle && IsGrabHandleEnabled() )
1477 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1481 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1482 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1483 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1484 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1489 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1491 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1493 if(longPress.state == Dali::Gesture::Started)
1495 // Start edit mode on long press
1496 if ( !mEditModeActive )
1501 // If text exists then select nearest word.
1502 if ( !mStyledText.empty())
1506 ShowGrabHandleAndSetVisibility( false );
1511 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1512 // converts the pre-edit word being displayed to a committed word.
1513 if ( !mUnderlinedPriorToPreEdit )
1516 style.SetUnderline( false );
1517 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1519 mPreEditFlag = false;
1520 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1521 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1522 PreEditReset( false );
1524 mCursorPosition = 0;
1526 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1527 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1529 ImfManager imfManager = ImfManager::Get();
1532 imfManager.SetCursorPosition ( mCursorPosition );
1533 imfManager.NotifyCursorPosition();
1535 std::size_t start = 0;
1536 std::size_t end = 0;
1537 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1539 SelectText( start, end );
1542 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1543 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1545 ShowPopupCutCopyPaste();
1550 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1552 const Text clipboardText( notifier.GetContent() );
1553 PasteText( clipboardText );
1555 SetCursorVisibility( true );
1556 StartCursorBlinkTimer();
1558 ShowGrabHandleAndSetVisibility( false );
1564 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1566 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1568 const std::string& name = button.GetName();
1570 if(name == OPTION_SELECT_WORD)
1572 std::size_t start = 0;
1573 std::size_t end = 0;
1574 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1576 SelectText( start, end );
1578 else if(name == OPTION_SELECT_ALL)
1580 SetCursorVisibility(false);
1581 StopCursorBlinkTimer();
1583 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1584 std::size_t start = 0;
1586 SelectText( start, end );
1588 else if(name == OPTION_CUT)
1590 bool ret = CopySelectedTextToClipboard();
1594 DeleteHighlightedText( true );
1598 SetCursorVisibility( true );
1599 StartCursorBlinkTimer();
1603 else if(name == OPTION_COPY)
1605 CopySelectedTextToClipboard();
1609 SetCursorVisibility( true );
1610 StartCursorBlinkTimer();
1614 else if(name == OPTION_PASTE)
1616 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1618 PasteText(retrievedString);
1620 SetCursorVisibility( true );
1621 StartCursorBlinkTimer();
1623 ShowGrabHandleAndSetVisibility( false );
1628 else if(name == OPTION_CLIPBOARD)
1630 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1631 // Hence pass the false parameter for signalFinished.
1632 HidePopup( true, false );
1633 mClipboard.ShowClipboard();
1639 bool TextInput::OnCursorBlinkTimerTick()
1642 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1643 if ( mCursorRTLEnabled )
1645 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1647 mCursorBlinkStatus = !mCursorBlinkStatus;
1652 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1654 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1656 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1657 if(mHighlightMeshActor && mState == StateEdit)
1659 ShowPopupCutCopyPaste();
1663 //FIXME this routine needs to be re-written as it contains too many branches.
1664 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1666 std::string keyName = event.keyPressedName;
1667 std::string keyString = event.keyPressed;
1669 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1671 // Do not consume "Tab" and "Escape" keys.
1672 if(keyName == "Tab" || keyName == "Escape")
1674 // Escape key to end the edit mode
1680 HidePopup(); // If Pop-up shown then hides it as editing text.
1682 // Update Flag, indicates whether to update the text-input contents or not.
1683 // Any key stroke that results in a visual change of the text-input should
1684 // set this flag to true.
1687 // Whether to scroll text to cursor position.
1688 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1689 bool scroll = false;
1691 if (keyName == "Return")
1693 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1695 bool preEditFlagPreviouslySet( mPreEditFlag );
1697 if (mHighlightMeshActor)
1699 // replaces highlighted text with new line
1700 DeleteHighlightedText( false );
1702 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1704 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1705 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1708 mCommitByKeyInput = true;
1711 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1712 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1714 mPreEditFlag = true;
1715 mIgnoreCommitFlag = false;
1725 else if ( keyName == "space" )
1727 if ( mHighlightMeshActor )
1729 // Some text is selected so erase it before adding space.
1730 DeleteHighlightedText( true );
1734 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1736 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1737 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1740 mCommitByKeyInput = true;
1745 else if (keyName == "BackSpace")
1747 if ( mHighlightMeshActor )
1749 // Some text is selected so erase it
1750 DeleteHighlightedText( true );
1755 if ( mCursorPosition > 0 )
1757 DeleteCharacter( mCursorPosition );
1762 else if (keyName == "Right")
1767 else if (keyName == "Left")
1769 AdvanceCursor(true);
1772 else // event is a character
1774 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1775 if ( !keyString.empty() )
1777 if ( mHighlightMeshActor )
1779 // replaces highlighted text with new character
1780 DeleteHighlightedText( false );
1784 // Received key String
1785 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1790 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1791 // as this is a costly operation.
1797 if(update || scroll)
1799 if( IsScrollEnabled() )
1801 // Calculates the new cursor position (in actor coordinates)
1802 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1804 ScrollTextViewToMakeCursorVisible( cursorPosition );
1811 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1813 std::string keyName = event.keyPressedName;
1814 std::string keyString = event.keyPressed;
1816 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1818 // The selected text become deselected when the key code is DALI_KEY_BACK.
1819 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1828 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1830 // Updates the stored scroll position.
1831 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1833 const Vector3& controlSize = GetControlSize();
1834 Size cursorSize( CURSOR_THICKNESS, 0.f );
1836 // Updates the cursor and grab handle position and visibility.
1837 if( mGrabHandle || mCursor )
1839 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1840 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1842 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1844 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1848 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1849 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1854 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1855 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1859 // Updates the selection handles and highlighted text position and visibility.
1860 if( mSelectionHandleOne && mSelectionHandleTwo )
1862 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1863 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1864 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1865 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1866 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1867 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1869 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1870 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1872 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1873 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1874 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1875 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1877 if( mHighlightMeshActor )
1879 mHighlightMeshActor.SetVisible( true );
1885 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1887 // Scroll the text to make the cursor visible.
1888 const Size cursorSize( CURSOR_THICKNESS,
1889 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1891 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1893 const Vector3& controlSize = GetControlSize();
1895 // Calculates the new scroll position.
1896 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1897 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1899 scrollOffset.x += cursorPosition.x;
1902 if( cursorPosition.y - cursorSize.height < 0.f )
1904 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1906 else if( cursorPosition.y > controlSize.height )
1908 scrollOffset.y += cursorPosition.y;
1911 // Sets the new scroll position.
1912 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1913 SetScrollPosition( scrollOffset );
1916 void TextInput::StartScrollTimer()
1920 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1921 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1924 if( !mScrollTimer.IsRunning() )
1926 mScrollTimer.Start();
1930 void TextInput::StopScrollTimer()
1934 mScrollTimer.Stop();
1938 bool TextInput::OnScrollTimerTick()
1940 // TODO: need to set the new style accordingly the new handle position.
1942 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1944 // nothing to do if all handles are invisible or doesn't exist.
1950 // Choose between the grab handle or the selection handles.
1951 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1952 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1953 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1955 std::size_t newCursorPosition = 0;
1956 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1958 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1959 // the new selection handle's position needs to be different of the other one.
1960 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1961 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1962 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1964 if( differentSelectionHandles )
1966 handlePosition = newCursorPosition;
1968 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1970 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1972 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1973 scrollPosition += scrollDelta;
1974 SetScrollPosition( scrollPosition );
1976 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1981 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1984 actualHandlePosition.x += mScrollDisplacement.x;
1985 actualHandlePosition.y += mScrollDisplacement.y;
1990 // Public Internal Methods (public for testing purpose)
1992 void TextInput::SetUpTouchEvents()
1994 if ( !mTapDetector )
1996 mTapDetector = TapGestureDetector::New();
1997 // Attach the actors and connect the signal
1998 mTapDetector.Attach(Self());
2000 // As contains children which may register for tap the default control detector is not used.
2001 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2004 if ( !mDoubleTapDetector )
2006 mDoubleTapDetector = TapGestureDetector::New();
2007 mDoubleTapDetector.SetTapsRequired( 2 );
2008 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2010 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2011 // so that we do not, unnecessarily, have a double tap request all the time
2014 if ( !mPanGestureDetector )
2016 mPanGestureDetector = PanGestureDetector::New();
2017 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2020 if ( !mLongPressDetector )
2022 mLongPressDetector = LongPressGestureDetector::New();
2023 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2024 mLongPressDetector.Attach(Self());
2028 void TextInput::CreateTextViewActor()
2030 mDisplayedTextView = Toolkit::TextView::New();
2031 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2032 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2033 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2034 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2035 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2036 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2037 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2038 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2039 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2040 mDisplayedTextView.SetSizePolicy( Control::Fixed, Control::Fixed );
2042 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2044 Self().Add( mDisplayedTextView );
2047 // Start a timer to initiate, used by the cursor to blink.
2048 void TextInput::StartCursorBlinkTimer()
2050 if ( !mCursorBlinkTimer )
2052 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2053 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2056 if ( !mCursorBlinkTimer.IsRunning() )
2058 mCursorBlinkTimer.Start();
2062 // Start a timer to initiate, used by the cursor to blink.
2063 void TextInput::StopCursorBlinkTimer()
2065 if ( mCursorBlinkTimer )
2067 mCursorBlinkTimer.Stop();
2071 void TextInput::StartEditMode()
2073 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2075 if(!mEditModeActive)
2080 if ( mDoubleTapDetector )
2082 mDoubleTapDetector.Attach( Self() );
2086 void TextInput::EndEditMode()
2088 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2090 ClearKeyInputFocus();
2092 if ( mDoubleTapDetector )
2094 mDoubleTapDetector.Detach( Self() );
2098 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2100 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2102 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2104 style.SetUnderline( true );
2105 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2109 void TextInput::RemovePreEditStyle()
2111 if ( !mUnderlinedPriorToPreEdit )
2114 style.SetUnderline( false );
2115 SetActiveStyle( style, TextStyle::UNDERLINE );
2119 // IMF related methods
2122 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2124 bool update( false );
2125 bool preeditResetRequired ( false );
2127 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2129 HidePopup(); // If Pop-up shown then hides it as editing text.
2132 switch ( imfEvent.eventName )
2134 case ImfManager::PREEDIT:
2136 mIgnoreFirstCommitFlag = false;
2138 // Some text may be selected, hiding keyboard causes an empty predictive string to be sent, we don't want to delete highlight in this case
2139 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2141 // replaces highlighted text with new character
2142 DeleteHighlightedText( false );
2145 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2147 if( IsScrollEnabled() )
2149 // Calculates the new cursor position (in actor coordinates)
2150 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2151 ScrollTextViewToMakeCursorVisible( cursorPosition );
2158 case ImfManager::COMMIT:
2160 if( mIgnoreFirstCommitFlag )
2162 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2163 mIgnoreFirstCommitFlag = false;
2167 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2169 // Some text may be selected, hiding keyboard causes an empty predictive string to be sent, we don't want to delete highlight in this case
2170 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2172 // replaces highlighted text with new character
2173 DeleteHighlightedText( false );
2176 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2177 // not needed, one such scenario is when the pre-edit word is too long to fit.
2178 if ( !mIgnoreCommitFlag )
2180 update = CommitReceived( imfEvent.predictiveString );
2184 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2190 if( IsScrollEnabled() )
2192 // Calculates the new cursor position (in actor coordinates)
2193 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2195 ScrollTextViewToMakeCursorVisible( cursorPosition );
2200 case ImfManager::DELETESURROUNDING:
2202 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2203 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2205 mPreEditFlag = false;
2207 std::size_t toDelete = 0;
2208 std::size_t numberOfCharacters = 0;
2210 if( mHighlightMeshActor )
2212 // delete highlighted text.
2213 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2214 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2218 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2220 toDelete = mCursorPosition + imfEvent.cursorOffset;
2222 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2224 numberOfCharacters = mStyledText.size() - toDelete;
2228 numberOfCharacters = imfEvent.numberOfChars;
2231 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2232 DeleteRange( toDelete, numberOfCharacters );
2234 mCursorPosition = toDelete;
2235 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2237 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2240 case ImfManager::GETSURROUNDING:
2242 // If text is selected/highlighted and surrounding text received we do not want the keyboard to store the word at cursor and return it as a predictive word along with
2243 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2244 if (! ( mHighlightMeshActor || mSelectingText ) )
2246 std::string text( GetText() );
2247 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2249 imfManager.SetCursorPosition( mCursorPosition );
2250 imfManager.SetSurroundingText( text );
2253 if( 0 != mNumberOfSurroundingCharactersDeleted )
2255 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2256 mNumberOfSurroundingCharactersDeleted = 0;
2258 if( mStyledText.empty() )
2260 // Styled text is empty, so set the placeholder text.
2261 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2262 mPlaceHolderSet = true;
2267 case ImfManager::VOID:
2269 DALI_ASSERT_DEBUG( false );
2273 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2275 return callbackData;
2278 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2280 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2282 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2283 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2285 bool preeditResetRequest ( false );
2287 if( mPreEditFlag ) // Already in pre-edit state.
2289 if( mStyledText.size() >= mMaxStringLength )
2291 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2292 // Cannot fit these characters into field, clear pre-edit.
2293 if ( !mUnderlinedPriorToPreEdit )
2296 style.SetUnderline( false );
2297 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2299 mIgnoreCommitFlag = true;
2300 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2301 mPreEditFlag = false;
2302 EmitMaxInputCharactersReachedSignal();
2306 // delete existing pre-edit string
2307 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2309 // Store new pre-edit string
2310 mPreEditString.SetText( keyString );
2312 if ( keyString.empty() )
2314 mPreEditFlag = false;
2315 mCursorPosition = mPreEditStartPosition;
2317 if( mStyledText.empty() )
2319 // Styled text is empty, so set the placeholder text.
2320 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2321 mPlaceHolderSet = true;
2325 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2327 GetTextLayoutInfo();
2331 // Insert new pre-edit string. InsertAt updates the size and position table.
2332 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2333 // If word was too long to be inserted then cursorOffset would be out of range as keyboard assumes there is not limit. Hence use of std::min.
2334 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2335 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2336 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2338 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2342 else // mPreEditFlag not set
2344 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2346 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2347 // new pre-edit so move into pre-edit state by setting flag
2348 mPreEditFlag = true;
2349 mPreEditString.SetText( keyString ); // store new pre-edit string
2350 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2351 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2352 // If word was too long to be inserted then cursorOffset would be out of range as keyboard assumes there is not limit. Hence use of std::min.
2353 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2354 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2355 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2357 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2362 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2366 return preeditResetRequest;
2369 bool TextInput::CommitReceived(const std::string& keyString )
2371 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2372 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2374 bool update( false );
2376 RemovePreEditStyle();
2378 const std::size_t styledTextSize( mStyledText.size() );
2379 if( styledTextSize >= mMaxStringLength )
2381 // Cannot fit these characters into field, clear pre-edit.
2384 mIgnoreCommitFlag = true;
2385 mPreEditFlag = false;
2387 EmitMaxInputCharactersReachedSignal();
2393 // delete existing pre-edit string
2394 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2395 mPreEditFlag = false;
2397 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2398 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2400 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2402 // No need to update cursor position as Cursor location given by touch.
2403 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2404 mPreserveCursorPosition = false;
2408 // Cursor not set by touch so needs to be re-positioned to input more text
2409 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2411 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2412 if ( mCommitByKeyInput )
2414 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2415 mCommitByKeyInput = false;
2419 if ( mSelectTextOnCommit )
2421 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2426 else // mPreEditFlag not set
2428 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2430 if( mStyledText.empty() && mPlaceHolderSet )
2432 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2433 mDisplayedTextView.SetText( "" );
2434 mNumberOfSurroundingCharactersDeleted = 0;
2435 mPlaceHolderSet = false;
2437 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2439 mNumberOfSurroundingCharactersDeleted = 0;
2443 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2448 mSelectTextOnCommit = false;
2450 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2451 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2456 // End of IMF related methods
2458 std::size_t TextInput::DeletePreEdit()
2460 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2462 DALI_ASSERT_DEBUG( mPreEditFlag );
2464 const std::size_t preEditStringLength = mPreEditString.GetLength();
2465 const std::size_t styledTextSize = mStyledText.size();
2467 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2469 // Prevents erase items outside mStyledText bounds.
2470 if( mPreEditStartPosition > styledTextSize )
2472 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2473 mPreEditStartPosition = styledTextSize;
2476 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2478 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2479 endPosition = styledTextSize;
2482 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2484 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2485 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2487 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2489 return preEditStringLength;
2492 void TextInput::PreEditReset( bool preserveCursorPosition )
2494 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2495 preserveCursorPosition, mCursorPosition);
2497 // Store flag to indicate that we do not want to lose the cursor position as the reset may have occurred due to touch event moving the cursor.
2498 mPreserveCursorPosition = preserveCursorPosition;
2500 // Reset incase we are in a pre-edit state.
2501 ImfManager imfManager = ImfManager::Get();
2504 imfManager.Reset(); // Will trigger a commit message
2508 void TextInput::CursorUpdate()
2512 ImfManager imfManager = ImfManager::Get();
2515 std::string text( GetText() );
2516 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2517 imfManager.SetCursorPosition ( mCursorPosition );
2518 imfManager.NotifyCursorPosition();
2522 /* Delete highlighted characters redisplay*/
2523 void TextInput::DeleteHighlightedText( bool inheritStyle )
2525 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2527 if(mHighlightMeshActor)
2529 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2531 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2532 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2534 // Get the styled text of the characters to be deleted as it may be needed if
2535 // the "exceed the text-input's boundaries" option is disabled.
2536 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2538 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2540 mStyledText.erase( start, end ); // erase range of characters
2542 // Remove text from TextView.
2544 if( mStyledText.empty() )
2546 // Styled text is empty, so set the placeholder text.
2547 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2548 mPlaceHolderSet = true;
2552 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2554 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2556 // It may happen than after removing a white space or a new line character,
2557 // two words merge, this new word could be big enough to not fit in its
2558 // current line, so moved to the next one, and make some part of the text to
2559 // exceed the text-input's boundary.
2560 if( !mExceedEnabled )
2562 // Get the new text layout after removing some characters.
2563 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2565 // Get text-input's size.
2566 const Vector3& size = GetControlSize();
2568 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2569 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2571 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2573 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2574 styledCharactersToDelete.begin(),
2575 styledCharactersToDelete.end() );
2579 GetTextLayoutInfo();
2585 const TextStyle oldInputStyle( mInputStyle );
2587 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2589 if( oldInputStyle != mInputStyle )
2591 // Updates the line height accordingly with the input style.
2594 EmitStyleChangedSignal();
2600 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2602 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2603 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2605 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2608 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2610 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2611 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2613 mStyledText.erase(itStart, itEnd);
2615 // update the selection handles if they are visible.
2616 if( mHighlightMeshActor )
2618 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2619 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2621 if( minHandle >= start + ncharacters )
2623 minHandle -= ncharacters;
2625 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2630 if( maxHandle >= start + ncharacters )
2632 maxHandle -= ncharacters;
2634 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2640 // Set text is not called here as currently it can not process the set text from deletion and then the set text from the in-coming pre-edit.
2643 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2645 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2646 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2647 // Mean we do not re-draw the text more than we have too.
2650 /* Delete character at current cursor position and redisplay*/
2651 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2653 // Ensure positionToDelete is not out of bounds.
2654 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2655 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2656 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2658 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2661 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2663 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2665 // Get the styled text of the character to be deleted as it may be needed if
2666 // the "exceed the text-input's boundaries" option is disabled.
2667 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2669 mStyledText.erase(it); // erase the character left of positionToDelete
2671 if( mStyledText.empty() )
2673 // Styled text is empty, so set the placeholder text.
2674 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2675 mPlaceHolderSet = true;
2679 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2681 const Character characterToDelete = styledCharacterToDelete.mText[0];
2683 // It may happen than after removing a white space or a new line character,
2684 // two words merge, this new word could be big enough to not fit in its
2685 // current line, so moved to the next one, and make some part of the text to
2686 // exceed the text-input's boundary.
2687 if( !mExceedEnabled )
2689 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2691 // Get the new text layout after removing one character.
2692 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2694 // Get text-input's size.
2695 const Vector3& size = GetControlSize();
2697 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2698 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2700 MarkupProcessor::StyledTextArray array;
2701 array.push_back( styledCharacterToDelete );
2702 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2704 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2709 GetTextLayoutInfo();
2711 ShowGrabHandleAndSetVisibility( false );
2713 mCursorPosition = positionToDelete -1;
2715 const TextStyle oldInputStyle( mInputStyle );
2717 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2719 if( oldInputStyle != mInputStyle )
2721 // Updates the line height accordingly with the input style.
2724 EmitStyleChangedSignal();
2729 /*Insert new character into the string and (optionally) redisplay text-input*/
2730 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2732 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2734 // Ensure insertionPosition is not out of bounds.
2735 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2737 bool textExceedsMaximunNumberOfCharacters = false;
2738 bool textExceedsBoundary = false;
2739 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2741 ShowGrabHandleAndSetVisibility( false );
2743 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2747 mIgnoreCommitFlag = true;
2748 mPreEditFlag = false;
2749 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2750 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2753 if( textExceedsMaximunNumberOfCharacters )
2755 EmitMaxInputCharactersReachedSignal();
2758 if( textExceedsBoundary )
2760 EmitInputTextExceedsBoundariesSignal();
2761 PreEditReset( false );
2765 return insertedStringLength;
2768 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2774 cursor = ImageActor::New( cursorImage );
2778 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2781 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2782 cursor.SetNinePatchBorder( border );
2784 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2785 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2786 cursor.SetVisible(false);
2791 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2793 // As cursor is not moving due to grab handle, handle should be hidden.
2794 ShowGrabHandleAndSetVisibility( false );
2796 bool cursorPositionChanged = false;
2799 if ( mCursorPosition >= places )
2801 mCursorPosition = mCursorPosition - places;
2802 cursorPositionChanged = true;
2807 if ((mCursorPosition + places) <= mStyledText.size())
2809 mCursorPosition = mCursorPosition + places;
2810 cursorPositionChanged = true;
2814 if( cursorPositionChanged )
2816 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2818 const TextStyle oldInputStyle( mInputStyle );
2819 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2823 if( oldInputStyle != mInputStyle )
2825 // Updates the line height accordingly with the input style.
2828 EmitStyleChangedSignal();
2831 ImfManager imfManager = ImfManager::Get();
2834 imfManager.SetCursorPosition ( mCursorPosition );
2835 imfManager.NotifyCursorPosition();
2840 void TextInput::DrawCursor(const std::size_t nthChar)
2842 // Get height of cursor and set its size
2843 Size size( CURSOR_THICKNESS, 0.0f );
2844 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2846 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2850 // Measure Font so know how big text will be if no initial text to measure.
2851 size.height = mLineHeight;
2854 mCursor.SetSize(size);
2856 // If the character is italic then the cursor also tilts.
2857 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2859 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2861 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2863 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2864 bool altPositionValid; // Alternate cursor validity flag.
2865 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2866 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2868 SetAltCursorEnabled( altPositionValid );
2870 if(!altPositionValid)
2872 mCursor.SetPosition( position + UI_OFFSET );
2876 size.height *= 0.5f;
2877 mCursor.SetSize(size);
2878 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2880 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2881 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2882 size.height = rowSize.height * 0.5f;
2883 mCursorRTL.SetSize(size);
2884 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2887 if( IsScrollEnabled() )
2889 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2890 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2895 void TextInput::SetAltCursorEnabled( bool enabled )
2897 mCursorRTLEnabled = enabled;
2898 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2901 void TextInput::SetCursorVisibility( bool visible )
2903 mCursorVisibility = visible;
2904 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2905 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2908 void TextInput::CreateGrabHandle( Dali::Image image )
2914 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2918 mGrabHandleImage = image;
2921 mGrabHandle = ImageActor::New(mGrabHandleImage);
2922 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2923 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2925 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2927 ShowGrabHandleAndSetVisibility( false );
2929 CreateGrabArea( mGrabHandle );
2931 mActiveLayer.Add(mGrabHandle);
2935 void TextInput::CreateGrabArea( Actor& parent )
2937 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2938 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2939 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2940 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2941 mTapDetector.Attach( mGrabArea );
2942 mPanGestureDetector.Attach( mGrabArea );
2944 parent.Add(mGrabArea);
2947 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2949 Vector3 actualHandlePosition;
2953 mActualGrabHandlePosition.x += displacement.x;
2954 mActualGrabHandlePosition.y += displacement.y;
2956 // Grab handle should jump to the nearest character and take cursor with it
2957 std::size_t newCursorPosition = 0;
2958 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2960 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2962 bool handleVisible = true;
2964 if( IsScrollEnabled() )
2966 const Vector3 controlSize = GetControlSize();
2967 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
2968 // Scrolls the text if the handle is not in a visible position
2969 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2976 mCurrentHandlePosition = actualHandlePosition;
2977 mScrollDisplacement = Vector2::ZERO;
2981 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
2983 mScrollDisplacement.x = -SCROLL_SPEED;
2985 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
2987 mScrollDisplacement.x = SCROLL_SPEED;
2989 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
2991 mScrollDisplacement.y = -SCROLL_SPEED;
2993 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
2995 mScrollDisplacement.y = SCROLL_SPEED;
3001 if( handleVisible && // Only redraw cursor and do updates if position changed
3002 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3004 mCursorPosition = newCursorPosition;
3006 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3008 const TextStyle oldInputStyle( mInputStyle );
3010 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3012 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3014 if( oldInputStyle != mInputStyle )
3016 // Updates the line height accordingly with the input style.
3019 EmitStyleChangedSignal();
3024 return actualHandlePosition;
3027 void TextInput::ShowGrabHandle( bool visible )
3029 if ( IsGrabHandleEnabled() )
3033 mGrabHandle.SetVisible( mGrabHandleVisibility );
3035 StartMonitoringStageForTouch();
3039 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3041 mGrabHandleVisibility = visible;
3042 ShowGrabHandle( visible );
3045 // Callbacks connected to be Property notifications for Boundary checking.
3047 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3049 mIsSelectionHandleOneFlipped = true;
3050 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3051 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3054 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3056 mIsSelectionHandleOneFlipped = false;
3057 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3058 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3061 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3063 mIsSelectionHandleTwoFlipped = true;
3064 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3065 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3068 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3070 mIsSelectionHandleTwoFlipped = false;
3071 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3072 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3075 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3076 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3078 mSelectionHandleOne.SetOpacity(0.0f);
3081 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3083 mSelectionHandleOne.SetOpacity(1.0f);
3086 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3088 mSelectionHandleTwo.SetOpacity(0.0f);
3091 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3093 mSelectionHandleTwo.SetOpacity(1.0f);
3096 // End of Callbacks connected to be Property notifications for Boundary checking.