2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
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.
18 #include <dali/dali.h>
20 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
21 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
22 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
23 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
25 #include <dali/integration-api/debug.h>
32 #define GET_LOCALE_TEXT(string) dgettext("sys_string", string)
41 #if defined(DEBUG_ENABLED)
42 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
45 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
46 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
47 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
48 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
49 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
50 const Vector4 LIGHTBLUE( 10.0f/255.0f, 140.0f/255.0f, 210.0f/255.0f, 1.0f ); // Used for Selection highlight
52 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
53 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
54 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
55 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
56 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
57 const char* DEFAULT_CURSOR( DALI_IMAGE_DIR "cursor.png" );
59 const char* DEFAULT_ICON_CLIPBOARD( DALI_IMAGE_DIR "copy_paste_icon_clipboard.png" );
60 const char* DEFAULT_ICON_COPY( DALI_IMAGE_DIR "copy_paste_icon_copy.png" );
61 const char* DEFAULT_ICON_CUT( DALI_IMAGE_DIR "copy_paste_icon_cut.png" );
62 const char* DEFAULT_ICON_PASTE( DALI_IMAGE_DIR "copy_paste_icon_paste.png" );
63 const char* DEFAULT_ICON_SELECT( DALI_IMAGE_DIR "copy_paste_icon_select.png" );
64 const char* DEFAULT_ICON_SELECT_ALL( DALI_IMAGE_DIR "copy_paste_icon_select_all.png" );
66 const Vector4 DEFAULT_CURSOR_IMAGE_9_BORDER( 2.0f, 2.0f, 2.0f, 2.0f );
68 const std::string OPTION_SELECT_WORD("select_word"); ///< "Select Word" popup option.
69 const std::string OPTION_SELECT_ALL("select_all"); ///< "Select All" popup option.
70 const std::string OPTION_CUT("cut"); ///< "Cut" popup option.
71 const std::string OPTION_COPY("copy"); ///< "Copy" popup option.
72 const std::string OPTION_PASTE("paste"); ///< "Paste" popup option.
73 const std::string OPTION_CLIPBOARD("clipboard"); ///< "Clipboard" popup option.
75 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
76 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
77 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.0f ); ///< 1. Highlight rendered (z-offset).
78 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.1f ); ///< 2. Text rendered (z-offset).
79 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
81 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
82 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
83 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
84 const float TOP_HANDLE_TOP_OFFSET(-1.5f); ///< Offset between top handle and cutCopyPaste pop-up
85 const float BOTTOM_HANDLE_BOTTOM_OFFSET(1.5f); ///< Offset between bottom handle and cutCopyPaste pop-up
86 const float CURSOR_THICKNESS(6.0f);
87 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
89 const std::string NEWLINE( "\n" );
91 const TextStyle DEFAULT_TEXT_STYLE;
93 const unsigned int SCROLL_TICK_INTERVAL = 50u;
94 const float SCROLL_THRESHOLD = 10.f;
95 const float SCROLL_SPEED = 15.f;
98 * Whether the given style is the default style or not.
99 * @param[in] style The given style.
100 * @return \e true if the given style is the default. Otherwise it returns \e false.
102 bool IsDefaultStyle( const TextStyle& style )
104 return DEFAULT_TEXT_STYLE == style;
108 * Whether the given styled text is using the default style or not.
109 * @param[in] textArray The given text.
110 * @return \e true if the given styled text is using the default style. Otherwise it returns \e false.
112 bool IsTextDefaultStyle( const Toolkit::MarkupProcessor::StyledTextArray& textArray )
114 for( Toolkit::MarkupProcessor::StyledTextArray::const_iterator it = textArray.begin(), endIt = textArray.end(); it != endIt; ++it )
116 const TextStyle& style( (*it).mStyle );
118 if( !IsDefaultStyle( style ) )
128 * Selection state enumeration (FSM)
132 SelectionNone, ///< Currently not encountered selected section.
133 SelectionStarted, ///< Encountered selected section
134 SelectionFinished ///< Finished selected section
137 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
139 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
143 if( ( *it ).mIsVisible )
145 return --cursorPosition;
154 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
156 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
158 if( ( *it ).mIsVisible )
160 return cursorPosition;
166 return cursorPosition;
170 * Whether the given position plus the cursor size offset is inside the given boundary.
172 * @param[in] position The given position.
173 * @param[in] cursorSize The cursor size.
174 * @param[in] controlSize The given boundary.
176 * @return whether the given position is inside the given boundary.
178 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
180 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
181 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
182 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
183 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
187 * Splits a text in two halves.
189 * If the text's number of characters is odd, firstHalf has one more character.
191 * @param[in] text The text to be split.
192 * @param[out] firstHalf The first half of the text.
193 * @param[out] secondHalf The second half of the text.
195 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
196 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
197 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
202 const std::size_t textLength = text.size();
203 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
205 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
206 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
209 } // end of namespace
225 return Toolkit::TextInput::New();
228 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
230 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
231 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
232 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
233 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
234 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
235 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
239 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
241 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
243 QuadCoordinates quad(x1, y1, x2, y2);
244 mQuadList.push_back( quad );
247 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
249 for(std::size_t i = 0;i < mQuadList.size(); i++)
251 QuadCoordinates& quad = mQuadList[i];
253 quad.min.Clamp(min, max);
254 quad.max.Clamp(min, max);
258 // [TextInput] ////////////////////////////////////////////////////////////////
260 Dali::Toolkit::TextInput TextInput::New()
262 // Create the implementation
263 TextInputPtr textInput(new TextInput());
264 // Pass ownership to CustomActor via derived handle
265 Dali::Toolkit::TextInput handle(*textInput);
267 textInput->Initialize();
272 TextInput::TextInput()
273 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
278 mDisplayedTextView(),
279 mStyledPlaceHolderText(),
280 mMaxStringLength( DEFAULT_MAX_SIZE ),
281 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
282 mCursorPosition( 0 ),
283 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
284 mIsSelectionHandleOneFlipped( false ),
285 mIsSelectionHandleTwoFlipped( false ),
286 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
287 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
288 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
289 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
290 mSelectionHandleOnePosition( 0 ),
291 mSelectionHandleTwoPosition( 0 ),
293 mPreEditStartPosition( 0 ),
294 mPreEditLength ( 0 ),
295 mNumberOfSurroundingCharactersDeleted( 0 ),
296 mTouchStartTime( 0 ),
298 mCurrentCopySelecton(),
300 mScrollDisplacement(),
301 mCurrentHandlePosition(),
302 mCurrentSelectionId(),
303 mCurrentSelectionHandlePosition(),
304 mRequestedSelection( 0, 0 ),
305 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
306 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
308 mOverrideAutomaticAlignment( false ),
309 mCursorRTLEnabled( false ),
310 mClosestCursorPositionEOL ( false ),
311 mCursorBlinkStatus( true ),
312 mCursorVisibility( false ),
313 mGrabHandleVisibility( false ),
314 mIsCursorInScrollArea( true ),
315 mIsGrabHandleInScrollArea( true ),
316 mEditModeActive( false ),
317 mEditOnTouch( true ),
318 mTextSelection( true ),
319 mExceedEnabled( true ),
320 mGrabHandleEnabled( true ),
321 mIsSelectionHandleFlipEnabled( true ),
322 mPreEditFlag( false ),
323 mIgnoreCommitFlag( false ),
324 mIgnoreFirstCommitFlag( false ),
325 mSelectingText( false ),
326 mPreserveCursorPosition( false ),
327 mSelectTextOnCommit( false ),
328 mUnderlinedPriorToPreEdit ( false ),
329 mCommitByKeyInput( false ),
330 mPlaceHolderSet( false ),
331 mMarkUpEnabled( false )
333 // Updates the line height accordingly with the input style.
337 TextInput::~TextInput()
339 StopCursorBlinkTimer();
344 std::string TextInput::GetText() const
348 // Return text-view's text only if the text-input's text is not empty
349 // in order to not to return the placeholder text.
350 if( !mStyledText.empty() )
352 text = mDisplayedTextView.GetText();
358 std::string TextInput::GetMarkupText() const
360 std::string markupString;
361 MarkupProcessor::GetMarkupString( mStyledText, markupString );
366 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
368 // Get the placeholder styled text array from the markup string.
369 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
371 if( mStyledText.empty() )
373 // Set the placeholder text only if the styled text is empty.
374 mDisplayedTextView.SetText( mStyledPlaceHolderText );
375 mPlaceHolderSet = true;
379 std::string TextInput::GetPlaceholderText()
381 // Traverses the styled placeholder array getting only the text.
382 // Note that for some languages a 'character' could be represented by more than one 'char'
384 std::string placeholderText;
385 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
387 placeholderText.append( (*it).mText.GetText() );
390 return placeholderText ;
393 void TextInput::SetInitialText(const std::string& initialText)
395 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
397 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
399 mPreEditFlag = false;
400 mIgnoreCommitFlag = true;
403 SetText( initialText );
404 PreEditReset( false ); // Reset keyboard as text changed
407 void TextInput::SetText(const std::string& initialText)
409 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
411 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
413 if( mStyledText.empty() )
415 // If the initial text is empty, set the placeholder text.
416 mDisplayedTextView.SetText( mStyledPlaceHolderText );
417 mPlaceHolderSet = true;
421 mDisplayedTextView.SetText( mStyledText );
422 mPlaceHolderSet = false;
427 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
429 ImfManager imfManager = ImfManager::Get();
432 imfManager.SetCursorPosition( mCursorPosition );
433 imfManager.SetSurroundingText( initialText );
434 imfManager.NotifyCursorPosition();
437 if( IsScrollEnabled() )
439 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
442 ShowGrabHandleAndSetVisibility( false );
451 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
453 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
455 mDisplayedTextView.SetText( styleText );
456 mPlaceHolderSet = false;
458 // If text alignment hasn't been manually set by application developer, then we
459 // automatically determine the alignment based on the content of the text i.e. what
460 // language the text begins with.
461 // TODO: This should determine different alignments for each line (broken by '\n') of text.
462 if(!mOverrideAutomaticAlignment)
464 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
465 bool leftToRight(true);
467 if( !styleText.empty() )
469 bool breakOut(false);
471 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
473 const Text& text = textIter->mText;
475 for( std::size_t i = 0; i < text.GetLength(); ++i )
477 Character character( text[i] );
478 if( character.GetCharacterDirection() != Character::Neutral )
480 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
488 // Based on this direction, either left or right align text if not manually set by application developer.
489 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
490 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
491 Toolkit::Alignment::VerticalTop ) );
492 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
498 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
500 mMaxStringLength = maxChars;
503 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
505 DALI_ASSERT_DEBUG( maxLines > 0 )
509 mNumberOflinesLimit = maxLines;
513 std::size_t TextInput::GetNumberOfLinesLimit() const
515 return mNumberOflinesLimit;
518 std::size_t TextInput::GetNumberOfCharacters() const
520 return mStyledText.size();
523 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
525 return mInputStartedSignalV2;
528 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
530 return mInputFinishedSignalV2;
533 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
535 return mCutAndPasteToolBarDisplayedV2;
538 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
540 return mStyleChangedSignalV2;
543 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
545 return mTextModifiedSignal;
548 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
550 return mMaxInputCharactersReachedSignalV2;
553 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
555 return mInputTextExceedBoundariesSignalV2;
558 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
560 Dali::BaseHandle handle( object );
562 bool connected( true );
563 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
565 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
567 textInput.InputStartedSignal().Connect( tracker, functor );
569 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
571 textInput.InputFinishedSignal().Connect( tracker, functor );
573 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
575 textInput.StyleChangedSignal().Connect( tracker, functor );
577 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
579 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
581 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
583 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
587 // signalName does not match any signal
594 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
598 // update line height before calculate the actual position.
603 if( setCursorOnTouchPoint )
605 // Sets the cursor position for the given touch point.
606 ReturnClosestIndex( touchPoint, mCursorPosition );
608 // Creates the grab handle.
609 if( IsGrabHandleEnabled() )
611 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
615 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
616 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
617 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
618 ShowGrabHandleAndSetVisibility( true );
620 // Scrolls the text-view if needed.
621 if( IsScrollEnabled() )
623 ScrollTextViewToMakeCursorVisible( cursorPosition );
629 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
641 bool TextInput::IsEditable() const
643 return mEditModeActive;
646 void TextInput::SetEditOnTouch( bool editOnTouch )
648 mEditOnTouch = editOnTouch;
651 bool TextInput::IsEditOnTouch() const
656 void TextInput::SetTextSelectable( bool textSelectable )
658 mTextSelection = textSelectable;
661 bool TextInput::IsTextSelectable() const
663 return mTextSelection;
666 bool TextInput::IsTextSelected() const
668 return mHighlightMeshActor;
671 void TextInput::DeSelectText()
678 void TextInput::SetGrabHandleImage(Dali::Image image )
682 CreateGrabHandle(image);
686 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
688 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
692 mCursor.SetImage( image );
693 mCursor.SetNinePatchBorder( border );
697 Vector3 TextInput::GetSelectionHandleSize()
699 return DEFAULT_SELECTION_HANDLE_SIZE;
702 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
704 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
708 mCursorRTL.SetImage( image);
709 mCursorRTL.SetNinePatchBorder( border );
713 void TextInput::EnableGrabHandle(bool toggle)
715 // enables grab handle with will in turn de-activate magnifier
716 mGrabHandleEnabled = toggle;
719 bool TextInput::IsGrabHandleEnabled()
721 // if false then magnifier will be shown instead.
722 return mGrabHandleEnabled;
725 void TextInput::EnableSelectionHandleFlip( bool toggle )
727 // Deprecated function. To be removed.
728 mIsSelectionHandleFlipEnabled = toggle;
731 bool TextInput::IsSelectionHandleFlipEnabled()
733 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
737 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
739 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
740 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
741 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
743 mSelectionHandleFlipMargin = margin;
746 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
748 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
749 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
751 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
752 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
754 const Vector4 boundary( originX,
756 originX + boundingRectangle.width,
757 originY + boundingRectangle.height );
759 mBoundingRectangleWorldCoordinates = boundary;
762 const Rect<float> TextInput::GetBoundingRectangle() const
764 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
766 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
767 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
769 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
774 const Vector4& TextInput::GetSelectionHandleFlipMargin()
776 return mSelectionHandleFlipMargin;
779 void TextInput::SetTextColor( const Vector4& color )
781 mDisplayedTextView.SetColor( color );
784 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
786 if( style != mInputStyle )
789 bool emitSignal = false;
791 // mask: modify style according to mask, if different emit signal.
792 const TextStyle oldInputStyle( mInputStyle );
794 // Copy the new style.
795 mInputStyle.Copy( style, mask );
797 // if style has changed, emit signal.
798 if( oldInputStyle != mInputStyle )
803 // Updates the line height accordingly with the input style.
806 // Changing font point size will require the cursor to be re-sized
811 EmitStyleChangedSignal();
816 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
818 if ( IsTextSelected() )
820 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
821 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
823 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
825 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
828 // Keeps the old style to be compared with the new one.
829 const TextStyle oldInputStyle( mInputStyle );
831 // Copy only those parameters from the style which are set in the mask.
832 mInputStyle.Copy( style, mask );
834 if( mInputStyle != oldInputStyle )
836 // Updates the line height accordingly with the input style.
839 EmitStyleChangedSignal();
844 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
846 if( !mStyledText.empty() )
848 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
852 TextStyle TextInput::GetStyleAtCursor() const
856 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
858 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
859 style = mStyledText.at( mCursorPosition-1 ).mStyle;
865 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
867 Dali::Font defaultFont = Dali::Font::New();
868 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
875 TextStyle TextInput::GetStyleAt( std::size_t position ) const
877 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
879 if( position >= mStyledText.size() )
881 position = mStyledText.size() - 1;
884 return mStyledText.at( position ).mStyle;
887 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
889 mDisplayedTextView.SetTextAlignment( align );
890 mOverrideAutomaticAlignment = true;
893 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
895 mDisplayedTextView.SetLineJustification( justification );
896 mOverrideAutomaticAlignment = true;
899 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
901 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
904 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
906 return mDisplayedTextView.GetFadeBoundary();
909 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
911 return mDisplayedTextView.GetTextAlignment();
914 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
916 mDisplayedTextView.SetMultilinePolicy( policy );
919 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
921 return mDisplayedTextView.GetMultilinePolicy();
924 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
926 mDisplayedTextView.SetWidthExceedPolicy( policy );
929 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
931 return mDisplayedTextView.GetWidthExceedPolicy();
934 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
936 mDisplayedTextView.SetHeightExceedPolicy( policy );
939 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
941 return mDisplayedTextView.GetHeightExceedPolicy();
944 void TextInput::SetExceedEnabled( bool enable )
946 mExceedEnabled = enable;
949 bool TextInput::GetExceedEnabled() const
951 return mExceedEnabled;
954 void TextInput::SetBackground(Dali::Image image )
956 // TODO Should add this function and add public api to match.
959 bool TextInput::OnTouchEvent(const TouchEvent& event)
964 bool TextInput::OnKeyEvent(const KeyEvent& event)
966 switch( event.state )
970 return OnKeyDownEvent(event);
976 return OnKeyUpEvent(event);
988 void TextInput::OnKeyInputFocusGained()
990 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
992 mEditModeActive = true;
994 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
996 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
998 // Updates the line height accordingly with the input style.
1001 // Connect the signals to use in text input.
1002 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1003 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1005 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1008 GetTextLayoutInfo();
1011 SetCursorVisibility( true );
1012 StartCursorBlinkTimer();
1014 Toolkit::TextInput handle( GetOwner() );
1015 mInputStartedSignalV2.Emit( handle );
1017 ImfManager imfManager = ImfManager::Get();
1021 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1023 // Notify that the text editing start.
1024 imfManager.Activate();
1026 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1027 imfManager.SetRestoreAferFocusLost( true );
1029 imfManager.SetCursorPosition( mCursorPosition );
1030 imfManager.NotifyCursorPosition();
1033 mClipboard = Clipboard::Get(); // Store handle to clipboard
1035 // Now in edit mode we can accept string to paste from clipboard
1036 if( Adaptor::IsAvailable() )
1038 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1041 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1046 void TextInput::OnKeyInputFocusLost()
1048 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1052 // If key input focus is lost, it removes the
1053 // underline from the last pre-edit text.
1054 RemovePreEditStyle();
1055 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1056 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1060 ImfManager imfManager = ImfManager::Get();
1063 // The text editing is finished. Therefore the imf manager don't have restore activation.
1064 imfManager.SetRestoreAferFocusLost( false );
1066 // Notify that the text editing finish.
1067 imfManager.Deactivate();
1069 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1071 // Disconnect signal used the text input.
1072 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1074 Toolkit::TextInput handle( GetOwner() );
1075 mInputFinishedSignalV2.Emit( handle );
1076 mEditModeActive = false;
1077 mPreEditFlag = false;
1079 SetCursorVisibility( false );
1080 StopCursorBlinkTimer();
1082 ShowGrabHandleAndSetVisibility( false );
1085 // No longer in edit mode so do not want to receive string from clipboard
1086 if( Adaptor::IsAvailable() )
1088 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1091 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1093 Clipboard clipboard = Clipboard::Get();
1097 clipboard.HideClipboard();
1102 void TextInput::OnControlStageConnection()
1104 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1106 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1108 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1112 void TextInput::CreateActiveLayer()
1114 Actor self = Self();
1115 mActiveLayer = Layer::New();
1117 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1118 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1119 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1121 self.Add( mActiveLayer );
1122 mActiveLayer.RaiseToTop();
1125 void TextInput::OnInitialize()
1127 CreateTextViewActor();
1131 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1132 // different positions depending on language)
1133 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1134 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1135 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1137 Actor self = Self();
1138 self.Add( mCursor );
1139 self.Add( mCursorRTL );
1141 mCursorVisibility = false;
1143 CreateActiveLayer(); // todo move this so layer only created when needed.
1145 // Assign names to image actors
1146 mCursor.SetName("mainCursor");
1147 mCursorRTL.SetName("rtlCursor");
1150 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1152 mDisplayedTextView.SetSize( targetSize );
1153 GetTextLayoutInfo();
1154 mActiveLayer.SetSize(targetSize);
1157 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1159 Relayout( mDisplayedTextView, size, container );
1160 GetTextLayoutInfo();
1165 Vector3 TextInput::GetNaturalSize()
1167 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1169 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1171 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1172 naturalSize.height = mLineHeight;
1178 float TextInput::GetHeightForWidth( float width )
1180 float height = mDisplayedTextView.GetHeightForWidth( width );
1182 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1184 // If the height is zero, it means there is no text. Let's return the cursor height.
1185 height = mLineHeight;
1191 /*end of Virtual methods from parent*/
1193 // Private Internal methods
1195 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1197 switch (gesture.state)
1199 case Gesture::Started:
1200 // fall through so code not duplicated
1201 case Gesture::Continuing:
1203 if (actor == mGrabArea)
1205 SetCursorVisibility( true );
1206 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1207 MoveGrabHandle( gesture.displacement );
1208 HidePopup(); // Do not show popup whilst handle is moving
1210 else if (actor == mHandleOneGrabArea)
1212 // the displacement in PanGesture is affected by the actor's rotation.
1213 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1214 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1216 MoveSelectionHandle( HandleOne, gesture.displacement );
1218 mState = StateDraggingHandle;
1221 else if (actor == mHandleTwoGrabArea)
1223 // the displacement in PanGesture is affected by the actor's rotation.
1224 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1225 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1227 MoveSelectionHandle( HandleTwo, gesture.displacement );
1229 mState = StateDraggingHandle;
1235 case Gesture::Finished:
1237 // Revert back to non-pressed selection handle images
1238 if (actor == mGrabArea)
1240 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1241 SetCursorVisibility( true );
1242 SetUpPopUpSelection();
1245 if (actor == mHandleOneGrabArea)
1247 // the displacement in PanGesture is affected by the actor's rotation.
1248 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1249 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1251 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1253 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1255 ShowPopupCutCopyPaste();
1257 if (actor == mHandleTwoGrabArea)
1259 // the displacement in PanGesture is affected by the actor's rotation.
1260 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1261 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1263 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1265 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1267 ShowPopupCutCopyPaste();
1276 // Stop the flashing animation so easy to see when moved.
1277 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1279 if (touch.GetPoint(0).state == TouchPoint::Down)
1281 SetCursorVisibility( true );
1282 StopCursorBlinkTimer();
1284 else if (touch.GetPoint(0).state == TouchPoint::Up)
1286 SetCursorVisibility( true );
1287 StartCursorBlinkTimer();
1292 // selection handle one
1293 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1295 if (touch.GetPoint(0).state == TouchPoint::Down)
1297 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1299 else if (touch.GetPoint(0).state == TouchPoint::Up)
1301 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1306 // selection handle two
1307 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1309 if (touch.GetPoint(0).state == TouchPoint::Down)
1311 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1313 else if (touch.GetPoint(0).state == TouchPoint::Up)
1315 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1320 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1322 // If text exists then select nearest word.
1323 if ( !mStyledText.empty())
1327 ShowGrabHandleAndSetVisibility( false );
1332 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1333 // converts the pre-edit word being displayed to a committed word.
1334 if ( !mUnderlinedPriorToPreEdit )
1337 style.SetUnderline( false );
1338 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1340 mPreEditFlag = false;
1341 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1342 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1343 PreEditReset( false );
1345 mCursorPosition = 0;
1347 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1348 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1350 ImfManager imfManager = ImfManager::Get();
1353 imfManager.SetCursorPosition ( mCursorPosition );
1354 imfManager.NotifyCursorPosition();
1357 std::size_t start = 0;
1358 std::size_t end = 0;
1359 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1361 SelectText( start, end );
1363 // if no text but clipboard has content then show paste option
1364 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1366 ShowPopupCutCopyPaste();
1369 // If no text and clipboard empty then do nothing
1372 // TODO: Change the function name to be more general.
1373 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1375 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1376 , (mEditOnTouch)?"true":"false"
1377 , (mEditModeActive)?"true":"false");
1379 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1384 if( mGrabArea == actor )
1386 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1388 SetUpPopUpSelection();
1398 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1400 // Initially don't create the grab handle.
1401 bool createGrabHandle = false;
1403 if ( !mEditModeActive )
1405 // update line height before calculate the actual position.
1408 // Only start edit mode if TextInput configured to edit on touch
1411 // Set the initial cursor position in the tap point.
1412 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1414 // Create the grab handle.
1415 // TODO Make this a re-usable function.
1416 if ( IsGrabHandleEnabled() )
1418 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1422 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1423 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1424 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1425 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1429 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1430 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1431 // otherwise the Grab handle will be shown when selecting.
1438 // Show the keyboard if it was hidden.
1439 if (!VirtualKeyboard::IsVisible())
1441 VirtualKeyboard::Show();
1444 // Reset keyboard as tap event has occurred.
1445 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1446 PreEditReset( true );
1448 GetTextLayoutInfo();
1450 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1452 // 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.
1454 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1456 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1458 // Notify keyboard so it can 're-capture' word for predictive text.
1459 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1460 ImfManager imfManager = ImfManager::Get();
1463 imfManager.SetCursorPosition ( mCursorPosition );
1464 imfManager.NotifyCursorPosition();
1466 const TextStyle oldInputStyle( mInputStyle );
1468 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1472 // Create the grab handle.
1473 // Grab handle is created later.
1474 createGrabHandle = true;
1476 if( oldInputStyle != mInputStyle )
1478 // Updates the line height accordingly with the input style.
1481 EmitStyleChangedSignal();
1486 if ( createGrabHandle && IsGrabHandleEnabled() )
1488 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1492 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1493 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1494 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1495 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1500 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1502 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1504 if(longPress.state == Dali::Gesture::Started)
1506 // Start edit mode on long press
1507 if ( !mEditModeActive )
1512 // If text exists then select nearest word.
1513 if ( !mStyledText.empty())
1517 ShowGrabHandleAndSetVisibility( false );
1522 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1523 // converts the pre-edit word being displayed to a committed word.
1524 if ( !mUnderlinedPriorToPreEdit )
1527 style.SetUnderline( false );
1528 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1530 mPreEditFlag = false;
1531 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1532 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1533 PreEditReset( false );
1535 mCursorPosition = 0;
1537 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1538 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1540 ImfManager imfManager = ImfManager::Get();
1543 imfManager.SetCursorPosition ( mCursorPosition );
1544 imfManager.NotifyCursorPosition();
1546 std::size_t start = 0;
1547 std::size_t end = 0;
1548 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1550 SelectText( start, end );
1553 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1554 if ( mClipboard.NumberOfItems() || !mStyledText.empty() )
1556 ShowPopupCutCopyPaste();
1561 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1563 const Text clipboardText( notifier.GetContent() );
1564 PasteText( clipboardText );
1566 SetCursorVisibility( true );
1567 StartCursorBlinkTimer();
1569 ShowGrabHandleAndSetVisibility( false );
1575 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1577 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1579 const std::string& name = button.GetName();
1581 if(name == OPTION_SELECT_WORD)
1583 std::size_t start = 0;
1584 std::size_t end = 0;
1585 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1587 SelectText( start, end );
1589 else if(name == OPTION_SELECT_ALL)
1591 SetCursorVisibility(false);
1592 StopCursorBlinkTimer();
1594 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1595 std::size_t start = 0;
1597 SelectText( start, end );
1599 else if(name == OPTION_CUT)
1601 bool ret = CopySelectedTextToClipboard();
1605 DeleteHighlightedText( true );
1609 SetCursorVisibility( true );
1610 StartCursorBlinkTimer();
1614 else if(name == OPTION_COPY)
1616 CopySelectedTextToClipboard();
1620 SetCursorVisibility( true );
1621 StartCursorBlinkTimer();
1625 else if(name == OPTION_PASTE)
1627 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1629 PasteText(retrievedString);
1631 SetCursorVisibility( true );
1632 StartCursorBlinkTimer();
1634 ShowGrabHandleAndSetVisibility( false );
1638 else if(name == OPTION_CLIPBOARD)
1640 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1641 // Hence pass the false parameter for signalFinished.
1642 HidePopup( true, false );
1643 mClipboard.ShowClipboard();
1649 bool TextInput::OnCursorBlinkTimerTick()
1652 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1653 if ( mCursorRTLEnabled )
1655 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1657 mCursorBlinkStatus = !mCursorBlinkStatus;
1662 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1664 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1666 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1667 if(mHighlightMeshActor && mState == StateEdit)
1669 ShowPopupCutCopyPaste();
1673 //FIXME this routine needs to be re-written as it contains too many branches.
1674 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1676 std::string keyName = event.keyPressedName;
1677 std::string keyString = event.keyPressed;
1679 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1681 // Do not consume "Tab" and "Escape" keys.
1682 if(keyName == "Tab" || keyName == "Escape")
1684 // Escape key to end the edit mode
1690 HidePopup(); // If Pop-up shown then hides it as editing text.
1692 // Update Flag, indicates whether to update the text-input contents or not.
1693 // Any key stroke that results in a visual change of the text-input should
1694 // set this flag to true.
1697 // Whether to scroll text to cursor position.
1698 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1699 bool scroll = false;
1701 if (keyName == "Return")
1703 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1705 bool preEditFlagPreviouslySet( mPreEditFlag );
1707 if (mHighlightMeshActor)
1709 // replaces highlighted text with new line
1710 DeleteHighlightedText( false );
1712 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1714 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1715 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1718 mCommitByKeyInput = true;
1721 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1722 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1724 mPreEditFlag = true;
1725 mIgnoreCommitFlag = false;
1735 else if ( keyName == "space" )
1737 if ( mHighlightMeshActor )
1739 // Some text is selected so erase it before adding space.
1740 DeleteHighlightedText( true );
1744 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1746 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1747 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1750 mCommitByKeyInput = true;
1755 else if (keyName == "BackSpace")
1757 if ( mHighlightMeshActor )
1759 // Some text is selected so erase it
1760 DeleteHighlightedText( true );
1765 if ( mCursorPosition > 0 )
1767 DeleteCharacter( mCursorPosition );
1773 else if (keyName == "Right")
1778 else if (keyName == "Left")
1780 AdvanceCursor(true);
1783 else // event is a character
1785 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1786 if ( !keyString.empty() )
1788 if ( mHighlightMeshActor )
1790 // replaces highlighted text with new character
1791 DeleteHighlightedText( false );
1795 // Received key String
1796 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1802 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1803 // as this is a costly operation.
1809 if(update || scroll)
1811 if( IsScrollEnabled() )
1813 // Calculates the new cursor position (in actor coordinates)
1814 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1816 ScrollTextViewToMakeCursorVisible( cursorPosition );
1823 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1825 std::string keyName = event.keyPressedName;
1826 std::string keyString = event.keyPressed;
1828 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1830 // The selected text become deselected when the key code is DALI_KEY_BACK.
1831 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1840 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1842 // Updates the stored scroll position.
1843 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1845 const Vector3& controlSize = GetControlSize();
1846 Size cursorSize( CURSOR_THICKNESS, 0.f );
1848 // Updates the cursor and grab handle position and visibility.
1849 if( mGrabHandle || mCursor )
1851 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1852 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1854 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1856 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1860 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1861 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1866 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1867 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1871 // Updates the selection handles and highlighted text position and visibility.
1872 if( mSelectionHandleOne && mSelectionHandleTwo )
1874 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1875 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1876 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1877 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1878 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1879 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1881 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1882 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1884 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1885 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1886 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1887 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1889 if( mHighlightMeshActor )
1891 mHighlightMeshActor.SetVisible( true );
1897 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1899 // Scroll the text to make the cursor visible.
1900 const Size cursorSize( CURSOR_THICKNESS,
1901 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1903 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1905 const Vector3& controlSize = GetControlSize();
1907 // Calculates the new scroll position.
1908 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1909 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1911 scrollOffset.x += cursorPosition.x;
1914 if( cursorPosition.y - cursorSize.height < 0.f )
1916 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1918 else if( cursorPosition.y > controlSize.height )
1920 scrollOffset.y += cursorPosition.y;
1923 // Sets the new scroll position.
1924 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1925 SetScrollPosition( scrollOffset );
1928 void TextInput::StartScrollTimer()
1932 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1933 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1936 if( !mScrollTimer.IsRunning() )
1938 mScrollTimer.Start();
1942 void TextInput::StopScrollTimer()
1946 mScrollTimer.Stop();
1950 bool TextInput::OnScrollTimerTick()
1952 // TODO: need to set the new style accordingly the new handle position.
1954 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1956 // nothing to do if all handles are invisible or doesn't exist.
1962 // Choose between the grab handle or the selection handles.
1963 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1964 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1965 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1967 std::size_t newCursorPosition = 0;
1968 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
1970 // Whether the handle's position is different of the previous one and in the case of the selection handle,
1971 // the new selection handle's position needs to be different of the other one.
1972 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
1973 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
1974 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
1976 if( differentSelectionHandles )
1978 handlePosition = newCursorPosition;
1980 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
1982 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
1984 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
1985 scrollPosition += scrollDelta;
1986 SetScrollPosition( scrollPosition );
1988 if( mDisplayedTextView.IsScrollPositionTrimmed() )
1993 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
1996 actualHandlePosition.x += mScrollDisplacement.x;
1997 actualHandlePosition.y += mScrollDisplacement.y;
2002 // Public Internal Methods (public for testing purpose)
2004 void TextInput::SetUpTouchEvents()
2006 if ( !mTapDetector )
2008 mTapDetector = TapGestureDetector::New();
2009 // Attach the actors and connect the signal
2010 mTapDetector.Attach(Self());
2012 // As contains children which may register for tap the default control detector is not used.
2013 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2016 if ( !mDoubleTapDetector )
2018 mDoubleTapDetector = TapGestureDetector::New();
2019 mDoubleTapDetector.SetTapsRequired( 2 );
2020 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2022 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2023 // so that we do not, unnecessarily, have a double tap request all the time
2026 if ( !mPanGestureDetector )
2028 mPanGestureDetector = PanGestureDetector::New();
2029 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2032 if ( !mLongPressDetector )
2034 mLongPressDetector = LongPressGestureDetector::New();
2035 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2036 mLongPressDetector.Attach(Self());
2040 void TextInput::CreateTextViewActor()
2042 mDisplayedTextView = Toolkit::TextView::New();
2043 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2044 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2045 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2046 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2047 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2048 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2049 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2050 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2051 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2052 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2054 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2056 Self().Add( mDisplayedTextView );
2059 // Start a timer to initiate, used by the cursor to blink.
2060 void TextInput::StartCursorBlinkTimer()
2062 if ( !mCursorBlinkTimer )
2064 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2065 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2068 if ( !mCursorBlinkTimer.IsRunning() )
2070 mCursorBlinkTimer.Start();
2074 // Start a timer to initiate, used by the cursor to blink.
2075 void TextInput::StopCursorBlinkTimer()
2077 if ( mCursorBlinkTimer )
2079 mCursorBlinkTimer.Stop();
2083 void TextInput::StartEditMode()
2085 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2087 if(!mEditModeActive)
2092 if ( mDoubleTapDetector )
2094 mDoubleTapDetector.Attach( Self() );
2098 void TextInput::EndEditMode()
2100 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2102 ClearKeyInputFocus();
2104 if ( mDoubleTapDetector )
2106 mDoubleTapDetector.Detach( Self() );
2110 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2112 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2114 mUnderlinedPriorToPreEdit = mInputStyle.GetUnderline();
2116 style.SetUnderline( true );
2117 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2121 void TextInput::RemovePreEditStyle()
2123 if ( !mUnderlinedPriorToPreEdit )
2126 style.SetUnderline( false );
2127 SetActiveStyle( style, TextStyle::UNDERLINE );
2131 // IMF related methods
2134 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2136 bool update( false );
2137 bool preeditResetRequired ( false );
2139 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2141 HidePopup(); // If Pop-up shown then hides it as editing text.
2144 switch ( imfEvent.eventName )
2146 case ImfManager::PREEDIT:
2148 mIgnoreFirstCommitFlag = false;
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 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2159 if( IsScrollEnabled() )
2161 // Calculates the new cursor position (in actor coordinates)
2162 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2163 ScrollTextViewToMakeCursorVisible( cursorPosition );
2170 case ImfManager::COMMIT:
2172 if( mIgnoreFirstCommitFlag )
2174 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2175 mIgnoreFirstCommitFlag = false;
2179 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2181 // 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
2182 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2184 // replaces highlighted text with new character
2185 DeleteHighlightedText( false );
2188 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2189 // not needed, one such scenario is when the pre-edit word is too long to fit.
2190 if ( !mIgnoreCommitFlag )
2192 update = CommitReceived( imfEvent.predictiveString );
2196 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2202 if( IsScrollEnabled() )
2204 // Calculates the new cursor position (in actor coordinates)
2205 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2207 ScrollTextViewToMakeCursorVisible( cursorPosition );
2212 case ImfManager::DELETESURROUNDING:
2214 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2215 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2217 mPreEditFlag = false;
2219 std::size_t toDelete = 0;
2220 std::size_t numberOfCharacters = 0;
2222 if( mHighlightMeshActor )
2224 // delete highlighted text.
2225 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2226 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2230 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2232 toDelete = mCursorPosition + imfEvent.cursorOffset;
2234 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2236 numberOfCharacters = mStyledText.size() - toDelete;
2240 numberOfCharacters = imfEvent.numberOfChars;
2243 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2244 DeleteRange( toDelete, numberOfCharacters );
2246 mCursorPosition = toDelete;
2247 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2251 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2254 case ImfManager::GETSURROUNDING:
2256 // 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
2257 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2258 if (! ( mHighlightMeshActor || mSelectingText ) )
2260 std::string text( GetText() );
2261 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2263 imfManager.SetCursorPosition( mCursorPosition );
2264 imfManager.SetSurroundingText( text );
2267 if( 0 != mNumberOfSurroundingCharactersDeleted )
2269 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2270 mNumberOfSurroundingCharactersDeleted = 0;
2272 if( mStyledText.empty() )
2274 // Styled text is empty, so set the placeholder text.
2275 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2276 mPlaceHolderSet = true;
2281 case ImfManager::VOID:
2283 DALI_ASSERT_DEBUG( false );
2287 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2289 return callbackData;
2292 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2294 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2296 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2297 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2299 bool preeditResetRequest ( false );
2301 if( mPreEditFlag ) // Already in pre-edit state.
2303 if( mStyledText.size() >= mMaxStringLength )
2305 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2306 // Cannot fit these characters into field, clear pre-edit.
2307 if ( !mUnderlinedPriorToPreEdit )
2310 style.SetUnderline( false );
2311 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2313 mIgnoreCommitFlag = true;
2314 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2315 mPreEditFlag = false;
2316 EmitMaxInputCharactersReachedSignal();
2320 // delete existing pre-edit string
2321 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2323 // Store new pre-edit string
2324 mPreEditString.SetText( keyString );
2326 if ( keyString.empty() )
2328 mPreEditFlag = false;
2329 mCursorPosition = mPreEditStartPosition;
2331 if( mStyledText.empty() )
2333 // Styled text is empty, so set the placeholder text.
2334 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2335 mPlaceHolderSet = true;
2339 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2341 GetTextLayoutInfo();
2346 // Insert new pre-edit string. InsertAt updates the size and position table.
2347 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2348 // 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.
2349 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2350 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2351 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2354 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2358 else // mPreEditFlag not set
2360 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2362 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2363 // new pre-edit so move into pre-edit state by setting flag
2364 mPreEditFlag = true;
2365 mPreEditString.SetText( keyString ); // store new pre-edit string
2366 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2367 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2368 // 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.
2369 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2370 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2371 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2372 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2378 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2382 return preeditResetRequest;
2385 bool TextInput::CommitReceived(const std::string& keyString )
2387 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2388 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2390 bool update( false );
2392 RemovePreEditStyle();
2394 const std::size_t styledTextSize( mStyledText.size() );
2395 if( styledTextSize >= mMaxStringLength )
2397 // Cannot fit these characters into field, clear pre-edit.
2400 mIgnoreCommitFlag = true;
2401 mPreEditFlag = false;
2403 EmitMaxInputCharactersReachedSignal();
2409 // delete existing pre-edit string
2410 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2411 mPreEditFlag = false;
2413 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2414 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2416 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2418 // No need to update cursor position as Cursor location given by touch.
2419 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2420 mPreserveCursorPosition = false;
2424 // Cursor not set by touch so needs to be re-positioned to input more text
2425 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2427 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2428 if ( mCommitByKeyInput )
2430 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2431 mCommitByKeyInput = false;
2437 if ( mSelectTextOnCommit )
2439 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2444 else // mPreEditFlag not set
2446 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2448 if( mStyledText.empty() && mPlaceHolderSet )
2450 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2451 mDisplayedTextView.SetText( "" );
2452 mNumberOfSurroundingCharactersDeleted = 0;
2453 mPlaceHolderSet = false;
2455 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2457 mNumberOfSurroundingCharactersDeleted = 0;
2462 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2467 mSelectTextOnCommit = false;
2469 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2470 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2475 // End of IMF related methods
2477 std::size_t TextInput::DeletePreEdit()
2479 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2481 DALI_ASSERT_DEBUG( mPreEditFlag );
2483 const std::size_t preEditStringLength = mPreEditString.GetLength();
2484 const std::size_t styledTextSize = mStyledText.size();
2486 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2488 // Prevents erase items outside mStyledText bounds.
2489 if( mPreEditStartPosition > styledTextSize )
2491 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2492 mPreEditStartPosition = styledTextSize;
2495 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2497 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2498 endPosition = styledTextSize;
2501 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2503 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2504 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2506 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2508 return preEditStringLength;
2511 void TextInput::PreEditReset( bool preserveCursorPosition )
2513 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2514 preserveCursorPosition, mCursorPosition);
2516 // 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.
2517 mPreserveCursorPosition = preserveCursorPosition;
2519 // Reset incase we are in a pre-edit state.
2520 ImfManager imfManager = ImfManager::Get();
2523 imfManager.Reset(); // Will trigger a commit message
2527 void TextInput::CursorUpdate()
2531 ImfManager imfManager = ImfManager::Get();
2534 std::string text( GetText() );
2535 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2536 imfManager.SetCursorPosition ( mCursorPosition );
2537 imfManager.NotifyCursorPosition();
2541 /* Delete highlighted characters redisplay*/
2542 void TextInput::DeleteHighlightedText( bool inheritStyle )
2544 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2546 if(mHighlightMeshActor)
2548 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2550 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2551 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2553 // Get the styled text of the characters to be deleted as it may be needed if
2554 // the "exceed the text-input's boundaries" option is disabled.
2555 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2557 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2559 mStyledText.erase( start, end ); // erase range of characters
2561 // Remove text from TextView.
2563 if( mStyledText.empty() )
2565 // Styled text is empty, so set the placeholder text.
2566 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2567 mPlaceHolderSet = true;
2571 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2573 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2575 // It may happen than after removing a white space or a new line character,
2576 // two words merge, this new word could be big enough to not fit in its
2577 // current line, so moved to the next one, and make some part of the text to
2578 // exceed the text-input's boundary.
2579 if( !mExceedEnabled )
2581 // Get the new text layout after removing some characters.
2582 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2584 // Get text-input's size.
2585 const Vector3& size = GetControlSize();
2587 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2588 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2590 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2592 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2593 styledCharactersToDelete.begin(),
2594 styledCharactersToDelete.end() );
2598 GetTextLayoutInfo();
2604 const TextStyle oldInputStyle( mInputStyle );
2606 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2608 if( oldInputStyle != mInputStyle )
2610 // Updates the line height accordingly with the input style.
2613 EmitStyleChangedSignal();
2619 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2621 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2622 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2624 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2627 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2629 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2630 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2632 mStyledText.erase(itStart, itEnd);
2634 // update the selection handles if they are visible.
2635 if( mHighlightMeshActor )
2637 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2638 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2640 if( minHandle >= start + ncharacters )
2642 minHandle -= ncharacters;
2644 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2649 if( maxHandle >= start + ncharacters )
2651 maxHandle -= ncharacters;
2653 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2659 // 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.
2662 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2664 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2665 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2666 // Mean we do not re-draw the text more than we have too.
2669 /* Delete character at current cursor position and redisplay*/
2670 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2672 // Ensure positionToDelete is not out of bounds.
2673 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2674 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2675 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2677 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2680 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2682 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2684 // Get the styled text of the character to be deleted as it may be needed if
2685 // the "exceed the text-input's boundaries" option is disabled.
2686 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2688 mStyledText.erase(it); // erase the character left of positionToDelete
2690 if( mStyledText.empty() )
2692 // Styled text is empty, so set the placeholder text.
2693 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2694 mPlaceHolderSet = true;
2698 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2700 const Character characterToDelete = styledCharacterToDelete.mText[0];
2702 // It may happen than after removing a white space or a new line character,
2703 // two words merge, this new word could be big enough to not fit in its
2704 // current line, so moved to the next one, and make some part of the text to
2705 // exceed the text-input's boundary.
2706 if( !mExceedEnabled )
2708 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2710 // Get the new text layout after removing one character.
2711 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2713 // Get text-input's size.
2714 const Vector3& size = GetControlSize();
2716 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2717 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2719 MarkupProcessor::StyledTextArray array;
2720 array.push_back( styledCharacterToDelete );
2721 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2723 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2728 GetTextLayoutInfo();
2730 ShowGrabHandleAndSetVisibility( false );
2732 mCursorPosition = positionToDelete -1;
2734 const TextStyle oldInputStyle( mInputStyle );
2736 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2738 if( oldInputStyle != mInputStyle )
2740 // Updates the line height accordingly with the input style.
2743 EmitStyleChangedSignal();
2748 /*Insert new character into the string and (optionally) redisplay text-input*/
2749 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2751 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2753 // Ensure insertionPosition is not out of bounds.
2754 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2756 bool textExceedsMaximunNumberOfCharacters = false;
2757 bool textExceedsBoundary = false;
2758 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2760 ShowGrabHandleAndSetVisibility( false );
2762 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2766 mIgnoreCommitFlag = true;
2767 mPreEditFlag = false;
2768 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2769 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2772 if( textExceedsMaximunNumberOfCharacters )
2774 EmitMaxInputCharactersReachedSignal();
2777 if( textExceedsBoundary )
2779 EmitInputTextExceedsBoundariesSignal();
2780 PreEditReset( false );
2784 return insertedStringLength;
2787 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2793 cursor = ImageActor::New( cursorImage );
2797 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2800 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2801 cursor.SetNinePatchBorder( border );
2803 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2804 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2805 cursor.SetVisible(false);
2810 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2812 // As cursor is not moving due to grab handle, handle should be hidden.
2813 ShowGrabHandleAndSetVisibility( false );
2815 bool cursorPositionChanged = false;
2818 if ( mCursorPosition >= places )
2820 mCursorPosition = mCursorPosition - places;
2821 cursorPositionChanged = true;
2826 if ((mCursorPosition + places) <= mStyledText.size())
2828 mCursorPosition = mCursorPosition + places;
2829 cursorPositionChanged = true;
2833 if( cursorPositionChanged )
2835 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2837 const TextStyle oldInputStyle( mInputStyle );
2838 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2842 if( oldInputStyle != mInputStyle )
2844 // Updates the line height accordingly with the input style.
2847 EmitStyleChangedSignal();
2850 ImfManager imfManager = ImfManager::Get();
2853 imfManager.SetCursorPosition ( mCursorPosition );
2854 imfManager.NotifyCursorPosition();
2859 void TextInput::DrawCursor(const std::size_t nthChar)
2861 // Get height of cursor and set its size
2862 Size size( CURSOR_THICKNESS, 0.0f );
2863 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2865 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2869 // Measure Font so know how big text will be if no initial text to measure.
2870 size.height = mLineHeight;
2873 mCursor.SetSize(size);
2875 // If the character is italic then the cursor also tilts.
2876 mCursor.SetRotation( mInputStyle.GetItalics() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2878 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2880 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2882 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2883 bool altPositionValid; // Alternate cursor validity flag.
2884 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2885 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2887 SetAltCursorEnabled( altPositionValid );
2889 if(!altPositionValid)
2891 mCursor.SetPosition( position + UI_OFFSET );
2895 size.height *= 0.5f;
2896 mCursor.SetSize(size);
2897 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2899 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2900 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2901 size.height = rowSize.height * 0.5f;
2902 mCursorRTL.SetSize(size);
2903 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2906 if( IsScrollEnabled() )
2908 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2909 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2914 void TextInput::SetAltCursorEnabled( bool enabled )
2916 mCursorRTLEnabled = enabled;
2917 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2920 void TextInput::SetCursorVisibility( bool visible )
2922 mCursorVisibility = visible;
2923 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2924 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2927 void TextInput::CreateGrabHandle( Dali::Image image )
2933 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2937 mGrabHandleImage = image;
2940 mGrabHandle = ImageActor::New(mGrabHandleImage);
2941 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2942 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2944 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2946 ShowGrabHandleAndSetVisibility( false );
2948 CreateGrabArea( mGrabHandle );
2950 mActiveLayer.Add(mGrabHandle);
2954 void TextInput::CreateGrabArea( Actor& parent )
2956 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2957 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2958 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2959 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2960 mTapDetector.Attach( mGrabArea );
2961 mPanGestureDetector.Attach( mGrabArea );
2963 parent.Add(mGrabArea);
2966 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2968 Vector3 actualHandlePosition;
2972 mActualGrabHandlePosition.x += displacement.x;
2973 mActualGrabHandlePosition.y += displacement.y;
2975 // Grab handle should jump to the nearest character and take cursor with it
2976 std::size_t newCursorPosition = 0;
2977 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2979 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2981 bool handleVisible = true;
2983 if( IsScrollEnabled() )
2985 const Vector3 controlSize = GetControlSize();
2986 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
2987 // Scrolls the text if the handle is not in a visible position
2988 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2995 mCurrentHandlePosition = actualHandlePosition;
2996 mScrollDisplacement = Vector2::ZERO;
3000 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3002 mScrollDisplacement.x = -SCROLL_SPEED;
3004 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3006 mScrollDisplacement.x = SCROLL_SPEED;
3008 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3010 mScrollDisplacement.y = -SCROLL_SPEED;
3012 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3014 mScrollDisplacement.y = SCROLL_SPEED;
3020 if( handleVisible && // Only redraw cursor and do updates if position changed
3021 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3023 mCursorPosition = newCursorPosition;
3025 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3027 const TextStyle oldInputStyle( mInputStyle );
3029 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3031 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3033 if( oldInputStyle != mInputStyle )
3035 // Updates the line height accordingly with the input style.
3038 EmitStyleChangedSignal();
3043 return actualHandlePosition;
3046 void TextInput::ShowGrabHandle( bool visible )
3048 if ( IsGrabHandleEnabled() )
3052 mGrabHandle.SetVisible( mGrabHandleVisibility );
3054 StartMonitoringStageForTouch();
3058 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3060 mGrabHandleVisibility = visible;
3061 ShowGrabHandle( visible );
3064 // Callbacks connected to be Property notifications for Boundary checking.
3066 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3068 mIsSelectionHandleOneFlipped = true;
3069 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3070 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3073 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3075 mIsSelectionHandleOneFlipped = false;
3076 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3077 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3080 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3082 mIsSelectionHandleTwoFlipped = true;
3083 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3084 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3087 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3089 mIsSelectionHandleTwoFlipped = false;
3090 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3091 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3094 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3095 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3097 mSelectionHandleOne.SetOpacity(0.0f);
3100 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3102 mSelectionHandleOne.SetOpacity(1.0f);
3105 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3107 mSelectionHandleTwo.SetOpacity(0.0f);
3110 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3112 mSelectionHandleTwo.SetOpacity(1.0f);
3115 // End of Callbacks connected to be Property notifications for Boundary checking.
3117 void TextInput::SetUpHandlePropertyNotifications()
3119 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3121 Vector3 handlesize = GetSelectionHandleSize();
3123 // Exceeding horizontal boundary
3124 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3125 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3127 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3128 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3130 // Within horizontal boundary
3131 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3132 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3134 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3135 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3137 // Exceeding vertical boundary
3138 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3139 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3140 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3141 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3143 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3144 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3145 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3146 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3148 // Within vertical boundary
3149 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3150 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3151 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3152 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3154 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3155 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3156 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3157 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3160 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3162 mSelectionHandleOnePosition = start;
3163 mSelectionHandleTwoPosition = end;
3165 if ( !mSelectionHandleOne )
3167 // create normal and pressed images
3168 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3169 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3171 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3172 mSelectionHandleOne.SetName("SelectionHandleOne");
3173 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3174 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3175 mIsSelectionHandleOneFlipped = false;
3176 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3178 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3179 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3181 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3182 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3184 mTapDetector.Attach( mHandleOneGrabArea );
3185 mPanGestureDetector.Attach( mHandleOneGrabArea );
3187 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3189 mSelectionHandleOne.Add( mHandleOneGrabArea );
3190 mActiveLayer.Add( mSelectionHandleOne );
3193 if ( !mSelectionHandleTwo )
3195 // create normal and pressed images
3196 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3197 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3199 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3200 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3201 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3202 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3203 mIsSelectionHandleTwoFlipped = false;
3204 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3206 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3207 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3208 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3209 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3211 mTapDetector.Attach( mHandleTwoGrabArea );
3212 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3214 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3216 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3218 mActiveLayer.Add( mSelectionHandleTwo );
3221 SetUpHandlePropertyNotifications();
3223 // update table as text may have changed.
3224 GetTextLayoutInfo();
3226 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3227 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3229 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3230 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3232 // Calculates and set the visibility if the scroll mode is enabled.
3233 bool isSelectionHandleOneVisible = true;
3234 bool isSelectionHandleTwoVisible = true;
3235 if( IsScrollEnabled() )
3237 const Vector3& controlSize( GetControlSize() );
3238 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3239 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3240 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3241 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3244 CreateHighlight(); // function will only create highlight if not already created.
3247 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3249 Vector3 actualHandlePosition;
3251 if ( mSelectionHandleOne && mSelectionHandleTwo )
3253 const Vector3& controlSize = GetControlSize();
3255 Size cursorSize( CURSOR_THICKNESS, 0.f );
3257 // Get a reference of the wanted selection handle (handle one or two).
3258 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3260 // Get a reference for the current position of the handle and a copy of its pair
3261 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3262 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3264 // Get a handle of the selection handle actor
3265 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3267 // Selection handles should jump to the nearest character
3268 std::size_t newHandlePosition = 0;
3269 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3271 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3273 bool handleVisible = true;
3275 if( IsScrollEnabled() )
3277 mCurrentSelectionId = handleId;
3279 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3280 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3281 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3288 mCurrentSelectionHandlePosition = actualHandlePosition;
3289 mScrollDisplacement = Vector2::ZERO;
3293 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3295 mScrollDisplacement.x = -SCROLL_SPEED;
3297 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3299 mScrollDisplacement.x = SCROLL_SPEED;
3301 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3303 mScrollDisplacement.y = -SCROLL_SPEED;
3305 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3307 mScrollDisplacement.y = SCROLL_SPEED;
3313 if ( handleVisible && // Ensure the handle is visible.
3314 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3315 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3317 currentSelectionHandlePosition = newHandlePosition;
3319 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3320 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3324 if ( handleId == HandleOne )
3326 const TextStyle oldInputStyle( mInputStyle );
3328 // Set Active Style to that of first character in selection
3329 if( mSelectionHandleOnePosition < mStyledText.size() )
3331 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3334 if( oldInputStyle != mInputStyle )
3336 // Updates the line height accordingly with the input style.
3339 EmitStyleChangedSignal();
3345 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3348 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3351 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3352 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3354 if ( selectionHandleActor )
3356 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3357 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3358 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3360 if( IsScrollEnabled() )
3362 const Size cursorSize( CURSOR_THICKNESS,
3363 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3364 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3366 GetControlSize() ) );
3371 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3373 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3374 // For now the visual position of end of logical string will be end of visual string.
3375 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3377 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3380 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3382 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3383 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3384 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3385 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3387 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3389 // Deselect text prior to startSelectionIt
3390 for(;it!=startSelectionIt;++it)
3392 selectedVisualText[*it] = false;
3395 // Select text from startSelectionIt -> endSelectionIt
3396 for(;it!=endSelectionIt;++it)
3398 selectedVisualText[*it] = true;
3401 // Deselect text after endSelection
3404 selectedVisualText[*it] = false;
3407 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3410 // Calculate the dimensions of the quads they will make the highlight mesh
3411 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3413 // At the moment there is no public API to modify the block alignment option.
3414 const bool blockAlignEnabled = true;
3416 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3418 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3420 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3421 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3423 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3424 std::vector<bool> selectedVisualText;
3425 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3426 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3427 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3429 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3430 float rowLeft = 0.0f;
3431 float rowRight = 0.0f;
3432 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3433 float maxRowLeft = std::numeric_limits<float>::max();
3434 float maxRowRight = 0.0f;
3436 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3438 // Scan through entire text.
3441 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3443 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3444 bool charSelected( false );
3445 if( selectedIt != selectedEndIt )
3447 charSelected = *selectedIt++;
3450 if(selectionState == SelectionNone)
3454 selectionState = SelectionStarted;
3455 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3456 rowRight = rowLeft + charInfo.mSize.width;
3459 else if(selectionState == SelectionStarted)
3461 // break selection on:
3462 // 1. new line causing selection break. (\n or wordwrap)
3463 // 2. character not selected.
3464 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3467 // finished selection.
3468 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3469 // that it resides on. That way this enumeration is not necessary.
3471 if(lastIt->mIsNewLineChar)
3473 // 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.
3474 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3476 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3477 maxRowLeft = std::min(maxRowLeft, min.x);
3478 maxRowRight = std::max(maxRowRight, max.x);
3479 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3480 float rowTop = rowBottom - rowSize.height;
3482 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3483 if(charSelected && blockAlignEnabled)
3485 rowRight = std::numeric_limits<float>::max();
3487 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3489 selectionState = SelectionNone;
3491 // Still selected? start a new selection
3494 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3495 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3496 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3497 selectionState = SelectionStarted;
3502 // build up highlight(s) with this selection data.
3503 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3504 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3511 // If reached end, and still on selection, then close selection.
3514 if(selectionState == SelectionStarted)
3516 // finished selection.
3518 if(lastIt->mIsNewLineChar)
3520 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3522 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3523 maxRowLeft = std::min(maxRowLeft, min.x);
3524 maxRowRight = std::max(maxRowRight, max.x);
3525 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3526 float rowTop = rowBottom - rowSize.height;
3527 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3531 // Get the top left and bottom right corners.
3532 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3533 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3534 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3536 // Clamp quads so they appear to clip to borders of the whole text.
3537 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3539 // For block-align align Further Clamp quads to max left and right extents
3540 if(blockAlignEnabled)
3542 // BlockAlign: Will adjust highlight to block:
3544 // H[ello] (top row right = max of all rows right)
3545 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3546 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3547 // [text] (bottom row left = min of all rows left)
3548 // (common in SMS messaging selection)
3550 // As opposed to the default which is tight text highlighting.
3555 // (common in regular text editors/web browser selection)
3557 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3560 // Finally clamp quads again so they don't exceed the boundry of the control.
3561 const Vector3& controlSize = GetControlSize();
3562 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3565 return mNewHighlightInfo;
3568 void TextInput::UpdateHighlight()
3570 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3572 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3574 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3575 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3576 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3577 // [BOTTOM] [ MIDDLE ]
3580 // Each quad is created as 2 triangles.
3581 // Middle is just 1 quad regardless of its size.
3595 if ( mHighlightMeshActor )
3597 // vertex and triangle buffers should always be present if MeshActor is alive.
3598 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3599 MeshData::VertexContainer vertices;
3600 Dali::MeshData::FaceIndices faceIndices;
3602 if( !newHighlightInfo.mQuadList.empty() )
3604 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3605 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3607 // vertex position defaults to (0 0 0)
3608 MeshData::Vertex vertex;
3609 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3612 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3614 // Add each quad geometry (a sub-selection) to the mesh data.
3624 QuadCoordinates& quad = *iter;
3626 vertex.x = quad.min.x;
3627 vertex.y = quad.min.y;
3628 vertices.push_back( vertex );
3631 vertex.x = quad.max.x;
3632 vertex.y = quad.min.y;
3633 vertices.push_back( vertex );
3635 // bottom-left (v+2)
3636 vertex.x = quad.min.x;
3637 vertex.y = quad.max.y;
3638 vertices.push_back( vertex );
3640 // bottom-right (v+3)
3641 vertex.x = quad.max.x;
3642 vertex.y = quad.max.y;
3643 vertices.push_back( vertex );
3645 // triangle A (3, 1, 0)
3646 faceIndices.push_back( v + 3 );
3647 faceIndices.push_back( v + 1 );
3648 faceIndices.push_back( v );
3650 // triangle B (0, 2, 3)
3651 faceIndices.push_back( v );
3652 faceIndices.push_back( v + 2 );
3653 faceIndices.push_back( v + 3 );
3655 mMeshData.SetFaceIndices( faceIndices );
3658 BoneContainer bones(0); // passed empty as bones not required
3659 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3660 mHighlightMesh.UpdateMeshData(mMeshData);
3665 void TextInput::ClearPopup()
3667 mPopUpPanel.Clear();
3670 void TextInput::AddPopupOption(const std::string& name, const std::string& caption, const Image icon, bool finalOption)
3672 mPopUpPanel.AddOption(name, caption, icon, finalOption);
3675 void TextInput::SetPopupPosition(const Vector3& position)
3677 mPopUpPanel.Self().SetPosition( position );
3680 void TextInput::HidePopup(bool animate, bool signalFinished )
3682 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3684 mPopUpPanel.Hide( animate );
3686 if( animate && signalFinished )
3688 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3693 void TextInput::ShowPopup(bool animate)
3697 if(mHighlightMeshActor && mState == StateEdit)
3701 // When text is selected, show popup above top handle (and text), or below bottom handle.
3702 // topHandle: referring to the top most point of the handle or the top line of selection.
3703 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3705 topHandle = mSelectionHandleOneActualPosition;
3706 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3710 topHandle = mSelectionHandleTwoActualPosition;
3711 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3713 topHandle.y += TOP_HANDLE_TOP_OFFSET - rowSize.height;
3714 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3716 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3717 Vector3 bottomHandle;
3718 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3719 bottomHandle.y += GetSelectionHandleSize().y + BOTTOM_HANDLE_BOTTOM_OFFSET;
3720 mPopUpPanel.SetAlternativeOffset(Vector2(0.0f, bottomHandle.y - topHandle.y));
3724 // When no text is selected, show popup at world position of grab handle or cursor
3725 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3726 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3727 position.y -= rowSize.height;
3728 // if can't be positioned above, then position below row.
3729 Vector2 alternativePopUpPosition( 0.0f, position.y ); // default if no grab handle
3732 alternativePopUpPosition.y = rowSize.height + ( mGrabHandle.GetCurrentSize().height * DEFAULT_GRAB_HANDLE_RELATIVE_SIZE.y ) ;
3733 // If grab handle enabled then position pop-up below the grab handle.
3735 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3738 // reposition popup above the desired cursor posiiton.
3739 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3740 textViewSize.z = 0.0f;
3741 // World position = world position of ParentOrigin of cursor (i.e. top-left corner of TextView) + cursor position;
3742 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - (textViewSize * 0.5f) + position;
3744 SetPopupPosition( worldPosition );
3747 mPopUpPanel.Show(animate);
3748 StartMonitoringStageForTouch();
3750 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3753 void TextInput::ShowPopupCutCopyPaste()
3756 // Check the selected text is whole text or not.
3757 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3759 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3760 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3763 if ( !mStyledText.empty() )
3765 Image cutIcon = Image::New( DEFAULT_ICON_CUT );
3766 Image copyIcon = Image::New( DEFAULT_ICON_COPY );
3767 AddPopupOption( OPTION_CUT, GET_LOCALE_TEXT("IDS_COM_BODY_CUT"), cutIcon );
3768 AddPopupOption( OPTION_COPY, GET_LOCALE_TEXT("IDS_COM_BODY_COPY"), copyIcon, true );
3771 if(mClipboard.NumberOfItems())
3773 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3774 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3775 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon );
3776 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3779 mPopUpPanel.Hide(false);
3783 void TextInput::SetUpPopUpSelection()
3787 // If no text exists then don't offer to select
3788 if ( !mStyledText.empty() )
3790 Image selectIcon = Image::New( DEFAULT_ICON_SELECT );
3791 Image selectAllIcon = Image::New( DEFAULT_ICON_SELECT_ALL );
3792 AddPopupOption( OPTION_SELECT_WORD, GET_LOCALE_TEXT("IDS_COM_SK_SELECT"), selectIcon );
3793 AddPopupOption( OPTION_SELECT_ALL, GET_LOCALE_TEXT("IDS_COM_BODY_SELECT_ALL"), selectAllIcon );
3795 // if clipboard has valid contents then offer paste option
3796 if( mClipboard.NumberOfItems() )
3798 Image pasteIcon = Image::New( DEFAULT_ICON_PASTE );
3799 Image clipboardIcon = Image::New( DEFAULT_ICON_CLIPBOARD );
3800 AddPopupOption( OPTION_PASTE, GET_LOCALE_TEXT("IDS_COM_BODY_PASTE"), pasteIcon, true );
3801 AddPopupOption( OPTION_CLIPBOARD, GET_LOCALE_TEXT("IDS_COM_BODY_CLIPBOARD"), clipboardIcon, true );
3804 mPopUpPanel.Hide(false);
3807 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3812 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3813 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3814 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3815 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3817 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3819 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3821 float closestYdifference = std::numeric_limits<float>::max();
3822 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3823 std::size_t numberOfMatchedCharacters = 0;
3825 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3826 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3828 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3830 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3831 float baselinePosition = info.mPosition.y - info.mDescender;
3833 if( info.mIsVisible )
3835 // store difference between source y point and the y position of the current character
3836 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3838 if( currentYdifference < closestYdifference )
3840 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3841 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3842 closestYdifference = currentYdifference;
3843 matchedCharacters.clear();
3844 numberOfMatchedCharacters = 0; // reset count
3847 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3848 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3850 // ignore new line character.
3851 if( !info.mIsNewLineChar )
3853 matchedCharacters.push_back( info );
3854 numberOfMatchedCharacters++;
3858 } // End of loop checking each character's y position in the character layout table
3860 // Check if last character is a newline, if it is
3861 // then need pretend there is an imaginary line afterwards,
3862 // and check if user is touching below previous line.
3863 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3865 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3867 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3871 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3872 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3874 bool matched( false );
3876 // 2 Iterate through matching list of y positions and find closest matching X position.
3877 for( ; it != endIt; ++it )
3879 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3881 if( info.mIsVisible )
3883 // stop when on left side of character's center.
3884 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3885 if( sourceScrollOffset.x < characterMidPointPosition )
3887 if(info.mIsRightToLeftCharacter)
3889 rightToLeftChar = true;
3891 glyphIntersection = info.mPosition.x;
3896 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3902 rightToLeftChar = lastRightToLeftChar;
3905 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3906 closestIndex = lineOffset + matchCharacterIndex;
3908 mClosestCursorPositionEOL = false; // reset
3909 if ( it == endIt && !matched )
3911 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3914 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3915 if( rightToLeftChar && lastRightToLeftChar )
3917 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3922 // closestIndex is the visual index, need to convert it to the logical index
3923 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3925 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3927 // Checks for situations where user is touching between LTR and RTL
3928 // characters. To identify if the user means the end of a LTR string
3929 // or the beginning of an RTL string, and vice versa.
3930 if( closestIndex > 0 )
3932 if( rightToLeftChar && !lastRightToLeftChar )
3937 // A: In this touch range, the user is indicating that they wish to place
3938 // the cursor at the end of the LTR text.
3939 // B: In this touch range, the user is indicating that they wish to place
3940 // the cursor at the end of the RTL text.
3942 // Result of touching A area:
3943 // [.....LTR]|[RTL......]+
3945 // |: primary cursor (for typing LTR chars)
3946 // +: secondary cursor (for typing RTL chars)
3948 // Result of touching B area:
3949 // [.....LTR]+[RTL......]|
3951 // |: primary cursor (for typing RTL chars)
3952 // +: secondary cursor (for typing LTR chars)
3954 if( sourceScrollOffset.x < glyphIntersection )
3959 else if( !rightToLeftChar && lastRightToLeftChar )
3961 if( sourceScrollOffset.x < glyphIntersection )
3968 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3969 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3970 // one further ahead
3971 if( rightToLeftChar && !lastRightToLeftChar )
3976 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3978 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3980 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3989 float TextInput::GetLineJustificationPosition() const
3991 const Vector3& size = mDisplayedTextView.GetCurrentSize();
3992 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
3993 float alignmentOffset = 0.f;
3995 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
3996 if( alignment & Toolkit::Alignment::HorizontalLeft )
3998 alignmentOffset = 0.f;
4000 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4002 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4004 else if( alignment & Toolkit::Alignment::HorizontalRight )
4006 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4009 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4010 float justificationOffset = 0.f;
4012 switch( justification )
4014 case Toolkit::TextView::Left:
4016 justificationOffset = 0.f;
4019 case Toolkit::TextView::Center:
4021 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4024 case Toolkit::TextView::Right:
4026 justificationOffset = mTextLayoutInfo.mTextSize.width;
4029 case Toolkit::TextView::Justified:
4031 justificationOffset = 0.f;
4036 DALI_ASSERT_ALWAYS( false );
4040 return alignmentOffset + justificationOffset;
4043 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4045 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4046 A newline character is not inserted in this case */
4048 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4050 Vector3 cursorPosition;
4052 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4054 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4056 // end character so use
4057 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4058 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4062 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4065 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4067 // If previous character on a different line then use current characters position
4068 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4070 if ( mClosestCursorPositionEOL )
4072 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4076 cursorPosition = Vector3(currentCharInfo.mPosition);
4081 // Previous character is on same line so use position of previous character plus it's width.
4082 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4085 return cursorPosition;
4088 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4090 bool direction(false);
4091 Vector3 alternatePosition;
4092 bool alternatePositionValid(false);
4094 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4097 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4099 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4101 alternatePositionValid = false;
4102 directionRTL = false;
4104 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4106 std::size_t visualCharacterPosition;
4108 // When cursor is not at beginning, consider possibility of
4109 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4110 if(characterPosition > 0)
4112 // Cursor position should be the end of the last character.
4113 // If the last character is LTR, then the end is on the right side of the glyph.
4114 // If the last character is RTL, then the end is on the left side of the glyph.
4115 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4117 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4119 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4122 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4123 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4125 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4126 const Vector3& size = GetControlSize();
4128 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4130 --visualCharacterPosition;
4132 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4135 if(!info.mIsNewLineChar)
4137 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4141 // When cursor points to first character on new line, position cursor at the start of this glyph.
4142 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4144 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4145 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4146 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4148 cursorPosition.x = infoNext.mPosition.x + start;
4149 cursorPosition.y = infoNext.mPosition.y;
4153 // If cursor points to the end of text, then can only position
4154 // cursor where the new line starts based on the line-justification position.
4155 cursorPosition.x = GetLineJustificationPosition();
4157 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4159 // If this is after the last character, then we can assume that the new cursor
4160 // should be exactly one row below the current row.
4162 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4163 cursorPosition.y = info.mPosition.y + rowRect.height;
4167 // If this is not after last character, then we can use this row's height.
4168 // should be exactly one row below the current row.
4170 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4171 cursorPosition.y = info.mPosition.y + rowRect.height;
4176 directionRTL = info.mIsRightToLeftCharacter;
4178 // 1. When the cursor is neither at the beginning or the end,
4179 // we can show multiple cursors under situations when the cursor is
4180 // between RTL and LTR text...
4181 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4183 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4185 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4186 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4188 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4190 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4191 // Text: [...LTR...]|[...RTL...]
4193 // Alternate cursor pos: ^
4194 // In which case we need to display an alternate cursor for the RTL text.
4196 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4197 alternatePosition.y = infoAlt.mPosition.y;
4198 alternatePositionValid = true;
4200 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4202 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4203 // Text: |[...RTL...] [...LTR....]
4205 // Alternate cursor pos: ^
4206 // In which case we need to display an alternate cursor for the RTL text.
4208 alternatePosition.x = infoAlt.mPosition.x;
4209 alternatePosition.y = infoAlt.mPosition.y;
4210 alternatePositionValid = true;
4215 // 2. When the cursor is at the end of the text,
4216 // and we have multi-directional text,
4217 // we can also consider showing mulitple cursors.
4218 // The rule here is:
4219 // If first and last characters on row are different
4220 // Directions, then two cursors need to be displayed.
4222 // Get first logical glyph on row
4223 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4225 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4226 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4228 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4230 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4231 // Text: [...LTR...]|[...RTL...]
4233 // Alternate cursor pos: ^
4234 // In which case we need to display an alternate cursor for the RTL text, this cursor
4235 // should be at the end of the given line.
4237 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4238 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4239 alternatePosition.y = infoAlt.mPosition.y;
4240 alternatePositionValid = true;
4242 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4244 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4245 // Text: |[...RTL...] [...LTR....]
4247 // Alternate cursor pos: ^
4248 // In which case we need to display an alternate cursor for the RTL text.
4250 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4251 alternatePosition.x = infoAlt.mPosition.x;
4252 alternatePosition.y = infoAlt.mPosition.y;
4253 alternatePositionValid = true;
4256 } // characterPosition > 0
4257 else if(characterPosition == 0)
4259 // When the cursor position is at the beginning, it should be at the start of the current character.
4260 // If the current character is LTR, then the start is on the right side of the glyph.
4261 // If the current character is RTL, then the start is on the left side of the glyph.
4262 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4264 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4266 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4269 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4270 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4272 cursorPosition.x = info.mPosition.x + start;
4273 cursorPosition.y = info.mPosition.y;
4274 directionRTL = info.mIsRightToLeftCharacter;
4279 // If the character table is void, place the cursor accordingly the text alignment.
4280 const Vector3& size = GetControlSize();
4282 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4283 float alignmentOffset = 0.f;
4285 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4286 if( alignment & Toolkit::Alignment::HorizontalLeft )
4288 alignmentOffset = 0.f;
4290 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4292 alignmentOffset = 0.5f * ( size.width );
4294 else if( alignment & Toolkit::Alignment::HorizontalRight )
4296 alignmentOffset = size.width;
4299 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4300 cursorPosition.x = alignmentOffset;
4302 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4303 if( alignment & Toolkit::Alignment::VerticalTop )
4305 cursorPosition.y = mLineHeight;
4307 else if( alignment & Toolkit::Alignment::VerticalCenter )
4309 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4311 else if( alignment & Toolkit::Alignment::VerticalBottom )
4313 cursorPosition.y = size.height;
4317 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4318 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4319 if( alternatePositionValid )
4321 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4322 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4325 return cursorPosition;
4328 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4330 // scan string from current position to beginning of current line to note direction of line
4331 while(logicalPosition)
4334 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4335 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4342 return logicalPosition;
4345 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4349 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4352 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4354 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4355 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4357 min = Vector2::ZERO;
4358 max = Vector2(0.0f, mLineHeight);
4362 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4363 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4364 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4366 // If cursor is pointing to end of line, then start from last character.
4367 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4369 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4371 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4372 if( !it->mIsVisible )
4374 characterPosition = FindVisibleCharacter( Left, characterPosition );
4375 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4378 // Scan characters left and right of cursor, stopping when end of line/string reached or
4379 // y position greater than threshold of reference line.
4381 // 1. scan left until we reach the beginning or a different line.
4382 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4383 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4384 // min-x position is the left-most char's left (x)
4385 // max-x position is the right-most char's right (x)
4386 // min-y position is the minimum of all character's top (y)
4387 // max-y position is the maximum of all character's bottom (y+height)
4388 min.y = validCharIt->mPosition.y;
4389 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4394 min.y = std::min(min.y, validCharIt->mPosition.y);
4395 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4404 if( (it->mPosition.y < referenceLine) ||
4405 (it->mIsNewLineChar) ||
4412 // info refers to the first character on this line.
4413 min.x = validCharIt->mPosition.x;
4415 // 2. scan right until we reach end or a different line
4416 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4417 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4421 if( (it->mPosition.y > referenceLine) ||
4422 (it->mIsNewLineChar) ||
4429 min.y = std::min(min.y, validCharIt->mPosition.y);
4430 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4435 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4437 if ( validCharIt != end )
4439 // info refers to the last character on this line.
4440 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4443 return Size( max.x - min.x, max.y - min.y );
4446 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4448 Actor popUpPanel = mPopUpPanel.GetRootActor();
4450 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4456 Dali::Actor parent( touchedActor.GetParent() );
4460 return WasTouchedCheck( parent );
4467 void TextInput::StartMonitoringStageForTouch()
4469 Stage stage = Stage::GetCurrent();
4470 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4473 void TextInput::EndMonitoringStageForTouch()
4475 Stage stage = Stage::GetCurrent();
4476 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4479 void TextInput::OnStageTouched(const TouchEvent& event)
4481 if( event.GetPointCount() > 0 )
4483 if ( TouchPoint::Down == event.GetPoint(0).state )
4485 const Actor touchedActor(event.GetPoint(0).hitActor);
4487 bool popUpShown( false );
4489 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4494 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4496 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4498 EndMonitoringStageForTouch();
4499 HidePopup( true, false );
4502 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4504 EndMonitoringStageForTouch();
4505 ShowGrabHandleAndSetVisibility( false );
4511 void TextInput::SelectText(std::size_t start, std::size_t end)
4513 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4514 IsGrabHandleEnabled()?"true":"false",
4515 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4516 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4517 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4519 StartMonitoringStageForTouch();
4521 if ( mEditModeActive ) // Only allow text selection when in edit mode
4523 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4524 mSelectingText = true;
4526 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4528 ImfManager imfManager = ImfManager::Get();
4531 imfManager.SetCursorPosition ( mCursorPosition );
4532 imfManager.SetSurroundingText( GetText() );
4533 imfManager.NotifyCursorPosition();
4535 // 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.
4537 // Hide grab handle when selecting.
4538 ShowGrabHandleAndSetVisibility( false );
4540 if( start != end ) // something to select
4542 SetCursorVisibility( false );
4543 StopCursorBlinkTimer();
4545 CreateSelectionHandles(start, end);
4548 const TextStyle oldInputStyle( mInputStyle );
4549 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4551 if( oldInputStyle != mInputStyle )
4553 // Updates the line height accordingly with the input style.
4556 EmitStyleChangedSignal();
4562 mSelectingText = false;
4566 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4568 MarkupProcessor::StyledTextArray currentSelectedText;
4570 if ( IsTextSelected() )
4572 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4573 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4575 for(; it != end; ++it)
4577 MarkupProcessor::StyledText& styledText( *it );
4578 currentSelectedText.push_back( styledText );
4581 return currentSelectedText;
4584 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4586 const std::size_t beginIndex = std::min( begin, end );
4587 const std::size_t endIndex = std::max( begin, end );
4590 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4592 // Create a styled text array used to replace the text into the text-view.
4593 MarkupProcessor::StyledTextArray text;
4594 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4596 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4597 GetTextLayoutInfo();
4599 if( IsScrollEnabled() )
4601 // Need to set the scroll position as the text's size may have changed.
4602 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4605 ShowGrabHandleAndSetVisibility( false );
4611 // Set Handle positioning as the new style may have repositioned the characters.
4612 SetSelectionHandlePosition(HandleOne);
4613 SetSelectionHandlePosition(HandleTwo);
4616 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4618 // Just hide the grab handle when keyboard is hidden.
4619 if (!keyboardShown )
4621 ShowGrabHandleAndSetVisibility( false );
4623 // If the keyboard is not now being shown, then hide the popup panel
4624 mPopUpPanel.Hide( true );
4628 // Removes highlight and resumes edit mode state
4629 void TextInput::RemoveHighlight()
4631 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4633 if ( mHighlightMeshActor )
4635 if ( mSelectionHandleOne )
4637 mActiveLayer.Remove( mSelectionHandleOne );
4638 mSelectionHandleOne.Reset();
4639 mSelectionHandleOneOffset.x = 0.0f;
4641 if ( mSelectionHandleTwo )
4643 mActiveLayer.Remove( mSelectionHandleTwo );
4644 mSelectionHandleTwo.Reset();
4645 mSelectionHandleTwoOffset.x = 0.0f;
4648 mNewHighlightInfo.mQuadList.clear();
4650 Self().Remove( mHighlightMeshActor );
4652 SetCursorVisibility( true );
4653 StartCursorBlinkTimer();
4655 mHighlightMeshActor.Reset();
4656 // NOTE: We cannot dereference mHighlightMesh, due
4657 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4662 mSelectionHandleOnePosition = 0;
4663 mSelectionHandleTwoPosition = 0;
4666 void TextInput::CreateHighlight()
4668 if ( !mHighlightMeshActor )
4670 mMeshData = MeshData( );
4671 mMeshData.SetHasNormals( true );
4673 mCustomMaterial = Material::New("CustomMaterial");
4674 mCustomMaterial.SetDiffuseColor( LIGHTBLUE );
4676 mMeshData.SetMaterial( mCustomMaterial );
4678 mHighlightMesh = Mesh::New( mMeshData );
4680 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4681 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4682 mHighlightMeshActor.SetInheritShaderEffect( false );
4683 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4684 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4685 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4686 mHighlightMeshActor.SetAffectedByLighting(false);
4688 Self().Add(mHighlightMeshActor);
4693 bool TextInput::CopySelectedTextToClipboard()
4695 mCurrentCopySelecton.clear();
4697 mCurrentCopySelecton = GetSelectedText();
4699 std::string stringToStore;
4701 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4702 * a marked up string.
4704 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4705 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4706 bool success = mClipboard.SetItem( stringToStore );
4710 void TextInput::PasteText( const Text& text )
4712 // Update Flag, indicates whether to update the text-input contents or not.
4713 // Any key stroke that results in a visual change of the text-input should
4714 // set this flag to true.
4715 bool update = false;
4716 if( mHighlightMeshActor )
4718 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4719 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4721 ImfManager imfManager = ImfManager::Get();
4724 imfManager.SetCursorPosition( mCursorPosition );
4725 imfManager.NotifyCursorPosition();
4727 DeleteHighlightedText( true );
4731 bool textExceedsMaximunNumberOfCharacters = false;
4732 bool textExceedsBoundary = false;
4734 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4736 mCursorPosition += insertedStringLength;
4737 ImfManager imfManager = ImfManager::Get();
4740 imfManager.SetCursorPosition ( mCursorPosition );
4741 imfManager.NotifyCursorPosition();
4744 update = update || ( insertedStringLength > 0 );
4750 if( insertedStringLength < text.GetLength() )
4752 EmitMaxInputCharactersReachedSignal();
4755 if( textExceedsBoundary )
4757 EmitInputTextExceedsBoundariesSignal();
4761 void TextInput::SetTextDirection()
4763 // Put the cursor to the right if we are empty and an RTL language is being used.
4764 if ( mStyledText.empty() )
4766 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4768 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4769 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4771 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4772 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4774 int alignment( mDisplayedTextView.GetTextAlignment() &
4775 ( Toolkit::Alignment::VerticalTop |
4776 Toolkit::Alignment::VerticalCenter |
4777 Toolkit::Alignment::VerticalBottom |
4778 Toolkit::Alignment::HorizontalCenter ) );
4779 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4781 // If our alignment is in the center, then do not change.
4782 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4784 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4787 // If our justification is in the center, then do not change.
4788 if ( justification != Toolkit::TextView::Center )
4790 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4793 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4794 mDisplayedTextView.SetLineJustification( justification );
4798 void TextInput::UpdateLineHeight()
4800 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4801 mLineHeight = font.GetLineHeight();
4803 // 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.
4805 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4807 if( !mExceedEnabled || shrink )
4809 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4813 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4815 std::size_t position = 0;
4817 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4823 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4825 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4827 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4833 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4834 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4836 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4842 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4847 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4854 void TextInput::SetSortModifier( float depthOffset )
4856 if(mDisplayedTextView)
4858 mDisplayedTextView.SetSortModifier(depthOffset);
4862 void TextInput::SetSnapshotModeEnabled( bool enable )
4864 if(mDisplayedTextView)
4866 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4870 bool TextInput::IsSnapshotModeEnabled() const
4872 bool snapshotEnabled = false;
4874 if(mDisplayedTextView)
4876 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4879 return snapshotEnabled;
4882 void TextInput::SetMarkupProcessingEnabled( bool enable )
4884 mMarkUpEnabled = enable;
4887 bool TextInput::IsMarkupProcessingEnabled() const
4889 return mMarkUpEnabled;
4892 void TextInput::SetScrollEnabled( bool enable )
4894 if( mDisplayedTextView )
4896 mDisplayedTextView.SetScrollEnabled( enable );
4901 // Don't set cursor's and handle's visibility to false if they are outside the
4902 // boundaries of the text-input.
4903 mIsCursorInScrollArea = true;
4904 mIsGrabHandleInScrollArea = true;
4905 if( mSelectionHandleOne && mSelectionHandleTwo )
4907 mSelectionHandleOne.SetVisible( true );
4908 mSelectionHandleTwo.SetVisible( true );
4910 if( mHighlightMeshActor )
4912 mHighlightMeshActor.SetVisible( true );
4918 bool TextInput::IsScrollEnabled() const
4920 bool scrollEnabled = false;
4922 if( mDisplayedTextView )
4924 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4927 return scrollEnabled;
4930 void TextInput::SetScrollPosition( const Vector2& position )
4932 if( mDisplayedTextView )
4934 mDisplayedTextView.SetScrollPosition( position );
4938 Vector2 TextInput::GetScrollPosition() const
4940 Vector2 scrollPosition;
4942 if( mDisplayedTextView )
4944 scrollPosition = mDisplayedTextView.GetScrollPosition();
4947 return scrollPosition;
4950 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4952 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4953 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4954 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4956 // Add style to the new input text.
4957 MarkupProcessor::StyledTextArray textToInsert;
4958 for( std::size_t i = 0; i < insertedStringLength; ++i )
4960 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4961 textToInsert.push_back( newStyledCharacter );
4964 //Insert text to the TextView.
4965 const bool emptyTextView = mStyledText.empty();
4966 if( emptyTextView && mPlaceHolderSet )
4968 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4969 mDisplayedTextView.SetText( textToInsert );
4973 if( 0 == numberOfCharactersToReplace )
4975 mDisplayedTextView.InsertTextAt( position, textToInsert );
4979 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4982 mPlaceHolderSet = false;
4984 if( textToInsert.empty() )
4986 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4987 GetTextLayoutInfo();
4991 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4992 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4995 textExceedsBoundary = false;
4997 if( !mExceedEnabled )
4999 const Vector3& size = GetControlSize();
5001 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5003 // If new text does not fit within TextView
5004 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
5005 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
5006 GetTextLayoutInfo();
5007 textExceedsBoundary = true;
5008 insertedStringLength = 0;
5011 if( textExceedsBoundary )
5013 // Add the part of the text which fits on the text-input.
5015 // Split the text which doesn't fit in two halves.
5016 MarkupProcessor::StyledTextArray firstHalf;
5017 MarkupProcessor::StyledTextArray secondHalf;
5018 SplitText( textToInsert, firstHalf, secondHalf );
5020 // Clear text. This text will be filled with the text inserted.
5021 textToInsert.clear();
5023 // Where to insert the text.
5024 std::size_t positionToInsert = position;
5026 bool end = text.GetLength() <= 1;
5029 // Insert text and check ...
5030 const std::size_t textLength = firstHalf.size();
5031 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5032 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5034 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5036 // Inserted text doesn't fit.
5038 // Remove inserted text
5039 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5040 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5042 // The iteration finishes when only one character doesn't fit.
5043 end = textLength <= 1;
5047 // Prepare next two halves for next iteration.
5048 MarkupProcessor::StyledTextArray copyText = firstHalf;
5049 SplitText( copyText, firstHalf, secondHalf );
5056 // store text to be inserted in mStyledText.
5057 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5059 // Increase the inserted characters counter.
5060 insertedStringLength += textLength;
5062 // Prepare next two halves for next iteration.
5063 MarkupProcessor::StyledTextArray copyText = secondHalf;
5064 SplitText( copyText, firstHalf, secondHalf );
5066 // Update where next text has to be inserted
5067 positionToInsert += textLength;
5073 if( textToInsert.empty() && emptyTextView )
5075 // No character has been added and the text-view was empty.
5076 // Set the placeholder text.
5077 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5078 mPlaceHolderSet = true;
5082 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5083 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5084 mPlaceHolderSet = false;
5087 return insertedStringLength;
5090 void TextInput::GetTextLayoutInfo()
5092 if( mStyledText.empty() )
5094 // The text-input has no text, clear the text-view's layout info.
5095 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5099 if( mDisplayedTextView )
5101 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5105 // There is no text-view.
5106 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5111 void TextInput::EmitStyleChangedSignal()
5113 // emit signal if input style changes.
5114 Toolkit::TextInput handle( GetOwner() );
5115 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5118 void TextInput::EmitTextModified()
5120 // emit signal when text changes.
5121 Toolkit::TextInput handle( GetOwner() );
5122 mTextModifiedSignal.Emit( handle );
5126 void TextInput::EmitMaxInputCharactersReachedSignal()
5128 // emit signal if max characters is reached during text input.
5129 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5131 Toolkit::TextInput handle( GetOwner() );
5132 mMaxInputCharactersReachedSignalV2.Emit( handle );
5135 void TextInput::EmitInputTextExceedsBoundariesSignal()
5137 // Emit a signal when the input text exceeds the boundaries of the text input.
5139 Toolkit::TextInput handle( GetOwner() );
5140 mInputTextExceedBoundariesSignalV2.Emit( handle );
5143 } // namespace Internal
5145 } // namespace Toolkit