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.
19 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
25 #include <dali/public-api/adaptor-framework/virtual-keyboard.h>
26 #include <dali/public-api/animation/constraints.h>
27 #include <dali/public-api/common/stage.h>
28 #include <dali/public-api/events/key-event.h>
29 #include <dali/public-api/events/touch-event.h>
30 #include <dali/public-api/object/type-registry.h>
31 #include <dali/public-api/object/type-registry-helper.h>
32 #include <dali/public-api/object/property-notification.h>
33 #include <dali/public-api/size-negotiation/relayout-container.h>
34 #include <dali/integration-api/debug.h>
35 #include <dali/public-api/images/resource-image.h>
38 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
39 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
40 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
41 #include <dali-toolkit/public-api/controls/default-controls/solid-color-actor.h>
49 #if defined(DEBUG_ENABLED)
50 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
53 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
54 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
55 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
56 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
57 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
58 const Vector4 LIGHTBLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // Used for Selection highlight
60 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
61 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
62 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
63 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
64 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
66 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
67 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
68 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.1f ); ///< 1. Highlight rendered (z-offset).
69 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.2f ); ///< 2. Text rendered (z-offset).
70 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
72 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
73 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
74 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
75 const float TOP_HANDLE_TOP_OFFSET( 34.0f); ///< Offset between top handle and cutCopyPaste pop-up
76 const float BOTTOM_HANDLE_BOTTOM_OFFSET(34.0f); ///< Offset between bottom handle and cutCopyPaste pop-up
77 const float CURSOR_THICKNESS(4.0f);
78 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
79 const Vector4 DEFAULT_CURSOR_COLOR(1.0f, 1.0f, 1.0f, 1.0f);
81 const char* const NEWLINE = "\n";
83 const TextStyle DEFAULT_TEXT_STYLE;
85 const unsigned int SCROLL_TICK_INTERVAL = 50u;
86 const float SCROLL_THRESHOLD = 10.f;
87 const float SCROLL_SPEED = 15.f;
90 * Selection state enumeration (FSM)
94 SelectionNone, ///< Currently not encountered selected section.
95 SelectionStarted, ///< Encountered selected section
96 SelectionFinished ///< Finished selected section
99 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
101 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
105 if( ( *it ).mIsVisible )
107 return --cursorPosition;
116 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
118 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
120 if( ( *it ).mIsVisible )
122 return cursorPosition;
128 return cursorPosition;
132 * Whether the given position plus the cursor size offset is inside the given boundary.
134 * @param[in] position The given position.
135 * @param[in] cursorSize The cursor size.
136 * @param[in] controlSize The given boundary.
138 * @return whether the given position is inside the given boundary.
140 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
142 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
143 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
144 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
145 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
149 * Splits a text in two halves.
151 * If the text's number of characters is odd, firstHalf has one more character.
153 * @param[in] text The text to be split.
154 * @param[out] firstHalf The first half of the text.
155 * @param[out] secondHalf The second half of the text.
157 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
158 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
159 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
164 const std::size_t textLength = text.size();
165 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
167 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
168 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
171 } // end of namespace
182 namespace // Unnamed namespace
187 return Toolkit::TextInput::New();
190 // Setup properties, signals and actions using the type-registry.
191 DALI_TYPE_REGISTRATION_BEGIN( Toolkit::TextInput, Toolkit::Control, Create )
193 DALI_PROPERTY_REGISTRATION( TextInput, "highlight-color", VECTOR4, HIGHLIGHT_COLOR )
194 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-bg-color", VECTOR4, CUT_AND_PASTE_COLOR )
195 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-pressed-color", VECTOR4, CUT_AND_PASTE_PRESSED_COLOR )
196 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-border-color", VECTOR4, CUT_AND_PASTE_BORDER_COLOR )
197 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-icon-color", VECTOR4, CUT_AND_PASTE_ICON_COLOR )
198 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-icon-pressed-color", VECTOR4, CUT_AND_PASTE_ICON_PRESSED_COLOR )
199 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-text-color", VECTOR4, CUT_AND_PASTE_TEXT_COLOR )
200 DALI_PROPERTY_REGISTRATION( TextInput, "cut-and-paste-text-pressed-color", VECTOR4, CUT_AND_PASTE_TEXT_PRESSED_COLOR )
201 DALI_PROPERTY_REGISTRATION( TextInput, "cut-button-position-priority", UNSIGNED_INTEGER, CUT_BUTTON_POSITION_PRIORITY )
202 DALI_PROPERTY_REGISTRATION( TextInput, "copy-button-position-priority", UNSIGNED_INTEGER, COPY_BUTTON_POSITION_PRIORITY )
203 DALI_PROPERTY_REGISTRATION( TextInput, "paste-button-position-priority", UNSIGNED_INTEGER, PASTE_BUTTON_POSITION_PRIORITY )
204 DALI_PROPERTY_REGISTRATION( TextInput, "select-button-position-priority", UNSIGNED_INTEGER, SELECT_BUTTON_POSITION_PRIORITY )
205 DALI_PROPERTY_REGISTRATION( TextInput, "select-all-button-position-priority", UNSIGNED_INTEGER, SELECT_ALL_BUTTON_POSITION_PRIORITY )
206 DALI_PROPERTY_REGISTRATION( TextInput, "clipboard-button-position-priority", UNSIGNED_INTEGER, CLIPBOARD_BUTTON_POSITION_PRIORITY )
207 DALI_PROPERTY_REGISTRATION( TextInput, "popup-offset-from-text", VECTOR4, POP_UP_OFFSET_FROM_TEXT )
208 DALI_PROPERTY_REGISTRATION( TextInput, "cursor-color", VECTOR4, CURSOR_COLOR )
210 DALI_SIGNAL_REGISTRATION( TextInput, "start-input", SIGNAL_START_INPUT )
211 DALI_SIGNAL_REGISTRATION( TextInput, "end-input", SIGNAL_END_INPUT )
212 DALI_SIGNAL_REGISTRATION( TextInput, "style-changed", SIGNAL_STYLE_CHANGED )
213 DALI_SIGNAL_REGISTRATION( TextInput, "max-input-characters-reached", SIGNAL_MAX_INPUT_CHARACTERS_REACHED )
214 DALI_SIGNAL_REGISTRATION( TextInput, "toolbar-displayed", SIGNAL_TOOLBAR_DISPLAYED )
215 DALI_SIGNAL_REGISTRATION( TextInput, "text-exceed-boundaries", SIGNAL_TEXT_EXCEED_BOUNDARIES )
217 DALI_TYPE_REGISTRATION_END()
221 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
223 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
225 QuadCoordinates quad(x1, y1, x2, y2);
226 mQuadList.push_back( quad );
229 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
231 for(std::size_t i = 0;i < mQuadList.size(); i++)
233 QuadCoordinates& quad = mQuadList[i];
235 quad.min.Clamp(min, max);
236 quad.max.Clamp(min, max);
240 // [TextInput] ////////////////////////////////////////////////////////////////
242 Dali::Toolkit::TextInput TextInput::New()
244 // Create the implementation
245 TextInputPtr textInput(new TextInput());
246 // Pass ownership to CustomActor via derived handle
247 Dali::Toolkit::TextInput handle(*textInput);
248 handle.SetName( "TextInput");
250 textInput->Initialize();
254 TextInput::TextInput()
255 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
260 mDisplayedTextView(),
261 mStyledPlaceHolderText(),
262 mMaxStringLength( DEFAULT_MAX_SIZE ),
263 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
264 mCursorPosition( 0 ),
265 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
266 mIsSelectionHandleOneFlipped( false ),
267 mIsSelectionHandleTwoFlipped( false ),
268 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
269 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
270 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
271 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
272 mSelectionHandleOnePosition( 0 ),
273 mSelectionHandleTwoPosition( 0 ),
275 mPreEditStartPosition( 0 ),
276 mPreEditLength ( 0 ),
277 mNumberOfSurroundingCharactersDeleted( 0 ),
278 mTouchStartTime( 0 ),
280 mCurrentCopySelecton(),
283 mScrollDisplacement(),
284 mCurrentHandlePosition(),
285 mCurrentSelectionId(),
286 mCurrentSelectionHandlePosition(),
287 mRequestedSelection( 0, 0 ),
288 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
289 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
291 mMaterialColor( LIGHTBLUE ),
292 mPopupOffsetFromText ( Vector4( 0.0f, TOP_HANDLE_TOP_OFFSET, 0.0f, BOTTOM_HANDLE_BOTTOM_OFFSET ) ),
293 mOverrideAutomaticAlignment( false ),
294 mCursorRTLEnabled( false ),
295 mClosestCursorPositionEOL ( false ),
296 mCursorBlinkStatus( true ),
297 mCursorVisibility( false ),
298 mGrabHandleVisibility( false ),
299 mIsCursorInScrollArea( true ),
300 mIsGrabHandleInScrollArea( true ),
301 mEditModeActive( false ),
302 mEditOnTouch( true ),
303 mTextSelection( true ),
304 mExceedEnabled( true ),
305 mGrabHandleEnabled( true ),
306 mIsSelectionHandleFlipEnabled( true ),
307 mPreEditFlag( false ),
308 mIgnoreCommitFlag( false ),
309 mIgnoreFirstCommitFlag( false ),
310 mSelectingText( false ),
311 mPreserveCursorPosition( false ),
312 mSelectTextOnCommit( false ),
313 mUnderlinedPriorToPreEdit ( false ),
314 mCommitByKeyInput( false ),
315 mPlaceHolderSet( false ),
316 mMarkUpEnabled( false )
318 // Updates the line height accordingly with the input style.
322 TextInput::~TextInput()
324 StopCursorBlinkTimer();
329 std::string TextInput::GetText() const
333 // Return text-view's text only if the text-input's text is not empty
334 // in order to not to return the placeholder text.
335 if( !mStyledText.empty() )
337 text = mDisplayedTextView.GetText();
343 std::string TextInput::GetMarkupText() const
345 std::string markupString;
346 MarkupProcessor::GetMarkupString( mStyledText, markupString );
351 void TextInput::ShowPlaceholderText( const MarkupProcessor::StyledTextArray& stylePlaceHolderText )
353 mDisplayedTextView.SetText( stylePlaceHolderText );
354 mPlaceHolderSet = true;
355 mDisplayedTextView.SetScrollPosition( Vector2( 0.0f,0.0f ) );
358 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
360 // Get the placeholder styled text array from the markup string.
361 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
362 if( mStyledText.empty() )
364 ShowPlaceholderText( mStyledPlaceHolderText );
368 std::string TextInput::GetPlaceholderText()
370 // Traverses the styled placeholder array getting only the text.
371 // Note that for some languages a 'character' could be represented by more than one 'char'
373 std::string placeholderText;
374 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
376 placeholderText.append( (*it).mText.GetText() );
379 return placeholderText ;
382 void TextInput::SetInitialText(const std::string& initialText)
384 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
386 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
388 mPreEditFlag = false;
389 mIgnoreCommitFlag = true;
392 SetText( initialText );
393 PreEditReset( false ); // Reset keyboard as text changed
396 void TextInput::SetText(const std::string& initialText)
398 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
400 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
402 if( mStyledText.empty() )
404 ShowPlaceholderText( mStyledPlaceHolderText );
408 mDisplayedTextView.SetText( mStyledText );
409 mPlaceHolderSet = false;
414 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
416 ImfManager imfManager = ImfManager::Get();
419 imfManager.SetCursorPosition( mCursorPosition );
420 imfManager.SetSurroundingText( initialText );
421 imfManager.NotifyCursorPosition();
424 if( IsScrollEnabled() )
426 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
429 ShowGrabHandleAndSetVisibility( false );
438 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
440 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
442 mDisplayedTextView.SetText( styleText );
443 mPlaceHolderSet = false;
445 // If text alignment hasn't been manually set by application developer, then we
446 // automatically determine the alignment based on the content of the text i.e. what
447 // language the text begins with.
448 // TODO: This should determine different alignments for each line (broken by '\n') of text.
449 if(!mOverrideAutomaticAlignment)
451 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
452 bool leftToRight(true);
454 if( !styleText.empty() )
456 bool breakOut(false);
458 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
460 const Text& text = textIter->mText;
462 for( std::size_t i = 0; i < text.GetLength(); ++i )
464 Character character( text[i] );
465 if( character.GetCharacterDirection() != Character::Neutral )
467 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
475 // Based on this direction, either left or right align text if not manually set by application developer.
476 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
477 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
478 Toolkit::Alignment::VerticalTop ) );
479 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
485 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
487 mMaxStringLength = maxChars;
490 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
492 DALI_ASSERT_DEBUG( maxLines > 0 )
496 mNumberOflinesLimit = maxLines;
500 std::size_t TextInput::GetNumberOfLinesLimit() const
502 return mNumberOflinesLimit;
505 std::size_t TextInput::GetNumberOfCharacters() const
507 return mStyledText.size();
511 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
513 mMaterialColor = color;
516 const Vector4& TextInput::GetMaterialDiffuseColor() const
518 return mMaterialColor;
523 Toolkit::TextInput::InputSignalType& TextInput::InputStartedSignal()
525 return mInputStartedSignal;
528 Toolkit::TextInput::InputSignalType& TextInput::InputFinishedSignal()
530 return mInputFinishedSignal;
533 Toolkit::TextInput::InputSignalType& TextInput::CutAndPasteToolBarDisplayedSignal()
535 return mCutAndPasteToolBarDisplayed;
538 Toolkit::TextInput::StyleChangedSignalType& TextInput::StyleChangedSignal()
540 return mStyleChangedSignal;
543 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
545 return mTextModifiedSignal;
548 Toolkit::TextInput::MaxInputCharactersReachedSignalType& TextInput::MaxInputCharactersReachedSignal()
550 return mMaxInputCharactersReachedSignal;
553 Toolkit::TextInput::InputTextExceedBoundariesSignalType& TextInput::InputTextExceedBoundariesSignal()
555 return mInputTextExceedBoundariesSignal;
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( 0 == strcmp( signalName.c_str(), SIGNAL_START_INPUT ) )
567 textInput.InputStartedSignal().Connect( tracker, functor );
569 else if( 0 == strcmp( signalName.c_str(), SIGNAL_END_INPUT ) )
571 textInput.InputFinishedSignal().Connect( tracker, functor );
573 else if( 0 == strcmp( signalName.c_str(), SIGNAL_STYLE_CHANGED ) )
575 textInput.StyleChangedSignal().Connect( tracker, functor );
577 else if( 0 == strcmp( signalName.c_str(), SIGNAL_MAX_INPUT_CHARACTERS_REACHED ) )
579 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
581 else if( 0 == strcmp( signalName.c_str(), SIGNAL_TOOLBAR_DISPLAYED ) )
583 textInput.CutAndPasteToolBarDisplayedSignal().Connect( tracker, functor );
585 else if( 0 == strcmp( signalName.c_str(), SIGNAL_TEXT_EXCEED_BOUNDARIES ) )
587 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
591 // signalName does not match any signal
598 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
602 // update line height before calculate the actual position.
607 if( setCursorOnTouchPoint )
609 // Sets the cursor position for the given touch point.
610 ReturnClosestIndex( touchPoint, mCursorPosition );
612 // Creates the grab handle.
613 if( IsGrabHandleEnabled() )
615 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
619 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
620 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
621 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
622 ShowGrabHandleAndSetVisibility( true );
624 // Scrolls the text-view if needed.
625 if( IsScrollEnabled() )
627 ScrollTextViewToMakeCursorVisible( cursorPosition );
633 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
645 bool TextInput::IsEditable() const
647 return mEditModeActive;
650 void TextInput::SetEditOnTouch( bool editOnTouch )
652 mEditOnTouch = editOnTouch;
655 bool TextInput::IsEditOnTouch() const
660 void TextInput::SetTextSelectable( bool textSelectable )
662 mTextSelection = textSelectable;
665 bool TextInput::IsTextSelectable() const
667 return mTextSelection;
670 bool TextInput::IsTextSelected() const
675 void TextInput::DeSelectText()
682 void TextInput::SetGrabHandleImage(Dali::Image image )
686 CreateGrabHandle(image);
690 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
692 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
696 mCursor.SetImage( image );
697 mCursor.SetNinePatchBorder( border );
701 Vector3 TextInput::GetSelectionHandleSize()
703 return DEFAULT_SELECTION_HANDLE_SIZE;
706 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
708 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
712 mCursorRTL.SetImage( image);
713 mCursorRTL.SetNinePatchBorder( border );
717 void TextInput::EnableGrabHandle(bool toggle)
719 // enables grab handle with will in turn de-activate magnifier
720 mGrabHandleEnabled = toggle;
723 bool TextInput::IsGrabHandleEnabled()
725 // if false then magnifier will be shown instead.
726 return mGrabHandleEnabled;
729 void TextInput::EnableSelectionHandleFlip( bool toggle )
731 // Deprecated function. To be removed.
732 mIsSelectionHandleFlipEnabled = toggle;
735 bool TextInput::IsSelectionHandleFlipEnabled()
737 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
741 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
743 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
744 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
745 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
747 mSelectionHandleFlipMargin = margin;
750 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
752 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
753 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
755 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
756 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
758 const Vector4 boundary( originX,
760 originX + boundingRectangle.width,
761 originY + boundingRectangle.height );
763 mBoundingRectangleWorldCoordinates = boundary;
766 const Rect<float> TextInput::GetBoundingRectangle() const
768 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
770 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
771 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
773 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
778 const Vector4& TextInput::GetSelectionHandleFlipMargin()
780 return mSelectionHandleFlipMargin;
783 void TextInput::SetTextColor( const Vector4& color )
785 mDisplayedTextView.SetColor( color );
788 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
790 if( style != mInputStyle )
793 bool emitSignal = false;
795 // mask: modify style according to mask, if different emit signal.
796 const TextStyle oldInputStyle( mInputStyle );
798 // Copy the new style.
799 mInputStyle.Copy( style, mask );
801 // if style has changed, emit signal.
802 if( oldInputStyle != mInputStyle )
807 // Updates the line height accordingly with the input style.
810 // Changing font point size will require the cursor to be re-sized
815 EmitStyleChangedSignal();
820 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
822 if ( IsTextSelected() )
824 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
825 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
827 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
829 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
832 // Keeps the old style to be compared with the new one.
833 const TextStyle oldInputStyle( mInputStyle );
835 // Copy only those parameters from the style which are set in the mask.
836 mInputStyle.Copy( style, mask );
838 if( mInputStyle != oldInputStyle )
840 // Updates the line height accordingly with the input style.
843 EmitStyleChangedSignal();
848 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
850 if( !mStyledText.empty() )
852 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
856 TextStyle TextInput::GetStyleAtCursor() const
860 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
862 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
863 style = mStyledText.at( mCursorPosition-1 ).mStyle;
869 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
871 Dali::Font defaultFont = Dali::Font::New();
872 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
879 TextStyle TextInput::GetStyleAt( std::size_t position ) const
881 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
883 if( position >= mStyledText.size() )
885 position = mStyledText.size() - 1;
888 return mStyledText.at( position ).mStyle;
891 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
893 mDisplayedTextView.SetTextAlignment( align );
894 mOverrideAutomaticAlignment = true;
897 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
899 mDisplayedTextView.SetLineJustification( justification );
900 mOverrideAutomaticAlignment = true;
903 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
905 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
908 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
910 return mDisplayedTextView.GetFadeBoundary();
913 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
915 return mDisplayedTextView.GetTextAlignment();
918 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
920 mDisplayedTextView.SetMultilinePolicy( policy );
923 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
925 return mDisplayedTextView.GetMultilinePolicy();
928 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
930 mDisplayedTextView.SetWidthExceedPolicy( policy );
933 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
935 return mDisplayedTextView.GetWidthExceedPolicy();
938 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
940 mDisplayedTextView.SetHeightExceedPolicy( policy );
943 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
945 return mDisplayedTextView.GetHeightExceedPolicy();
948 void TextInput::SetExceedEnabled( bool enable )
950 mExceedEnabled = enable;
953 bool TextInput::GetExceedEnabled() const
955 return mExceedEnabled;
958 void TextInput::SetBackground(Dali::Image image )
960 // TODO Should add this function and add public api to match.
963 bool TextInput::OnTouchEvent(const TouchEvent& event)
968 bool TextInput::OnKeyEvent(const KeyEvent& event)
970 switch( event.state )
974 return OnKeyDownEvent(event);
980 return OnKeyUpEvent(event);
992 void TextInput::OnKeyInputFocusGained()
994 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
996 mEditModeActive = true;
998 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1000 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1002 // Updates the line height accordingly with the input style.
1005 // Connect the signals to use in text input.
1006 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1007 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1009 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1012 GetTextLayoutInfo();
1015 SetCursorVisibility( true );
1016 StartCursorBlinkTimer();
1018 Toolkit::TextInput handle( GetOwner() );
1019 mInputStartedSignal.Emit( handle );
1021 ImfManager imfManager = ImfManager::Get();
1025 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1027 // Notify that the text editing start.
1028 imfManager.Activate();
1030 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1031 imfManager.SetRestoreAfterFocusLost( true );
1033 imfManager.SetCursorPosition( mCursorPosition );
1034 imfManager.NotifyCursorPosition();
1037 mClipboard = Clipboard::Get(); // Store handle to clipboard
1039 // Now in edit mode we can accept string to paste from clipboard
1040 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1043 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1047 void TextInput::OnKeyInputFocusLost()
1049 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1053 // If key input focus is lost, it removes the
1054 // underline from the last pre-edit text.
1055 RemovePreEditStyle();
1056 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1057 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1061 ImfManager imfManager = ImfManager::Get();
1064 // The text editing is finished. Therefore the imf manager don't have restore activation.
1065 imfManager.SetRestoreAfterFocusLost( false );
1067 // Notify that the text editing finish.
1068 imfManager.Deactivate();
1070 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1072 // Disconnect signal used the text input.
1073 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1075 Toolkit::TextInput handle( GetOwner() );
1076 mInputFinishedSignal.Emit( handle );
1077 mEditModeActive = false;
1078 mPreEditFlag = false;
1080 SetCursorVisibility( false );
1081 StopCursorBlinkTimer();
1083 ShowGrabHandleAndSetVisibility( false );
1086 // No longer in edit mode so do not want to receive string from clipboard
1087 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1090 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1093 Clipboard clipboard = Clipboard::Get();
1096 clipboard.HideClipboard();
1100 void TextInput::OnControlStageConnection()
1102 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1104 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1106 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1110 void TextInput::CreateActiveLayer()
1112 Actor self = Self();
1113 mActiveLayer = Layer::New();
1114 mActiveLayer.SetName ( "ActiveLayerActor" );
1116 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1117 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1118 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1120 self.Add( mActiveLayer );
1121 mActiveLayer.RaiseToTop();
1124 void TextInput::OnInitialize()
1126 CreateTextViewActor();
1130 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1131 // different positions depending on language)
1132 mCursor = CreateCursor(DEFAULT_CURSOR_COLOR);
1133 mCursorRTL = CreateCursor(DEFAULT_CURSOR_COLOR);
1135 Actor self = Self();
1136 self.Add( mCursor );
1137 self.Add( mCursorRTL );
1139 mCursorVisibility = false;
1141 CreateActiveLayer(); // todo move this so layer only created when needed.
1143 // Assign names to image actors
1144 mCursor.SetName("mainCursor");
1145 mCursorRTL.SetName("rtlCursor");
1148 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1150 mDisplayedTextView.SetSize( targetSize );
1151 GetTextLayoutInfo();
1152 mActiveLayer.SetSize(targetSize);
1155 void TextInput::OnRelayout( const Vector2& size, RelayoutContainer& container )
1157 container.Add( mDisplayedTextView, size );
1158 container.Add( mPopupPanel.GetRootActor(), size );
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, const 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, const 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 std::size_t start = 0;
1351 std::size_t end = 0;
1352 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1354 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1356 ImfManager imfManager = ImfManager::Get();
1359 imfManager.SetCursorPosition ( mCursorPosition );
1360 imfManager.NotifyCursorPosition();
1363 if ( !mStyledText.at(end-1).mText[0].IsWhiteSpace() )
1365 SelectText( start, end );
1366 ShowPopupCutCopyPaste();
1370 RemoveHighlight( false ); // Remove highlight but do not auto hide popup
1371 HidePopup( false ); // Hide popup with setting to do auto show.
1372 SetUpPopupSelection( false ); // Set to false so if nearest word is whitespace it will not show cut button.
1376 else if ( mClipboard && mClipboard.NumberOfItems() )
1378 ShowPopupCutCopyPaste();
1381 // If no text and clipboard empty then do nothing
1384 // TODO: Change the function name to be more general.
1385 void TextInput::OnTextTap(Dali::Actor actor, const Dali::TapGesture& tap)
1387 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1388 , (mEditOnTouch)?"true":"false"
1389 , (mEditModeActive)?"true":"false");
1391 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1396 if( mGrabArea == actor )
1398 if( mPopupPanel.GetState() == TextInputPopup::StateHidden || mPopupPanel.GetState() == TextInputPopup::StateHiding )
1400 SetUpPopupSelection();
1410 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1412 // Initially don't create the grab handle.
1413 bool createGrabHandle = false;
1415 if ( !mEditModeActive )
1417 // update line height before calculate the actual position.
1420 // Only start edit mode if TextInput configured to edit on touch
1423 // Set the initial cursor position in the tap point.
1424 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1430 // Show the keyboard if it was hidden.
1431 if (!VirtualKeyboard::IsVisible())
1433 VirtualKeyboard::Show();
1436 // Reset keyboard as tap event has occurred.
1437 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1438 PreEditReset( true );
1440 GetTextLayoutInfo();
1442 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1444 // 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.
1446 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1448 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1450 // Notify keyboard so it can 're-capture' word for predictive text.
1451 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1452 ImfManager imfManager = ImfManager::Get();
1455 imfManager.SetCursorPosition ( mCursorPosition );
1456 imfManager.NotifyCursorPosition();
1458 const TextStyle oldInputStyle( mInputStyle );
1460 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1464 // Create the grab handle.
1465 // Grab handle is created later.
1466 createGrabHandle = true;
1468 if( oldInputStyle != mInputStyle )
1470 // Updates the line height accordingly with the input style.
1473 EmitStyleChangedSignal();
1478 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1479 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1480 // otherwise the Grab handle will be shown when selecting.
1481 if ( createGrabHandle && IsGrabHandleEnabled() )
1483 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
1484 bool altPositionValid; // Alternate cursor validity flag.
1485 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1486 Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
1488 if( altPositionValid )
1490 // Check which of the positions is the closest.
1491 if( fabsf( altPosition.x - tap.localPoint.x ) < fabsf( cursorPosition.x - tap.localPoint.x ) )
1493 cursorPosition = altPosition;
1499 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1500 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1501 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1502 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1507 void TextInput::OnLongPress(Dali::Actor actor, const Dali::LongPressGesture& longPress)
1509 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1511 // Ignore longpress if in selection mode already
1513 if(longPress.state == Dali::Gesture::Started)
1515 // Start edit mode on long press
1516 if ( !mEditModeActive )
1521 // If text exists then select nearest word.
1522 if ( !mStyledText.empty())
1526 ShowGrabHandleAndSetVisibility( false );
1531 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1532 // converts the pre-edit word being displayed to a committed word.
1533 if ( !mUnderlinedPriorToPreEdit )
1536 style.SetUnderline( false );
1537 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1539 mPreEditFlag = false;
1540 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1541 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1542 PreEditReset( false );
1544 mCursorPosition = 0;
1546 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1547 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1549 std::size_t start = 0;
1550 std::size_t end = 0;
1551 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1553 mCursorPosition = end; // Ensure cursor is positioned at end of selected word
1555 ImfManager imfManager = ImfManager::Get();
1558 imfManager.SetCursorPosition ( mCursorPosition );
1559 imfManager.NotifyCursorPosition();
1562 SelectText( start, end );
1565 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1566 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1568 ShowPopupCutCopyPaste();
1573 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1575 const Text clipboardText( notifier.GetContent() );
1576 PasteText( clipboardText );
1578 SetCursorVisibility( true );
1579 StartCursorBlinkTimer();
1581 ShowGrabHandleAndSetVisibility( false );
1587 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1589 mPopupPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1591 const std::string& name = button.GetName();
1593 if(name == TextInputPopup::OPTION_SELECT_WORD)
1595 std::size_t start = 0;
1596 std::size_t end = 0;
1597 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1599 SelectText( start, end );
1601 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1603 SetCursorVisibility(false);
1604 StopCursorBlinkTimer();
1606 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1607 std::size_t start = 0;
1609 SelectText( start, end );
1611 else if(name == TextInputPopup::OPTION_CUT)
1613 bool ret = CopySelectedTextToClipboard();
1617 DeleteHighlightedText( true );
1621 SetCursorVisibility( true );
1622 StartCursorBlinkTimer();
1626 else if(name == TextInputPopup::OPTION_COPY)
1628 CopySelectedTextToClipboard();
1632 SetCursorVisibility( true );
1633 StartCursorBlinkTimer();
1637 else if(name == TextInputPopup::OPTION_PASTE)
1639 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1641 PasteText(retrievedString);
1643 SetCursorVisibility( true );
1644 StartCursorBlinkTimer();
1646 ShowGrabHandleAndSetVisibility( false );
1650 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1652 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1653 // Hence pass the false parameter for signalFinished.
1654 HidePopup( true, false );
1655 mClipboard.ShowClipboard();
1661 bool TextInput::OnCursorBlinkTimerTick()
1664 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1665 if ( mCursorRTLEnabled )
1667 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1669 mCursorBlinkStatus = !mCursorBlinkStatus;
1674 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1676 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1678 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1681 //FIXME this routine needs to be re-written as it contains too many branches.
1682 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1684 std::string keyName = event.keyPressedName;
1685 std::string keyString = event.keyPressed;
1687 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1689 // Do not consume "Tab" and "Escape" keys.
1690 if(keyName == "Tab" || keyName == "Escape")
1692 // Escape key to end the edit mode
1698 HidePopup(); // If Pop-up shown then hides it as editing text.
1700 // Update Flag, indicates whether to update the text-input contents or not.
1701 // Any key stroke that results in a visual change of the text-input should
1702 // set this flag to true.
1705 // Whether to scroll text to cursor position.
1706 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1707 bool scroll = false;
1709 if (keyName == "Return")
1711 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1713 bool preEditFlagPreviouslySet( mPreEditFlag );
1715 // replaces highlighted text with new line
1716 DeleteHighlightedText( false );
1718 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1720 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1721 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1724 mCommitByKeyInput = true;
1727 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1728 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1730 mPreEditFlag = true;
1731 mIgnoreCommitFlag = false;
1741 else if ( keyName == "space" )
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")
1758 if ( mCursorPosition > 0 )
1760 DeleteCharacter( mCursorPosition );
1766 else if (keyName == "Right")
1771 else if (keyName == "Left")
1773 AdvanceCursor(true);
1776 else // event is a character
1778 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1779 if ( !keyString.empty() )
1781 // replaces highlighted text with new character
1782 DeleteHighlightedText( false );
1784 // Received key String
1785 mCursorPosition += InsertAt( Text( keyString ), mCursorPosition, 0 );
1791 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1792 // as this is a costly operation.
1798 if(update || scroll)
1800 if( IsScrollEnabled() )
1802 // Calculates the new cursor position (in actor coordinates)
1803 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1805 ScrollTextViewToMakeCursorVisible( cursorPosition );
1812 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1814 std::string keyName = event.keyPressedName;
1815 std::string keyString = event.keyPressed;
1817 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1819 // The selected text become deselected when the key code is DALI_KEY_BACK.
1820 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1829 void TextInput::ChooseRtlSelectionHandlePosition( const Vector3& cursorPositionOne,
1830 const Vector3& cursorPositionTwo,
1831 bool altPositionValidOne,
1832 bool altPositionValidTwo,
1833 const Vector3& altPositionOne,
1834 const Vector3& altPositionTwo )
1836 // TODO VCC Valid for one line.
1837 // Try to place the selection handles. TODO think in something better. Probably need to know the direction of the paragraph.
1838 if( cursorPositionOne != cursorPositionTwo )
1840 if( cursorPositionOne.x < cursorPositionTwo.x )
1842 mSelectionHandleOneActualPosition = cursorPositionOne;
1843 mSelectionHandleTwoActualPosition = cursorPositionTwo;
1847 mSelectionHandleOneActualPosition = cursorPositionTwo;
1848 mSelectionHandleTwoActualPosition = cursorPositionOne;
1853 mSelectionHandleOneActualPosition = cursorPositionOne;
1854 if( altPositionValidOne )
1856 if( altPositionOne.x < mSelectionHandleOneActualPosition.x )
1858 mSelectionHandleOneActualPosition = altPositionOne;
1861 if( altPositionValidTwo )
1863 if( altPositionTwo.x < mSelectionHandleOneActualPosition.x )
1865 mSelectionHandleOneActualPosition = altPositionTwo;
1869 mSelectionHandleTwoActualPosition = cursorPositionTwo;
1870 if( altPositionValidTwo )
1872 if( altPositionTwo.x > mSelectionHandleTwoActualPosition.x )
1874 mSelectionHandleTwoActualPosition = altPositionTwo;
1877 if( altPositionValidOne )
1879 if( altPositionOne.x > mSelectionHandleTwoActualPosition.x )
1881 mSelectionHandleTwoActualPosition = altPositionOne;
1887 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1889 // Updates the stored scroll position.
1890 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1892 const Vector3& controlSize = GetControlSize();
1893 Size cursorSize( CURSOR_THICKNESS, 0.f );
1895 // Updates the cursor and grab handle position and visibility.
1896 if( mGrabHandle || mCursor )
1898 cursorSize.height = GetRowRectFromCharacterPosition( mCursorPosition ).height;
1900 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
1901 bool altPositionValid; // Alternate cursor validity flag.
1902 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1903 Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
1905 if( altPositionValid )
1907 // Check which of the positions is the closest.
1908 if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( cursorPosition.x - mActualGrabHandlePosition.x ) )
1910 cursorPosition = altPosition;
1914 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1916 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1920 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1921 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1926 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1927 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1931 // Updates the selection handles and highlighted text position and visibility.
1932 if( mSelectionHandleOne && mSelectionHandleTwo )
1934 Vector3 altPositionOne; // Alternate (i.e. opposite direction) cursor position.
1935 bool altPositionValidOne; // Alternate cursor validity flag.
1936 bool directionRTLOne; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1937 Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition, directionRTLOne, altPositionOne, altPositionValidOne );
1939 Vector3 altPositionTwo; // Alternate (i.e. opposite direction) cursor position.
1940 bool altPositionValidTwo; // Alternate cursor validity flag.
1941 bool directionRTLTwo; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
1942 Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition, directionRTLTwo, altPositionTwo, altPositionValidTwo );
1944 // VCC TODO: This method is a hack for one line.
1945 ChooseRtlSelectionHandlePosition( cursorPositionOne,
1947 altPositionValidOne,
1948 altPositionValidTwo,
1952 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1953 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1954 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1955 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1957 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1958 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1959 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1960 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1965 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1967 // Scroll the text to make the cursor visible.
1968 const Size cursorSize( CURSOR_THICKNESS,
1969 GetRowRectFromCharacterPosition( mCursorPosition ).height );
1971 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1973 const Vector3& controlSize = GetControlSize();
1975 // Calculates the new scroll position.
1976 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1977 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1979 scrollOffset.x += cursorPosition.x;
1982 if( cursorPosition.y - cursorSize.height < 0.f || cursorPosition.y > controlSize.height )
1984 scrollOffset.y += cursorPosition.y;
1987 // Sets the new scroll position.
1988 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1989 SetScrollPosition( scrollOffset );
1992 void TextInput::StartScrollTimer()
1996 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1997 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
2000 if( !mScrollTimer.IsRunning() )
2002 mScrollTimer.Start();
2006 void TextInput::StopScrollTimer()
2010 mScrollTimer.Stop();
2014 bool TextInput::OnScrollTimerTick()
2016 // TODO: need to set the new style accordingly the new handle position.
2018 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
2020 // nothing to do if all handles are invisible or doesn't exist.
2026 // Choose between the grab handle or the selection handles.
2027 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
2028 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
2029 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
2031 std::size_t newCursorPosition = 0;
2032 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
2034 // Whether the handle's position is different of the previous one and in the case of the selection handle,
2035 // the new selection handle's position needs to be different of the other one.
2036 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
2037 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
2038 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
2040 if( differentSelectionHandles )
2042 handlePosition = newCursorPosition;
2044 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2046 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
2048 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
2049 scrollPosition += scrollDelta;
2050 SetScrollPosition( scrollPosition );
2052 if( mDisplayedTextView.IsScrollPositionTrimmed() )
2057 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
2060 actualHandlePosition.x += mScrollDisplacement.x;
2061 actualHandlePosition.y += mScrollDisplacement.y;
2066 // Public Internal Methods (public for testing purpose)
2068 void TextInput::SetUpTouchEvents()
2070 if ( !mTapDetector )
2072 mTapDetector = TapGestureDetector::New();
2073 // Attach the actors and connect the signal
2074 mTapDetector.Attach(Self());
2076 // As contains children which may register for tap the default control detector is not used.
2077 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2080 if ( !mDoubleTapDetector )
2082 mDoubleTapDetector = TapGestureDetector::New( 2 );
2083 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2085 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2086 // so that we do not, unnecessarily, have a double tap request all the time
2089 if ( !mPanGestureDetector )
2091 mPanGestureDetector = PanGestureDetector::New();
2092 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2095 if ( !mLongPressDetector )
2097 mLongPressDetector = LongPressGestureDetector::New();
2098 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2099 mLongPressDetector.Attach(Self());
2103 void TextInput::CreateTextViewActor()
2105 mDisplayedTextView = Toolkit::TextView::New();
2106 mDisplayedTextView.SetName( "DisplayedTextView ");
2107 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2108 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2109 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2110 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2111 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2112 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2113 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2114 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2115 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2117 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2119 Self().Add( mDisplayedTextView );
2122 // Start a timer to initiate, used by the cursor to blink.
2123 void TextInput::StartCursorBlinkTimer()
2125 if ( !mCursorBlinkTimer )
2127 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2128 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2131 if ( !mCursorBlinkTimer.IsRunning() )
2133 mCursorBlinkTimer.Start();
2137 // Start a timer to initiate, used by the cursor to blink.
2138 void TextInput::StopCursorBlinkTimer()
2140 if ( mCursorBlinkTimer )
2142 mCursorBlinkTimer.Stop();
2146 void TextInput::StartEditMode()
2148 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2150 if(!mEditModeActive)
2155 if ( mDoubleTapDetector )
2157 mDoubleTapDetector.Attach( Self() );
2161 void TextInput::EndEditMode()
2163 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2165 ClearKeyInputFocus();
2167 if ( mDoubleTapDetector )
2169 mDoubleTapDetector.Detach( Self() );
2173 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2175 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2177 mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2179 style.SetUnderline( true );
2180 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2184 void TextInput::RemovePreEditStyle()
2186 if ( !mUnderlinedPriorToPreEdit )
2189 style.SetUnderline( false );
2190 SetActiveStyle( style, TextStyle::UNDERLINE );
2194 // IMF related methods
2197 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2199 bool update( false );
2200 bool preeditResetRequired ( false );
2202 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2204 HidePopup(); // If Pop-up shown then hides it as editing text.
2207 switch ( imfEvent.eventName )
2209 case ImfManager::PREEDIT:
2211 mIgnoreFirstCommitFlag = false;
2213 // 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
2215 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2217 if( IsScrollEnabled() )
2219 // Calculates the new cursor position (in actor coordinates)
2220 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2221 ScrollTextViewToMakeCursorVisible( cursorPosition );
2228 case ImfManager::COMMIT:
2230 if( mIgnoreFirstCommitFlag )
2232 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2233 mIgnoreFirstCommitFlag = false;
2237 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2239 // 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
2241 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2242 // not needed, one such scenario is when the pre-edit word is too long to fit.
2243 if ( !mIgnoreCommitFlag )
2245 update = CommitReceived( imfEvent.predictiveString );
2249 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2255 if( IsScrollEnabled() )
2257 // Calculates the new cursor position (in actor coordinates)
2258 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2260 ScrollTextViewToMakeCursorVisible( cursorPosition );
2265 case ImfManager::DELETESURROUNDING:
2267 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2268 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2270 mPreEditFlag = false;
2272 std::size_t toDelete = 0;
2273 std::size_t numberOfCharacters = 0;
2276 if( static_cast<std::size_t>(std::abs( imfEvent.cursorOffset )) < mCursorPosition )
2278 toDelete = mCursorPosition + imfEvent.cursorOffset;
2280 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2282 numberOfCharacters = mStyledText.size() - toDelete;
2286 numberOfCharacters = imfEvent.numberOfChars;
2289 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2290 DeleteRange( toDelete, numberOfCharacters );
2292 mCursorPosition = toDelete;
2293 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2297 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2300 case ImfManager::GETSURROUNDING:
2302 // 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
2303 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2304 if (! ( false || mSelectingText ) )
2306 std::string text( GetText() );
2307 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2309 imfManager.SetCursorPosition( mCursorPosition );
2310 imfManager.SetSurroundingText( text );
2313 if( 0 != mNumberOfSurroundingCharactersDeleted )
2315 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2316 mNumberOfSurroundingCharactersDeleted = 0;
2318 if( mStyledText.empty() )
2320 ShowPlaceholderText( mStyledPlaceHolderText );
2325 case ImfManager::VOID:
2327 DALI_ASSERT_DEBUG( false );
2331 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2333 return callbackData;
2336 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2338 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2340 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2341 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2343 bool preeditResetRequest ( false );
2345 if( mPreEditFlag ) // Already in pre-edit state.
2347 if( mStyledText.size() >= mMaxStringLength )
2349 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2350 // Cannot fit these characters into field, clear pre-edit.
2351 if ( !mUnderlinedPriorToPreEdit )
2354 style.SetUnderline( false );
2355 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2357 mIgnoreCommitFlag = true;
2358 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2359 mPreEditFlag = false;
2360 EmitMaxInputCharactersReachedSignal();
2364 // delete existing pre-edit string
2365 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2367 // Store new pre-edit string
2368 mPreEditString.SetText( keyString );
2370 if ( keyString.empty() )
2372 mPreEditFlag = false;
2373 mCursorPosition = mPreEditStartPosition;
2375 if( mStyledText.empty() )
2377 ShowPlaceholderText( mStyledPlaceHolderText );
2381 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2384 GetTextLayoutInfo();
2389 // Insert new pre-edit string. InsertAt updates the size and position table.
2390 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2391 // 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.
2392 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2393 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2394 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2397 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2401 else // mPreEditFlag not set
2403 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2405 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2406 // new pre-edit so move into pre-edit state by setting flag
2407 mPreEditFlag = true;
2408 mPreEditString.SetText( keyString ); // store new pre-edit string
2409 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2410 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2411 // 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.
2412 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2413 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2414 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2415 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2421 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2425 return preeditResetRequest;
2428 bool TextInput::CommitReceived(const std::string& keyString )
2430 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2431 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2433 bool update( false );
2435 RemovePreEditStyle();
2437 const std::size_t styledTextSize( mStyledText.size() );
2438 if( styledTextSize >= mMaxStringLength )
2440 // Cannot fit these characters into field, clear pre-edit.
2443 mIgnoreCommitFlag = true;
2444 mPreEditFlag = false;
2446 EmitMaxInputCharactersReachedSignal();
2452 // delete existing pre-edit string
2453 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2454 mPreEditFlag = false;
2456 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2457 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2459 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2461 // No need to update cursor position as Cursor location given by touch.
2462 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2463 mPreserveCursorPosition = false;
2467 // Cursor not set by touch so needs to be re-positioned to input more text
2468 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2470 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2471 if ( mCommitByKeyInput )
2473 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2474 mCommitByKeyInput = false;
2480 if ( mSelectTextOnCommit )
2482 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2487 else // mPreEditFlag not set
2489 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2491 if( mStyledText.empty() && mPlaceHolderSet )
2493 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2494 mDisplayedTextView.SetText( "" );
2495 mNumberOfSurroundingCharactersDeleted = 0;
2496 mPlaceHolderSet = false;
2498 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2500 mNumberOfSurroundingCharactersDeleted = 0;
2505 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2510 mSelectTextOnCommit = false;
2512 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2513 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2518 // End of IMF related methods
2520 std::size_t TextInput::DeletePreEdit()
2522 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2524 DALI_ASSERT_DEBUG( mPreEditFlag );
2526 const std::size_t preEditStringLength = mPreEditString.GetLength();
2527 const std::size_t styledTextSize = mStyledText.size();
2529 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2531 // Prevents erase items outside mStyledText bounds.
2532 if( mPreEditStartPosition > styledTextSize )
2534 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2535 mPreEditStartPosition = styledTextSize;
2538 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2540 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2541 endPosition = styledTextSize;
2544 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2546 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2547 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2549 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2551 return preEditStringLength;
2554 void TextInput::PreEditReset( bool preserveCursorPosition )
2556 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2557 preserveCursorPosition, mCursorPosition);
2559 // 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.
2560 mPreserveCursorPosition = preserveCursorPosition;
2562 // Reset incase we are in a pre-edit state.
2563 ImfManager imfManager = ImfManager::Get();
2566 imfManager.Reset(); // Will trigger a commit message
2570 void TextInput::CursorUpdate()
2574 ImfManager imfManager = ImfManager::Get();
2577 std::string text( GetText() );
2578 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2579 imfManager.SetCursorPosition ( mCursorPosition );
2580 imfManager.NotifyCursorPosition();
2584 /* Delete highlighted characters redisplay*/
2585 void TextInput::DeleteHighlightedText( bool inheritStyle )
2587 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2591 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2593 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2594 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2596 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2599 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2601 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2602 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2604 mStyledText.erase(itStart, itEnd);
2606 // update the selection handles if they are visible.
2608 // 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.
2611 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2613 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2614 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2615 // Mean we do not re-draw the text more than we have too.
2618 /* Delete character at current cursor position and redisplay*/
2619 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2621 // Ensure positionToDelete is not out of bounds.
2622 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2623 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2624 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2626 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2629 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2631 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2633 // Get the styled text of the character to be deleted as it may be needed if
2634 // the "exceed the text-input's boundaries" option is disabled.
2635 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2637 mStyledText.erase(it); // erase the character left of positionToDelete
2639 if( mStyledText.empty() )
2641 ShowPlaceholderText( mStyledPlaceHolderText );
2645 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2647 const Character characterToDelete = styledCharacterToDelete.mText[0];
2649 // It may happen than after removing a white space or a new line character,
2650 // two words merge, this new word could be big enough to not fit in its
2651 // current line, so moved to the next one, and make some part of the text to
2652 // exceed the text-input's boundary.
2653 if( !mExceedEnabled )
2655 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2657 // Get the new text layout after removing one character.
2658 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2660 // Get text-input's size.
2661 const Vector3& size = GetControlSize();
2663 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2664 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2666 MarkupProcessor::StyledTextArray array;
2667 array.push_back( styledCharacterToDelete );
2668 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2670 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2675 GetTextLayoutInfo();
2677 ShowGrabHandleAndSetVisibility( false );
2679 mCursorPosition = positionToDelete -1;
2681 const TextStyle oldInputStyle( mInputStyle );
2683 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2685 if( oldInputStyle != mInputStyle )
2687 // Updates the line height accordingly with the input style.
2690 EmitStyleChangedSignal();
2695 /*Insert new character into the string and (optionally) redisplay text-input*/
2696 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2698 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2700 // Ensure insertionPosition is not out of bounds.
2701 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2703 bool textExceedsMaximunNumberOfCharacters = false;
2704 bool textExceedsBoundary = false;
2705 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2707 ShowGrabHandleAndSetVisibility( false );
2709 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2713 mIgnoreCommitFlag = true;
2714 mPreEditFlag = false;
2715 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2716 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2719 if( textExceedsMaximunNumberOfCharacters )
2721 EmitMaxInputCharactersReachedSignal();
2724 if( textExceedsBoundary )
2726 EmitInputTextExceedsBoundariesSignal();
2727 PreEditReset( false );
2731 return insertedStringLength;
2734 ImageActor TextInput::CreateCursor( const Vector4& color)
2737 cursor = CreateSolidColorActor(color);
2738 cursor.SetName( "Cursor" );
2740 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2741 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_LEFT);
2742 cursor.SetVisible(false);
2747 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2749 // As cursor is not moving due to grab handle, handle should be hidden.
2750 ShowGrabHandleAndSetVisibility( false );
2752 bool cursorPositionChanged = false;
2755 if ( mCursorPosition >= places )
2757 mCursorPosition = mCursorPosition - places;
2758 cursorPositionChanged = true;
2763 if ((mCursorPosition + places) <= mStyledText.size())
2765 mCursorPosition = mCursorPosition + places;
2766 cursorPositionChanged = true;
2770 if( cursorPositionChanged )
2772 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2774 const TextStyle oldInputStyle( mInputStyle );
2775 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2779 if( oldInputStyle != mInputStyle )
2781 // Updates the line height accordingly with the input style.
2784 EmitStyleChangedSignal();
2787 ImfManager imfManager = ImfManager::Get();
2790 imfManager.SetCursorPosition ( mCursorPosition );
2791 imfManager.NotifyCursorPosition();
2796 void TextInput::DrawCursor()
2798 const Size rowRect = GetRowRectFromCharacterPosition( mCursorPosition );
2800 // Get height of cursor and set its size
2801 Size size( CURSOR_THICKNESS, 0.0f );
2802 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
2804 size.height = rowRect.height;
2808 // Measure Font so know how big text will be if no initial text to measure.
2809 size.height = mLineHeight;
2812 mCursor.SetSize(size);
2814 // If the character is italic then the cursor also tilts.
2815 mCursor.SetOrientation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2817 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2819 if( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2821 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2822 bool altPositionValid; // Alternate cursor validity flag.
2823 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2824 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2826 SetAltCursorEnabled( altPositionValid );
2828 if( !altPositionValid )
2830 mCursor.SetPosition( position + UI_OFFSET );
2834 size.height *= 0.5f;
2835 mCursor.SetSize(size);
2836 mCursor.SetPosition( position + UI_OFFSET - Vector3( 0.0f, directionRTL ? 0.0f : size.height, 0.0f ) );
2838 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2839 size.height = rowRect.height * 0.5f;
2840 mCursorRTL.SetSize(size);
2841 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3( 0.0f, directionRTL ? size.height : 0.0f, 0.0f ) );
2844 if( IsScrollEnabled() )
2846 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2847 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2852 void TextInput::SetAltCursorEnabled( bool enabled )
2854 mCursorRTLEnabled = enabled;
2855 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2858 void TextInput::SetCursorVisibility( bool visible )
2860 mCursorVisibility = visible;
2861 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2862 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2865 void TextInput::CreateGrabHandle( Dali::Image image )
2871 mGrabHandleImage = ResourceImage::New(DEFAULT_GRAB_HANDLE);
2875 mGrabHandleImage = image;
2878 mGrabHandle = ImageActor::New(mGrabHandleImage);
2879 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2880 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2882 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2884 ShowGrabHandleAndSetVisibility( false );
2886 CreateGrabArea( mGrabHandle );
2888 mActiveLayer.Add(mGrabHandle);
2892 void TextInput::CreateGrabArea( Actor& parent )
2894 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2895 mGrabArea.SetName( "GrabArea" );
2896 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2897 mGrabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT );
2898 mGrabArea.SetSizeModeFactor( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE );
2899 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2900 mTapDetector.Attach( mGrabArea );
2901 mPanGestureDetector.Attach( mGrabArea );
2902 mLongPressDetector.Attach( mGrabArea );
2904 parent.Add(mGrabArea);
2907 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2909 Vector3 actualHandlePosition;
2913 mActualGrabHandlePosition.x += displacement.x;
2914 mActualGrabHandlePosition.y += displacement.y;
2916 // Grab handle should jump to the nearest character and take cursor with it
2917 std::size_t newCursorPosition = 0;
2918 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
2920 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2921 bool altPositionValid; // Alternate cursor validity flag.
2922 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2923 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition, directionRTL, altPosition, altPositionValid );
2925 if( altPositionValid )
2927 // Check which of the positions is the closest.
2928 if( fabsf( altPosition.x - mActualGrabHandlePosition.x ) < fabsf( actualHandlePosition.x - mActualGrabHandlePosition.x ) )
2930 actualHandlePosition = altPosition;
2934 bool handleVisible = true;
2936 if( IsScrollEnabled() )
2938 const Vector3 controlSize = GetControlSize();
2939 const Size cursorSize = GetRowRectFromCharacterPosition( newCursorPosition );
2940 // Scrolls the text if the handle is not in a visible position
2941 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
2948 mCurrentHandlePosition = actualHandlePosition;
2949 mScrollDisplacement = Vector2::ZERO;
2953 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
2955 mScrollDisplacement.x = -SCROLL_SPEED;
2957 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
2959 mScrollDisplacement.x = SCROLL_SPEED;
2961 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
2963 mScrollDisplacement.y = -SCROLL_SPEED;
2965 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
2967 mScrollDisplacement.y = SCROLL_SPEED;
2973 if( handleVisible && // Only redraw cursor and do updates if position changed
2974 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
2976 mCursorPosition = newCursorPosition;
2978 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
2980 const TextStyle oldInputStyle( mInputStyle );
2982 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
2984 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
2986 if( oldInputStyle != mInputStyle )
2988 // Updates the line height accordingly with the input style.
2991 EmitStyleChangedSignal();
2996 return actualHandlePosition;
2999 void TextInput::ShowGrabHandle( bool visible )
3001 if ( IsGrabHandleEnabled() )
3005 mGrabHandle.SetVisible( mGrabHandleVisibility );
3007 StartMonitoringStageForTouch();
3011 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3013 mGrabHandleVisibility = visible;
3014 ShowGrabHandle( visible );
3017 // Callbacks connected to be Property notifications for Boundary checking.
3019 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3021 mIsSelectionHandleOneFlipped = true;
3022 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3023 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3026 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3028 mIsSelectionHandleOneFlipped = false;
3029 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3030 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3033 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3035 mIsSelectionHandleTwoFlipped = true;
3036 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3037 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3040 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3042 mIsSelectionHandleTwoFlipped = false;
3043 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3044 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3047 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3048 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3050 mSelectionHandleOne.SetOpacity(0.0f);
3053 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3055 mSelectionHandleOne.SetOpacity(1.0f);
3058 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3060 mSelectionHandleTwo.SetOpacity(0.0f);
3063 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3065 mSelectionHandleTwo.SetOpacity(1.0f);
3068 // End of Callbacks connected to be Property notifications for Boundary checking.
3070 void TextInput::SetUpHandlePropertyNotifications()
3072 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3074 Vector3 handlesize = GetSelectionHandleSize();
3076 // Exceeding horizontal boundary
3077 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::Property::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3078 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3080 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::Property::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3081 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3083 // Within horizontal boundary
3084 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::Property::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3085 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3087 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::Property::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3088 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3090 // Exceeding vertical boundary
3091 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
3092 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3093 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3094 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3096 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
3097 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3098 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3099 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3101 // Within vertical boundary
3102 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
3103 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3104 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3105 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3107 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::Property::WORLD_POSITION_Y,
3108 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3109 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3110 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3113 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3115 mSelectionHandleOnePosition = start;
3116 mSelectionHandleTwoPosition = end;
3118 if ( !mSelectionHandleOne )
3120 // create normal and pressed images
3121 mSelectionHandleOneImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE );
3122 mSelectionHandleOneImagePressed = ResourceImage::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3124 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3125 mSelectionHandleOne.SetName("SelectionHandleOne");
3126 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3127 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3128 mIsSelectionHandleOneFlipped = false;
3129 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3131 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3132 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3134 mHandleOneGrabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT );
3135 mHandleOneGrabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
3136 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3138 mTapDetector.Attach( mHandleOneGrabArea );
3139 mPanGestureDetector.Attach( mHandleOneGrabArea );
3141 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3143 mSelectionHandleOne.Add( mHandleOneGrabArea );
3144 mActiveLayer.Add( mSelectionHandleOne );
3147 if ( !mSelectionHandleTwo )
3149 // create normal and pressed images
3150 mSelectionHandleTwoImage = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO );
3151 mSelectionHandleTwoImagePressed = ResourceImage::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3153 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3154 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3155 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3156 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3157 mIsSelectionHandleTwoFlipped = false;
3158 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3160 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3161 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3162 mHandleTwoGrabArea.SetSizeMode( SIZE_RELATIVE_TO_PARENT );
3163 mHandleTwoGrabArea.SetSizeModeFactor( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE );
3164 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3166 mTapDetector.Attach( mHandleTwoGrabArea );
3167 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3169 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3171 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3173 mActiveLayer.Add( mSelectionHandleTwo );
3176 SetUpHandlePropertyNotifications();
3178 // update table as text may have changed.
3179 GetTextLayoutInfo();
3181 Vector3 altPositionOne; // Alternate (i.e. opposite direction) cursor position.
3182 bool altPositionValidOne; // Alternate cursor validity flag.
3183 bool directionRTLOne; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3184 Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition, directionRTLOne, altPositionOne, altPositionValidOne );
3186 Vector3 altPositionTwo; // Alternate (i.e. opposite direction) cursor position.
3187 bool altPositionValidTwo; // Alternate cursor validity flag.
3188 bool directionRTLTwo; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3189 Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition, directionRTLTwo, altPositionTwo, altPositionValidTwo );
3191 // VCC TODO: This method is a hack for one line.
3192 ChooseRtlSelectionHandlePosition( cursorPositionOne,
3194 altPositionValidOne,
3195 altPositionValidTwo,
3199 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3200 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3202 // Calculates and set the visibility if the scroll mode is enabled.
3203 bool isSelectionHandleOneVisible = true;
3204 bool isSelectionHandleTwoVisible = true;
3205 if( IsScrollEnabled() )
3207 const Vector3& controlSize( GetControlSize() );
3208 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3209 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3210 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3211 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3214 CreateHighlight(); // function will only create highlight if not already created.
3217 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3219 Vector3 actualHandlePosition;
3221 if ( mSelectionHandleOne && mSelectionHandleTwo )
3223 const Vector3& controlSize = GetControlSize();
3225 Size cursorSize( CURSOR_THICKNESS, 0.f );
3227 // Get a reference of the wanted selection handle (handle one or two).
3228 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3230 // Get a reference for the current position of the handle and a copy of its pair
3231 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3232 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3234 // Get a handle of the selection handle actor
3235 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3237 // Selection handles should jump to the nearest character
3238 std::size_t newHandlePosition = 0;
3239 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3241 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
3242 bool altPositionValid; // Alternate cursor validity flag.
3243 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
3244 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition, directionRTL, altPosition, altPositionValid );
3245 if( altPositionValid )
3247 // Check which of the positions is the closest.
3248 if( fabsf( altPosition.x - actualSelectionHandlePosition.x ) < fabsf( actualHandlePosition.x - actualSelectionHandlePosition.x ) )
3250 actualHandlePosition = altPosition;
3254 bool handleVisible = true;
3256 if( IsScrollEnabled() )
3258 mCurrentSelectionId = handleId;
3260 cursorSize.height = GetRowRectFromCharacterPosition( newHandlePosition ).height;
3261 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3262 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3269 mCurrentSelectionHandlePosition = actualHandlePosition;
3270 mScrollDisplacement = Vector2::ZERO;
3274 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3276 mScrollDisplacement.x = -SCROLL_SPEED;
3278 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3280 mScrollDisplacement.x = SCROLL_SPEED;
3282 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3284 mScrollDisplacement.y = -SCROLL_SPEED;
3286 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3288 mScrollDisplacement.y = SCROLL_SPEED;
3294 if ( handleVisible && // Ensure the handle is visible.
3295 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3296 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3298 currentSelectionHandlePosition = newHandlePosition;
3300 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3301 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3305 if ( handleId == HandleOne )
3307 const TextStyle oldInputStyle( mInputStyle );
3309 // Set Active Style to that of first character in selection
3310 if( mSelectionHandleOnePosition < mStyledText.size() )
3312 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3315 if( oldInputStyle != mInputStyle )
3317 // Updates the line height accordingly with the input style.
3320 EmitStyleChangedSignal();
3326 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3329 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3331 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3332 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3334 if ( selectionHandleActor )
3336 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3337 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3338 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3340 if( IsScrollEnabled() )
3342 const Size cursorSize( CURSOR_THICKNESS,
3343 GetRowRectFromCharacterPosition( selectionHandlePosition ).height );
3344 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3346 GetControlSize() ) );
3351 void TextInput::GetVisualTextSelection( std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection )
3353 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3355 // VCC Set true/false in logical order. TODO : It needs to be checked.
3357 if( startSelection > endSelection )
3359 std::swap( startSelection, endSelection );
3361 std::size_t index = 0u;
3362 for( std::vector<bool>::iterator it = selectedVisualText.begin(), endIt = selectedVisualText.end(); it != endIt; ++it, ++index )
3364 if( ( index < startSelection ) || ( endSelection <= index ) )
3375 // Calculate the dimensions of the quads they will make the highlight mesh
3376 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3378 // At the moment there is no public API to modify the block alignment option.
3380 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3382 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3384 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3385 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3387 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3388 std::vector<bool> selectedVisualText;
3389 GetVisualTextSelection( selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
3390 std::vector<bool>::iterator selectedIt = selectedVisualText.begin();
3391 std::vector<bool>::iterator selectedEndIt = selectedVisualText.end();
3393 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3394 float rowLeft = 0.0f;
3395 float rowRight = 0.0f;
3396 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3397 float maxRowLeft = std::numeric_limits<float>::max();
3398 float maxRowRight = 0.0f;
3400 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3402 // Scan through entire text.
3405 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3407 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3408 bool charSelected = false;
3409 if( selectedIt != selectedEndIt )
3411 charSelected = *selectedIt++;
3414 if( selectionState == SelectionNone )
3418 selectionState = SelectionStarted;
3419 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3420 rowRight = rowLeft + charInfo.mSize.width;
3423 else if( selectionState == SelectionStarted )
3425 // break selection on:
3426 // 1. new line causing selection break. (\n or wordwrap)
3427 // 2. character not selected.
3428 if( !charSelected ||
3429 ( charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ) )
3431 // finished selection.
3432 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3433 // that it resides on. That way this enumeration is not necessary.
3435 if(lastIt->mIsNewParagraphChar)
3437 // 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.
3438 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3440 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3441 maxRowLeft = std::min(maxRowLeft, min.x);
3442 maxRowRight = std::max(maxRowRight, max.x);
3443 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3444 float rowTop = rowBottom - rowSize.height;
3446 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3449 rowRight = std::numeric_limits<float>::max();
3451 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3453 selectionState = SelectionNone;
3455 // Still selected? start a new selection
3458 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3460 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3461 selectionState = SelectionStarted;
3466 // build up highlight(s) with this selection data.
3467 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3468 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3475 // If reached end, and still on selection, then close selection.
3478 if(selectionState == SelectionStarted)
3480 // finished selection.
3482 if(lastIt->mIsNewParagraphChar)
3484 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3486 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3487 maxRowLeft = std::min(maxRowLeft, min.x);
3488 maxRowRight = std::max(maxRowRight, max.x);
3489 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3490 float rowTop = rowBottom - rowSize.height;
3491 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3495 // Get the top left and bottom right corners.
3496 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3497 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3498 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3500 // Clamp quads so they appear to clip to borders of the whole text.
3501 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3503 // For block-align align Further Clamp quads to max left and right extents
3504 // BlockAlign: Will adjust highlight to block:
3506 // H[ello] (top row right = max of all rows right)
3507 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3508 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3509 // [text] (bottom row left = min of all rows left)
3510 // (common in SMS messaging selection)
3512 // As opposed to the default which is tight text highlighting.
3517 // (common in regular text editors/web browser selection)
3518 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3520 // Finally clamp quads again so they don't exceed the boundry of the control.
3521 const Vector3& controlSize = GetControlSize();
3522 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3525 return mNewHighlightInfo;
3528 // VCC TODO: two methods are not needed. this one is a quick hack to fix PLMs. Should implement one which support both directions.
3529 // This method creates one quad per character so different selection boxes for a mix of LTR and RTL languages are created.
3530 TextInput::HighlightInfo TextInput::CalculateHighlightInfoRtl()
3532 // At the moment there is no public API to modify the block alignment option.
3534 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3536 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3538 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3539 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3541 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3542 std::vector<bool> selectedVisualText;
3543 GetVisualTextSelection( selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
3544 std::vector<bool>::iterator selectedIt = selectedVisualText.begin();
3545 std::vector<bool>::iterator selectedEndIt = selectedVisualText.end();
3547 // SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3548 float rowLeft = 0.0f;
3549 float rowRight = 0.0f;
3551 // VCC TODO this is valid for one line.
3553 const Size rowSize = GetRowRectFromCharacterPosition( 0, min, max );
3555 // Scan through entire text.
3558 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3560 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3561 bool charSelected = false;
3562 if( selectedIt != selectedEndIt )
3564 charSelected = *selectedIt++;
3569 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3570 rowRight = rowLeft + charInfo.mSize.width;
3572 float rowBottom = charInfo.mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3573 float rowTop = rowBottom - rowSize.height;
3574 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3580 // Finally clamp quads again so they don't exceed the boundry of the control.
3581 const Vector3& controlSize = GetControlSize();
3582 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3585 return mNewHighlightInfo;
3588 void TextInput::UpdateHighlight()
3590 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3592 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3594 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3595 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3596 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3597 // [BOTTOM] [ MIDDLE ]
3600 // Each quad is created as 2 triangles.
3601 // Middle is just 1 quad regardless of its size.
3617 void TextInput::ClearPopup()
3619 mPopupPanel.Clear();
3622 void TextInput::AddPopupOptions()
3624 mPopupPanel.AddPopupOptions();
3627 void TextInput::SetPopupPosition( const Vector3& position, const Vector2& alternativePosition )
3629 const Vector3& visiblePopUpSize = mPopupPanel.GetVisibileSize();
3631 Vector3 clampedPosition ( position );
3632 Vector3 tailOffsetPosition ( position );
3634 float xOffSet( 0.0f );
3636 Actor self = Self();
3637 const Vector3 textViewTopLeftWorldPosition = self.GetCurrentWorldPosition() - self.GetCurrentSize()*0.5f;
3639 const float popUpLeft = textViewTopLeftWorldPosition.x + position.x - visiblePopUpSize.width*0.5f;
3640 const float popUpTop = textViewTopLeftWorldPosition.y + position.y - visiblePopUpSize.height;
3642 // Clamp to left or right or of boundary
3643 if( popUpLeft < mBoundingRectangleWorldCoordinates.x )
3645 xOffSet = mBoundingRectangleWorldCoordinates.x - popUpLeft ;
3647 else if ( popUpLeft + visiblePopUpSize.width > mBoundingRectangleWorldCoordinates.z )
3649 xOffSet = mBoundingRectangleWorldCoordinates.z - ( popUpLeft + visiblePopUpSize.width );
3652 clampedPosition.x = position.x + xOffSet;
3653 tailOffsetPosition.x = -xOffSet;
3655 // Check if top left of PopUp outside of top bounding rectangle, if so then flip to lower position.
3656 bool flipTail( false );
3658 if ( popUpTop < mBoundingRectangleWorldCoordinates.y )
3660 clampedPosition.y = alternativePosition.y + visiblePopUpSize.height;
3664 mPopupPanel.GetRootActor().SetPosition( clampedPosition );
3665 mPopupPanel.SetTailPosition( tailOffsetPosition, flipTail );
3668 void TextInput::HidePopup(bool animate, bool signalFinished )
3670 if ( ( mPopupPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopupPanel.GetState() == TextInputPopup::StateShown ) )
3672 mPopupPanel.Hide( animate );
3674 if( animate && signalFinished )
3676 mPopupPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3681 void TextInput::ShowPopup( bool animate )
3684 Vector2 alternativePopupPosition;
3686 if(false && mState == StateEdit)
3689 Vector3 bottomHandle; // referring to the bottom most point of the handle or the bottom line of selection.
3691 // When text is selected, show popup above top handle (and text), or below bottom handle.
3692 // topHandle: referring to the top most point of the handle or the top line of selection.
3693 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3695 topHandle = mSelectionHandleOneActualPosition;
3696 bottomHandle = mSelectionHandleTwoActualPosition;
3697 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3701 topHandle = mSelectionHandleTwoActualPosition;
3702 bottomHandle = mSelectionHandleOneActualPosition;
3703 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3705 topHandle.y += -mPopupOffsetFromText.y - rowSize.height;
3706 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3708 float xPosition = ( fabsf( topHandle.x - bottomHandle.x ) )*0.5f + std::min( mSelectionHandleOneActualPosition.x , mSelectionHandleTwoActualPosition.x );
3710 position.x = xPosition;
3712 // Alternative position if no upper space
3713 bottomHandle.y += GetSelectionHandleSize().y + mPopupOffsetFromText.w;
3714 alternativePopupPosition = Vector2 ( position.x, bottomHandle.y );
3718 // When no text is selected, show popup at world position of grab handle or cursor
3719 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3720 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3721 position.y -= ( mPopupOffsetFromText.y + rowSize.height );
3722 // if can't be positioned above, then position below row.
3723 alternativePopupPosition = Vector2( position.x, position.y ); // default if no grab handle
3726 // If grab handle enabled then position pop-up below the grab handle.
3727 alternativePopupPosition.y = rowSize.height + mGrabHandle.GetCurrentSize().height + mPopupOffsetFromText.w +50.0f;
3731 SetPopupPosition( position, alternativePopupPosition );
3734 mPopupPanel.Show( Self(), animate );
3735 StartMonitoringStageForTouch();
3737 mPopupPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3740 void TextInput::ShowPopupCutCopyPaste()
3744 mPopupPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3745 // Check the selected text is whole text or not.
3746 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3748 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3751 if ( !mStyledText.empty() && IsTextSelected() )
3753 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCopy, true );
3754 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCut, true );
3757 if( mClipboard && mClipboard.NumberOfItems() )
3759 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsPaste, true );
3760 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3765 mPopupPanel.Hide(false);
3769 void TextInput::SetUpPopupSelection( bool showCutButton )
3772 mPopupPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3773 // If no text exists then don't offer to select
3774 if ( !mStyledText.empty() )
3776 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3777 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsSelect, true );
3778 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsCut, ( showCutButton && IsTextSelected() ) );
3780 // if clipboard has valid contents then offer paste option
3781 if( mClipboard && mClipboard.NumberOfItems() )
3783 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsPaste, true );
3784 mPopupPanel.TogglePopupButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3789 mPopupPanel.Hide(false);
3792 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3797 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3798 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3799 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3800 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3802 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3804 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3806 float closestYdifference = std::numeric_limits<float>::max();
3807 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3808 std::size_t numberOfMatchedCharacters = 0;
3810 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3811 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3813 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3815 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3816 float baselinePosition = info.mPosition.y - info.mDescender;
3818 if( info.mIsVisible )
3820 // store difference between source y point and the y position of the current character
3821 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3823 if( currentYdifference < closestYdifference )
3825 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3826 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3827 closestYdifference = currentYdifference;
3828 matchedCharacters.clear();
3829 numberOfMatchedCharacters = 0; // reset count
3832 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3833 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3835 // ignore new line character.
3836 if( !info.mIsNewParagraphChar )
3838 matchedCharacters.push_back( info );
3839 numberOfMatchedCharacters++;
3843 } // End of loop checking each character's y position in the character layout table
3845 // Check if last character is a newline, if it is
3846 // then need pretend there is an imaginary line afterwards,
3847 // and check if user is touching below previous line.
3848 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3850 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewParagraphChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3852 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3856 // 2 Iterate through matching list of y positions and find closest matching X position.
3858 bool matched( false );
3860 // Traverse the characters in the visual order. VCC TODO: check for more than one line.
3861 std::size_t visualIndex = 0u;
3862 const std::size_t matchedCharactersSize = matchedCharacters.size();
3863 for( ; visualIndex < matchedCharactersSize; ++visualIndex )
3865 const Toolkit::TextView::CharacterLayoutInfo& info( *( matchedCharacters.begin() + mTextLayoutInfo.mCharacterVisualToLogicalMap[visualIndex] ) );
3867 if( info.mIsVisible )
3869 // stop when on left side of character's center.
3870 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3871 if( sourceScrollOffset.x < characterMidPointPosition )
3873 if(info.mIsRightToLeftCharacter)
3875 rightToLeftChar = true;
3877 glyphIntersection = info.mPosition.x;
3882 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3886 if( visualIndex == matchedCharactersSize )
3888 rightToLeftChar = lastRightToLeftChar;
3891 closestIndex = lineOffset + visualIndex;
3893 mClosestCursorPositionEOL = false; // reset
3894 if( ( visualIndex == matchedCharactersSize ) && !matched )
3896 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3899 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3900 if( rightToLeftChar && lastRightToLeftChar )
3902 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3907 // closestIndex is the visual index, need to convert it to the logical index
3908 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3910 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3912 // Checks for situations where user is touching between LTR and RTL
3913 // characters. To identify if the user means the end of a LTR string
3914 // or the beginning of an RTL string, and vice versa.
3915 if( closestIndex > 0 )
3917 if( rightToLeftChar && !lastRightToLeftChar )
3922 // A: In this touch range, the user is indicating that they wish to place
3923 // the cursor at the end of the LTR text.
3924 // B: In this touch range, the user is indicating that they wish to place
3925 // the cursor at the end of the RTL text.
3927 // Result of touching A area:
3928 // [.....LTR]|[RTL......]+
3930 // |: primary cursor (for typing LTR chars)
3931 // +: secondary cursor (for typing RTL chars)
3933 // Result of touching B area:
3934 // [.....LTR]+[RTL......]|
3936 // |: primary cursor (for typing RTL chars)
3937 // +: secondary cursor (for typing LTR chars)
3939 if( sourceScrollOffset.x < glyphIntersection )
3944 else if( !rightToLeftChar && lastRightToLeftChar )
3946 if( sourceScrollOffset.x < glyphIntersection )
3953 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
3954 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
3955 // one further ahead
3956 if( rightToLeftChar && !lastRightToLeftChar )
3961 else if( closestIndex == std::numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
3963 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
3965 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
3974 float TextInput::GetLineJustificationPosition() const
3976 const Vector3& size = mDisplayedTextView.GetCurrentSize();
3977 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
3978 float alignmentOffset = 0.f;
3980 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
3981 if( alignment & Toolkit::Alignment::HorizontalLeft )
3983 alignmentOffset = 0.f;
3985 else if( alignment & Toolkit::Alignment::HorizontalCenter )
3987 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
3989 else if( alignment & Toolkit::Alignment::HorizontalRight )
3991 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
3994 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
3995 float justificationOffset = 0.f;
3997 switch( justification )
3999 case Toolkit::TextView::Left:
4001 justificationOffset = 0.f;
4004 case Toolkit::TextView::Center:
4006 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4009 case Toolkit::TextView::Right:
4011 justificationOffset = mTextLayoutInfo.mTextSize.width;
4014 case Toolkit::TextView::Justified:
4016 justificationOffset = 0.f;
4021 DALI_ASSERT_ALWAYS( false );
4025 return alignmentOffset + justificationOffset;
4028 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4030 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4031 A newline character is not inserted in this case */
4033 Vector3 cursorPosition;
4035 Toolkit::TextView::CharacterLayoutInfo currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4039 if( characterPosition > 0u )
4041 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1u ];
4043 // If previous character on a different line then use current characters position
4044 if( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4046 // VCC TODO: PositionCursorAfterWordWrap currently doesn't work for multiline. Need to check this branch.
4047 if ( mClosestCursorPositionEOL )
4049 cursorPosition = Vector3( previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z ) ;
4053 cursorPosition = Vector3( currentCharInfo.mPosition );
4062 // If the character is left to right, the position is the character's position plus its width.
4063 const float ltrOffset = !currentCharInfo.mIsRightToLeftCharacter ? currentCharInfo.mSize.width : 0.f;
4065 cursorPosition.x = currentCharInfo.mPosition.x + ltrOffset;
4066 cursorPosition.y = currentCharInfo.mPosition.y;
4069 return cursorPosition;
4072 Vector3 TextInput::GetActualPositionFromCharacterPosition( std::size_t characterPosition ) const
4074 bool direction = false;
4075 Vector3 alternatePosition;
4076 bool alternatePositionValid = false;
4078 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4081 Vector3 TextInput::GetActualPositionFromCharacterPosition( std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4083 DALI_ASSERT_DEBUG( ( mTextLayoutInfo.mCharacterLayoutInfoTable.size() == mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ) &&
4084 ( mTextLayoutInfo.mCharacterLayoutInfoTable.size() == mTextLayoutInfo.mCharacterVisualToLogicalMap.size() ) &&
4085 "TextInput::GetActualPositionFromCharacterPosition. All layout tables must have the same size." );
4087 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4089 alternatePositionValid = false;
4090 directionRTL = false;
4092 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4094 if( characterPosition == 0u )
4096 // When the cursor position is at the beginning, it should be at the start of the current character.
4097 // If the current character is LTR, then the start is on the right side of the glyph.
4098 // If the current character is RTL, then the start is on the left side of the glyph.
4100 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() ) ).mIsVisible )
4102 characterPosition = FindVisibleCharacter( Right, 0u );
4105 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4106 const float rtlOffset = info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f;
4108 cursorPosition.x = info.mPosition.x + rtlOffset;
4109 cursorPosition.y = info.mPosition.y;
4110 directionRTL = info.mIsRightToLeftCharacter;
4112 else if( characterPosition > 0u )
4114 // Get the direction of the paragraph.
4115 const std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition );
4116 const bool isParagraphRightToLeft = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + startCharacterPosition ) ).mIsRightToLeftCharacter;
4118 // When cursor is not at beginning, consider possibility of
4119 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4121 // Cursor position should be the end of the last character.
4122 // If the last character is LTR, then the end is on the right side of the glyph.
4123 // If the last character is RTL, then the end is on the left side of the glyph.
4125 --characterPosition;
4127 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition ) ).mIsVisible )
4129 characterPosition = FindVisibleCharacter( Left, characterPosition );
4132 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4133 if( ( characterPosition > 0u ) && info.mIsNewParagraphChar && !IsScrollEnabled() )
4135 // VCC TODO : check for a new paragraph character.
4137 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4138 const Vector3& size = GetControlSize();
4140 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4142 --characterPosition;
4144 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4147 if( !info.mIsNewParagraphChar )
4149 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4153 // VCC TODO : check for a new paragraph character.
4155 // When cursor points to first character on new line, position cursor at the start of this glyph.
4156 if( characterPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4158 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4159 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4161 cursorPosition.x = infoNext.mPosition.x + start;
4162 cursorPosition.y = infoNext.mPosition.y;
4166 // If cursor points to the end of text, then can only position
4167 // cursor where the new line starts based on the line-justification position.
4168 cursorPosition.x = GetLineJustificationPosition();
4170 if( characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size() )
4172 // If this is after the last character, then we can assume that the new cursor
4173 // should be exactly one row below the current row.
4175 const Size rowRect = GetRowRectFromCharacterPosition( characterPosition - 1u );
4176 cursorPosition.y = info.mPosition.y + rowRect.height;
4180 // If this is not after last character, then we can use this row's height.
4181 // should be exactly one row below the current row.
4183 const Size rowRect = GetRowRectFromCharacterPosition( characterPosition );
4184 cursorPosition.y = info.mPosition.y + rowRect.height;
4189 directionRTL = info.mIsRightToLeftCharacter;
4191 if( 1u < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4193 // 1. When the cursor is neither at the beginning or the end,
4194 // we can show multiple cursors under situations when the cursor is
4195 // between RTL and LTR text...
4196 if( characterPosition + 1u < mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4198 std::size_t characterAltPosition = characterPosition + 1u;
4200 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterAltPosition ];
4202 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4204 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4205 // Text: [...LTR...]|[...RTL...]
4207 // Alternate cursor pos: ^
4208 // In which case we need to display an alternate cursor for the RTL text.
4210 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4211 alternatePosition.y = infoAlt.mPosition.y;
4212 alternatePositionValid = true;
4214 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4216 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4217 // Text: |[...RTL...] [...LTR....]
4219 // Alternate cursor pos: ^
4220 // In which case we need to display an alternate cursor for the RTL text.
4222 alternatePosition.x = infoAlt.mPosition.x;
4223 alternatePosition.y = infoAlt.mPosition.y;
4224 alternatePositionValid = true;
4229 // 2. When the cursor is at the end of the text,
4230 // and we have multi-directional text,
4231 // we can also consider showing mulitple cursors.
4232 // The rule here is:
4233 // If first and last characters on row are different
4234 // Directions, then two cursors need to be displayed.
4236 if( info.mIsRightToLeftCharacter != isParagraphRightToLeft )
4238 // The last character's direction is differernt than the first one of current paragraph.
4241 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ GetFirstCharacterWithSameDirection( characterPosition ) ];
4243 if(info.mIsRightToLeftCharacter)
4245 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4246 // Text: [...LTR...]|[...RTL...]
4248 // Alternate cursor pos: ^
4249 // In which case we need to display an alternate cursor for the RTL text, this cursor
4250 // should be at the end of the given line.
4252 alternatePosition.x = infoStart.mPosition.x + infoStart.mSize.width;
4253 alternatePosition.y = infoStart.mPosition.y;
4254 alternatePositionValid = true;
4256 else if(!info.mIsRightToLeftCharacter) // starting RTL
4258 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4259 // Text: |[...RTL...] [...LTR....]
4261 // Alternate cursor pos: ^
4262 // In which case we need to display an alternate cursor for the RTL text.
4264 alternatePosition.x = infoStart.mPosition.x;
4265 alternatePosition.y = infoStart.mPosition.y;
4266 alternatePositionValid = true;
4271 } // characterPosition > 0
4275 // If the character table is void, place the cursor accordingly the text alignment.
4276 const Vector3& size = GetControlSize();
4278 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4279 float alignmentOffset = 0.f;
4281 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4282 if( alignment & Toolkit::Alignment::HorizontalLeft )
4284 alignmentOffset = 0.f;
4286 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4288 alignmentOffset = 0.5f * ( size.width );
4290 else if( alignment & Toolkit::Alignment::HorizontalRight )
4292 alignmentOffset = size.width;
4295 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4296 cursorPosition.x = alignmentOffset;
4298 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4299 if( alignment & Toolkit::Alignment::VerticalTop )
4301 cursorPosition.y = mLineHeight;
4303 else if( alignment & Toolkit::Alignment::VerticalCenter )
4305 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4307 else if( alignment & Toolkit::Alignment::VerticalBottom )
4309 cursorPosition.y = size.height;
4313 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4314 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4316 if( alternatePositionValid )
4318 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4319 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4322 return cursorPosition;
4325 std::size_t TextInput::GetRowStartFromCharacterPosition( std::size_t logicalPosition ) const
4327 // scan string from current position to beginning of current line to note direction of line
4328 while( logicalPosition )
4331 if( mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsNewParagraphChar )
4338 return logicalPosition;
4341 std::size_t TextInput::GetFirstCharacterWithSameDirection( std::size_t logicalPosition ) const
4343 const bool isRightToLeft = mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsRightToLeftCharacter;
4345 while( logicalPosition )
4348 if( isRightToLeft != mTextLayoutInfo.mCharacterLayoutInfoTable[logicalPosition].mIsRightToLeftCharacter )
4355 return logicalPosition;
4358 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4362 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4365 Size TextInput::GetRowRectFromCharacterPosition( std::size_t characterPosition, Vector2& min, Vector2& max ) const
4367 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4368 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4370 min = Vector2::ZERO;
4371 max = Vector2(0.0f, mLineHeight);
4375 DALI_ASSERT_DEBUG( characterPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4377 // Initializes the min and max position.
4378 const std::size_t initialPosition = ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) ? characterPosition - 1u : characterPosition;
4379 min = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + initialPosition ) ).mPosition.GetVectorXY();
4383 // 1) Find the line where the character is laid-out.
4384 for( Toolkit::TextView::LineLayoutInfoContainer::const_iterator lineIt = mTextLayoutInfo.mLines.begin(), lineEndIt = mTextLayoutInfo.mLines.end();
4385 !found && ( lineIt != mTextLayoutInfo.mLines.end() );
4388 const Toolkit::TextView::LineLayoutInfo& lineInfo( *lineIt );
4390 // Index within the whole text to the last character of the current line.
4391 std::size_t lastCharacterOfLine = 0u;
4393 Toolkit::TextView::LineLayoutInfoContainer::const_iterator lineNextIt = lineIt + 1u;
4394 if( lineNextIt != lineEndIt )
4396 lastCharacterOfLine = (*lineNextIt).mCharacterGlobalIndex - 1u;
4400 lastCharacterOfLine = mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1u;
4403 // Check if the given chracter position is within the line.
4404 if( ( lineInfo.mCharacterGlobalIndex <= initialPosition ) && ( initialPosition <= lastCharacterOfLine ) )
4406 // 2) Get the row rect of all laid-out characters on the line.
4408 // Need to scan all characters of the line because they are in the logical position.
4409 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + lineInfo.mCharacterGlobalIndex,
4410 endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + lastCharacterOfLine + 1u;
4414 const Toolkit::TextView::CharacterLayoutInfo& characterInfo( *it );
4416 min.x = std::min( min.x, characterInfo.mPosition.x );
4417 min.y = std::min( min.y, characterInfo.mPosition.y );
4418 max.x = std::max( max.x, characterInfo.mPosition.x + characterInfo.mSize.width );
4419 max.y = std::max( max.y, characterInfo.mPosition.y + characterInfo.mSize.height );
4426 return Size( max.x - min.x, max.y - min.y );
4429 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4431 Actor popUpPanel = mPopupPanel.GetRootActor();
4433 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4439 Dali::Actor parent( touchedActor.GetParent() );
4443 return WasTouchedCheck( parent );
4450 void TextInput::StartMonitoringStageForTouch()
4452 Stage stage = Stage::GetCurrent();
4453 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4456 void TextInput::EndMonitoringStageForTouch()
4458 Stage stage = Stage::GetCurrent();
4459 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4462 void TextInput::OnStageTouched(const TouchEvent& event)
4464 if( event.GetPointCount() > 0 )
4466 if ( TouchPoint::Down == event.GetPoint(0).state )
4468 const Actor touchedActor(event.GetPoint(0).hitActor);
4470 bool popUpShown( false );
4472 if ( ( mPopupPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopupPanel.GetState() == TextInputPopup::StateShown ) )
4477 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4479 if ( ( false || popUpShown ) && !textInputTouched )
4481 EndMonitoringStageForTouch();
4482 HidePopup( true, false );
4485 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4487 EndMonitoringStageForTouch();
4488 ShowGrabHandleAndSetVisibility( false );
4494 void TextInput::SelectText(std::size_t start, std::size_t end)
4496 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4497 IsGrabHandleEnabled()?"true":"false",
4498 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4499 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4500 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4502 StartMonitoringStageForTouch();
4504 if ( mEditModeActive ) // Only allow text selection when in edit mode
4506 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4507 mSelectingText = true;
4509 std::size_t selectionStartPosition = std::min( start, end );
4511 // Hide grab handle when selecting.
4512 ShowGrabHandleAndSetVisibility( false );
4514 if( start != end ) // something to select
4516 SetCursorVisibility( false );
4517 StopCursorBlinkTimer();
4519 CreateSelectionHandles(start, end);
4522 const TextStyle oldInputStyle( mInputStyle );
4523 mInputStyle = GetStyleAt( selectionStartPosition ); // Inherit style from selected position.
4525 if( oldInputStyle != mInputStyle )
4527 // Updates the line height accordingly with the input style.
4530 EmitStyleChangedSignal();
4536 mSelectingText = false;
4540 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4542 MarkupProcessor::StyledTextArray currentSelectedText;
4544 if ( IsTextSelected() )
4546 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4547 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4549 for(; it != end; ++it)
4551 MarkupProcessor::StyledText& styledText( *it );
4552 currentSelectedText.push_back( styledText );
4555 return currentSelectedText;
4558 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4560 const std::size_t beginIndex = std::min( begin, end );
4561 const std::size_t endIndex = std::max( begin, end );
4564 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4566 // Create a styled text array used to replace the text into the text-view.
4567 MarkupProcessor::StyledTextArray text;
4568 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4570 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4571 GetTextLayoutInfo();
4573 if( IsScrollEnabled() )
4575 // Need to set the scroll position as the text's size may have changed.
4576 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4579 ShowGrabHandleAndSetVisibility( false );
4585 // Set Handle positioning as the new style may have repositioned the characters.
4586 SetSelectionHandlePosition(HandleOne);
4587 SetSelectionHandlePosition(HandleTwo);
4590 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4592 // Just hide the grab handle when keyboard is hidden.
4593 if (!keyboardShown )
4595 ShowGrabHandleAndSetVisibility( false );
4597 // If the keyboard is not now being shown, then hide the popup panel
4598 mPopupPanel.Hide( true );
4602 // Removes highlight and resumes edit mode state
4603 void TextInput::RemoveHighlight( bool hidePopup )
4605 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4609 if ( mSelectionHandleOne )
4611 mActiveLayer.Remove( mSelectionHandleOne );
4612 mSelectionHandleOne.Reset();
4613 mSelectionHandleOneOffset.x = 0.0f;
4615 if ( mSelectionHandleTwo )
4617 mActiveLayer.Remove( mSelectionHandleTwo );
4618 mSelectionHandleTwo.Reset();
4619 mSelectionHandleTwoOffset.x = 0.0f;
4622 mNewHighlightInfo.mQuadList.clear();
4625 SetCursorVisibility( true );
4626 StartCursorBlinkTimer();
4628 // NOTE: We cannot dereference mHighlightMesh, due
4629 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4637 mSelectionHandleOnePosition = 0;
4638 mSelectionHandleTwoPosition = 0;
4641 void TextInput::CreateHighlight()
4646 bool TextInput::CopySelectedTextToClipboard()
4648 mCurrentCopySelecton.clear();
4650 mCurrentCopySelecton = GetSelectedText();
4652 std::string stringToStore;
4654 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4655 * a marked up string.
4657 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4658 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4660 bool success = mClipboard.SetItem( stringToStore );
4664 void TextInput::PasteText( const Text& text )
4666 // Update Flag, indicates whether to update the text-input contents or not.
4667 // Any key stroke that results in a visual change of the text-input should
4668 // set this flag to true.
4669 bool update = false;
4672 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4673 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4675 ImfManager imfManager = ImfManager::Get();
4678 imfManager.SetCursorPosition( mCursorPosition );
4679 imfManager.NotifyCursorPosition();
4681 DeleteHighlightedText( true );
4685 bool textExceedsMaximunNumberOfCharacters = false;
4686 bool textExceedsBoundary = false;
4688 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4690 mCursorPosition += insertedStringLength;
4691 ImfManager imfManager = ImfManager::Get();
4694 imfManager.SetCursorPosition ( mCursorPosition );
4695 imfManager.NotifyCursorPosition();
4698 update = update || ( insertedStringLength > 0 );
4705 if( insertedStringLength < text.GetLength() )
4707 EmitMaxInputCharactersReachedSignal();
4710 if( textExceedsBoundary )
4712 EmitInputTextExceedsBoundariesSignal();
4716 void TextInput::SetTextDirection()
4718 // Put the cursor to the right if we are empty and an RTL language is being used.
4719 if ( mStyledText.empty() )
4721 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4723 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4724 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4726 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4727 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4729 int alignment( mDisplayedTextView.GetTextAlignment() &
4730 ( Toolkit::Alignment::VerticalTop |
4731 Toolkit::Alignment::VerticalCenter |
4732 Toolkit::Alignment::VerticalBottom |
4733 Toolkit::Alignment::HorizontalCenter ) );
4734 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4736 // If our alignment is in the center, then do not change.
4737 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4739 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4742 // If our justification is in the center, then do not change.
4743 if ( justification != Toolkit::TextView::Center )
4745 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4748 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4749 mDisplayedTextView.SetLineJustification( justification );
4753 void TextInput::UpdateLineHeight()
4755 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4756 mLineHeight = font.GetLineHeight();
4758 // 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.
4760 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4762 if( !mExceedEnabled || shrink )
4764 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4768 std::size_t TextInput::FindVisibleCharacter( FindVisibleCharacterDirection direction , std::size_t cursorPosition ) const
4770 // VCC check if we need do this in the visual order ...
4771 std::size_t position = 0u;
4773 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4779 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4781 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1u : position ) ) ).mIsVisible )
4783 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4789 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4790 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1u : position ) ) ).mIsVisible )
4792 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4798 position = FindVisibleCharacterLeft( 0u, mTextLayoutInfo.mCharacterLayoutInfoTable );
4803 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4810 void TextInput::SetSortModifier( float depthOffset )
4812 if(mDisplayedTextView)
4814 mDisplayedTextView.SetSortModifier(depthOffset);
4818 void TextInput::SetSnapshotModeEnabled( bool enable )
4820 if(mDisplayedTextView)
4822 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4826 bool TextInput::IsSnapshotModeEnabled() const
4828 bool snapshotEnabled = false;
4830 if(mDisplayedTextView)
4832 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4835 return snapshotEnabled;
4838 void TextInput::SetMarkupProcessingEnabled( bool enable )
4840 mMarkUpEnabled = enable;
4843 bool TextInput::IsMarkupProcessingEnabled() const
4845 return mMarkUpEnabled;
4848 void TextInput::SetScrollEnabled( bool enable )
4850 if( mDisplayedTextView )
4852 mDisplayedTextView.SetScrollEnabled( enable );
4857 // Don't set cursor's and handle's visibility to false if they are outside the
4858 // boundaries of the text-input.
4859 mIsCursorInScrollArea = true;
4860 mIsGrabHandleInScrollArea = true;
4861 if( mSelectionHandleOne && mSelectionHandleTwo )
4863 mSelectionHandleOne.SetVisible( true );
4864 mSelectionHandleTwo.SetVisible( true );
4870 bool TextInput::IsScrollEnabled() const
4872 bool scrollEnabled = false;
4874 if( mDisplayedTextView )
4876 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4879 return scrollEnabled;
4882 void TextInput::SetScrollPosition( const Vector2& position )
4884 if( mDisplayedTextView )
4886 mDisplayedTextView.SetScrollPosition( position );
4890 Vector2 TextInput::GetScrollPosition() const
4892 Vector2 scrollPosition;
4894 if( mDisplayedTextView )
4896 scrollPosition = mDisplayedTextView.GetScrollPosition();
4899 return scrollPosition;
4902 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4904 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4905 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4906 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4908 // Add style to the new input text.
4909 MarkupProcessor::StyledTextArray textToInsert;
4910 for( std::size_t i = 0; i < insertedStringLength; ++i )
4912 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4913 textToInsert.push_back( newStyledCharacter );
4916 //Insert text to the TextView.
4917 const bool emptyTextView = mStyledText.empty();
4918 if( emptyTextView && mPlaceHolderSet )
4920 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
4921 mDisplayedTextView.SetText( textToInsert );
4925 if( 0 == numberOfCharactersToReplace )
4927 mDisplayedTextView.InsertTextAt( position, textToInsert );
4931 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
4934 mPlaceHolderSet = false;
4936 if( textToInsert.empty() )
4938 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
4939 GetTextLayoutInfo();
4943 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
4944 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4947 textExceedsBoundary = false;
4949 if( !mExceedEnabled )
4951 const Vector3& size = GetControlSize();
4953 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
4955 // If new text does not fit within TextView
4956 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
4957 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
4958 GetTextLayoutInfo();
4959 textExceedsBoundary = true;
4960 insertedStringLength = 0;
4963 if( textExceedsBoundary )
4965 // Add the part of the text which fits on the text-input.
4967 // Split the text which doesn't fit in two halves.
4968 MarkupProcessor::StyledTextArray firstHalf;
4969 MarkupProcessor::StyledTextArray secondHalf;
4970 SplitText( textToInsert, firstHalf, secondHalf );
4972 // Clear text. This text will be filled with the text inserted.
4973 textToInsert.clear();
4975 // Where to insert the text.
4976 std::size_t positionToInsert = position;
4978 bool end = text.GetLength() <= 1;
4981 // Insert text and check ...
4982 const std::size_t textLength = firstHalf.size();
4983 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
4984 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4986 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
4988 // Inserted text doesn't fit.
4990 // Remove inserted text
4991 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
4992 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
4994 // The iteration finishes when only one character doesn't fit.
4995 end = textLength <= 1;
4999 // Prepare next two halves for next iteration.
5000 MarkupProcessor::StyledTextArray copyText = firstHalf;
5001 SplitText( copyText, firstHalf, secondHalf );
5008 // store text to be inserted in mStyledText.
5009 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5011 // Increase the inserted characters counter.
5012 insertedStringLength += textLength;
5014 // Prepare next two halves for next iteration.
5015 MarkupProcessor::StyledTextArray copyText = secondHalf;
5016 SplitText( copyText, firstHalf, secondHalf );
5018 // Update where next text has to be inserted
5019 positionToInsert += textLength;
5025 if( textToInsert.empty() && emptyTextView )
5027 // No character has been added and the text-view was empty.
5028 // Show the placeholder text.
5029 ShowPlaceholderText( mStyledPlaceHolderText );
5033 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5034 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5035 mPlaceHolderSet = false;
5038 return insertedStringLength;
5041 void TextInput::GetTextLayoutInfo()
5043 if( mStyledText.empty() )
5045 // The text-input has no text, clear the text-view's layout info.
5046 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5050 if( mDisplayedTextView )
5052 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5056 // There is no text-view.
5057 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5062 void TextInput::SetOffsetFromText( const Vector4& offset )
5064 mPopupOffsetFromText = offset;
5067 const Vector4& TextInput::GetOffsetFromText() const
5069 return mPopupOffsetFromText;
5072 void TextInput::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value )
5074 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5078 TextInput& textInputImpl( GetImpl( textInput ) );
5080 switch ( propertyIndex )
5082 case Toolkit::TextInput::Property::HIGHLIGHT_COLOR:
5084 textInputImpl.SetMaterialDiffuseColor( value.Get< Vector4 >() );
5087 case Toolkit::TextInput::Property::CUT_AND_PASTE_COLOR:
5089 textInputImpl.mPopupPanel.SetCutPastePopupColor( value.Get< Vector4 >() );
5092 case Toolkit::TextInput::Property::CUT_AND_PASTE_PRESSED_COLOR:
5094 textInputImpl.mPopupPanel.SetCutPastePopupPressedColor( value.Get< Vector4 >() );
5097 case Toolkit::TextInput::Property::CUT_AND_PASTE_BORDER_COLOR:
5099 textInputImpl.mPopupPanel.SetCutPastePopupBorderColor( value.Get< Vector4 >() );
5102 case Toolkit::TextInput::Property::CUT_AND_PASTE_ICON_COLOR:
5104 textInputImpl.mPopupPanel.SetCutPastePopupIconColor( value.Get< Vector4 >() );
5107 case Toolkit::TextInput::Property::CUT_AND_PASTE_ICON_PRESSED_COLOR:
5109 textInputImpl.mPopupPanel.SetCutPastePopupIconPressedColor( value.Get< Vector4 >() );
5112 case Toolkit::TextInput::Property::CUT_AND_PASTE_TEXT_COLOR:
5114 textInputImpl.mPopupPanel.SetCutPastePopupTextColor( value.Get< Vector4 >() );
5117 case Toolkit::TextInput::Property::CUT_AND_PASTE_TEXT_PRESSED_COLOR:
5119 textInputImpl.mPopupPanel.SetCutPastePopupTextPressedColor( value.Get< Vector4 >() );
5122 case Toolkit::TextInput::Property::CUT_BUTTON_POSITION_PRIORITY:
5124 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCut, value.Get<unsigned int>() );
5127 case Toolkit::TextInput::Property::COPY_BUTTON_POSITION_PRIORITY:
5129 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCopy, value.Get<unsigned int>() );
5132 case Toolkit::TextInput::Property::PASTE_BUTTON_POSITION_PRIORITY:
5134 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsPaste, value.Get<unsigned int>() );
5137 case Toolkit::TextInput::Property::SELECT_BUTTON_POSITION_PRIORITY:
5139 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelect, value.Get<unsigned int>() );
5142 case Toolkit::TextInput::Property::SELECT_ALL_BUTTON_POSITION_PRIORITY:
5144 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll, value.Get<unsigned int>() );
5147 case Toolkit::TextInput::Property::CLIPBOARD_BUTTON_POSITION_PRIORITY:
5149 textInputImpl.mPopupPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsClipboard, value.Get<unsigned int>() );
5152 case Toolkit::TextInput::Property::POP_UP_OFFSET_FROM_TEXT:
5154 textInputImpl.SetOffsetFromText( value.Get< Vector4 >() );
5157 case Toolkit::TextInput::Property::CURSOR_COLOR:
5159 textInputImpl.mCursor.SetColor( value.Get< Vector4 >() );
5166 Property::Value TextInput::GetProperty( BaseObject* object, Property::Index propertyIndex )
5168 Property::Value value;
5170 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5174 TextInput& textInputImpl( GetImpl( textInput ) );
5176 switch ( propertyIndex )
5178 case Toolkit::TextInput::Property::HIGHLIGHT_COLOR:
5180 value = textInputImpl.GetMaterialDiffuseColor();
5183 case Toolkit::TextInput::Property::CUT_AND_PASTE_COLOR:
5185 value = textInputImpl.mPopupPanel.GetCutPastePopupColor();
5188 case Toolkit::TextInput::Property::CUT_AND_PASTE_PRESSED_COLOR:
5190 value = textInputImpl.mPopupPanel.GetCutPastePopupPressedColor();
5193 case Toolkit::TextInput::Property::CUT_AND_PASTE_BORDER_COLOR :
5195 value = textInputImpl.mPopupPanel.GetCutPastePopupBorderColor();
5198 case Toolkit::TextInput::Property::CUT_AND_PASTE_ICON_COLOR:
5200 value = textInputImpl.mPopupPanel.GetCutPastePopupIconColor();
5203 case Toolkit::TextInput::Property::CUT_AND_PASTE_ICON_PRESSED_COLOR:
5205 value = textInputImpl.mPopupPanel.GetCutPastePopupIconPressedColor();
5208 case Toolkit::TextInput::Property::CUT_AND_PASTE_TEXT_COLOR:
5210 value = textInputImpl.mPopupPanel.GetCutPastePopupTextColor();
5213 case Toolkit::TextInput::Property::CUT_AND_PASTE_TEXT_PRESSED_COLOR:
5215 value = textInputImpl.mPopupPanel.GetCutPastePopupTextPressedColor();
5218 case Toolkit::TextInput::Property::CUT_BUTTON_POSITION_PRIORITY:
5220 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCut );
5223 case Toolkit::TextInput::Property::COPY_BUTTON_POSITION_PRIORITY:
5225 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCopy );
5228 case Toolkit::TextInput::Property::PASTE_BUTTON_POSITION_PRIORITY:
5230 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsPaste );
5233 case Toolkit::TextInput::Property::SELECT_BUTTON_POSITION_PRIORITY:
5235 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelect );
5238 case Toolkit::TextInput::Property::SELECT_ALL_BUTTON_POSITION_PRIORITY:
5240 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll );
5243 case Toolkit::TextInput::Property::CLIPBOARD_BUTTON_POSITION_PRIORITY:
5245 value = textInputImpl.mPopupPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsClipboard );
5248 case Toolkit::TextInput::Property::POP_UP_OFFSET_FROM_TEXT:
5250 value = textInputImpl.GetOffsetFromText();
5253 case Toolkit::TextInput::Property::CURSOR_COLOR:
5255 value = textInputImpl.mCursor.GetCurrentColor();
5262 void TextInput::EmitStyleChangedSignal()
5264 // emit signal if input style changes.
5265 Toolkit::TextInput handle( GetOwner() );
5266 mStyleChangedSignal.Emit( handle, mInputStyle );
5269 void TextInput::EmitTextModified()
5271 // emit signal when text changes.
5272 Toolkit::TextInput handle( GetOwner() );
5273 mTextModifiedSignal.Emit( handle );
5277 void TextInput::EmitMaxInputCharactersReachedSignal()
5279 // emit signal if max characters is reached during text input.
5280 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5282 Toolkit::TextInput handle( GetOwner() );
5283 mMaxInputCharactersReachedSignal.Emit( handle );
5286 void TextInput::EmitInputTextExceedsBoundariesSignal()
5288 // Emit a signal when the input text exceeds the boundaries of the text input.
5290 Toolkit::TextInput handle( GetOwner() );
5291 mInputTextExceedBoundariesSignal.Emit( handle );
5294 } // namespace Internal
5296 } // namespace Toolkit