2 * Copyright (c) 2014 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 #include <dali/dali.h>
20 #include <dali-toolkit/internal/controls/text-input/text-input-impl.h>
21 #include <dali-toolkit/internal/controls/text-view/text-processor.h>
22 #include <dali-toolkit/public-api/controls/buttons/push-button.h>
23 #include <dali-toolkit/public-api/controls/alignment/alignment.h>
25 #include <dali/integration-api/debug.h>
38 #if defined(DEBUG_ENABLED)
39 Debug::Filter* gLogFilter = Debug::Filter::New(Debug::NoLogging, false, "LOG_TEXT_INPUT");
42 const std::size_t DEFAULT_MAX_SIZE( std::numeric_limits<std::size_t>::max() ); // Max possible number
43 const std::size_t DEFAULT_NUMBER_OF_LINES_LIMIT( std::numeric_limits<std::size_t>::max() ); // Max possible number
44 const Vector3 DEFAULT_SELECTION_HANDLE_SIZE( 51.0f, 79.0f, 0.0f ); // Selection cursor image size
45 const Vector3 DEFAULT_GRAB_HANDLE_RELATIVE_SIZE( 1.5f, 2.0f, 1.0f );
46 const Vector3 DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE( 1.5f, 1.5f, 1.0f );
47 const Vector4 LIGHTBLUE( 0.07f, 0.41f, 0.59f, 1.0f ); // Used for Selection highlight
49 const char* DEFAULT_GRAB_HANDLE( DALI_IMAGE_DIR "insertpoint-icon.png" );
50 const char* DEFAULT_SELECTION_HANDLE_ONE( DALI_IMAGE_DIR "text-input-selection-handle-left.png" );
51 const char* DEFAULT_SELECTION_HANDLE_TWO( DALI_IMAGE_DIR "text-input-selection-handle-right.png" );
52 const char* DEFAULT_SELECTION_HANDLE_ONE_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-left-press.png" );
53 const char* DEFAULT_SELECTION_HANDLE_TWO_PRESSED( DALI_IMAGE_DIR "text-input-selection-handle-right-press.png" );
54 const char* DEFAULT_CURSOR( DALI_IMAGE_DIR "cursor.png" );
56 const Vector4 DEFAULT_CURSOR_IMAGE_9_BORDER( 2.0f, 2.0f, 2.0f, 2.0f );
58 const std::size_t CURSOR_BLINK_INTERVAL = 500; ///< Cursor blink interval
59 const float CHARACTER_THRESHOLD( 2.5f ); ///< the threshold of a line.
60 const float DISPLAYED_HIGHLIGHT_Z_OFFSET( 0.1f ); ///< 1. Highlight rendered (z-offset).
61 const float DISPLAYED_TEXT_VIEW_Z_OFFSET( 0.2f ); ///< 2. Text rendered (z-offset).
62 const float UI_Z_OFFSET( 0.2f ); ///< 3. Text Selection Handles/Cursor z-offset.
64 const Vector3 UI_OFFSET(0.0f, 0.0f, UI_Z_OFFSET); ///< Text Selection Handles/Cursor offset.
65 const Vector3 DEFAULT_HANDLE_ONE_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle One's Offset
66 const Vector3 DEFAULT_HANDLE_TWO_OFFSET(0.0f, -5.0f, 0.0f); ///< Handle Two's Offset
67 const float TOP_HANDLE_TOP_OFFSET( 34.0f); ///< Offset between top handle and cutCopyPaste pop-up
68 const float BOTTOM_HANDLE_BOTTOM_OFFSET(34.0f); ///< Offset between bottom handle and cutCopyPaste pop-up
69 const float CURSOR_THICKNESS(6.0f);
70 const Degree CURSOR_ANGLE_OFFSET(2.0f); ///< Offset from the angle of italic angle.
72 const std::string NEWLINE( "\n" );
74 const TextStyle DEFAULT_TEXT_STYLE;
76 const unsigned int SCROLL_TICK_INTERVAL = 50u;
77 const float SCROLL_THRESHOLD = 10.f;
78 const float SCROLL_SPEED = 15.f;
81 * Selection state enumeration (FSM)
85 SelectionNone, ///< Currently not encountered selected section.
86 SelectionStarted, ///< Encountered selected section
87 SelectionFinished ///< Finished selected section
91 * Whether the given style is the default style or not.
92 * @param[in] style The given style.
93 * @return \e true if the given style is the default. Otherwise it returns \e false.
95 bool IsDefaultStyle( const TextStyle& style )
97 return DEFAULT_TEXT_STYLE == style;
101 * Whether the given styled text is using the default style or not.
102 * @param[in] textArray The given text.
103 * @return \e true if the given styled text is using the default style. Otherwise it returns \e false.
105 bool IsTextDefaultStyle( const Toolkit::MarkupProcessor::StyledTextArray& textArray )
107 for( Toolkit::MarkupProcessor::StyledTextArray::const_iterator it = textArray.begin(), endIt = textArray.end(); it != endIt; ++it )
109 const TextStyle& style( (*it).mStyle );
111 if( !IsDefaultStyle( style ) )
120 std::size_t FindVisibleCharacterLeft( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
122 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_reverse_iterator it = characterLayoutInfoTable.rbegin() + characterLayoutInfoTable.size() - cursorPosition, endIt = characterLayoutInfoTable.rend();
126 if( ( *it ).mIsVisible )
128 return --cursorPosition;
137 std::size_t FindVisibleCharacterRight( std::size_t cursorPosition, const Toolkit::TextView::CharacterLayoutInfoContainer& characterLayoutInfoTable )
139 for( Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = characterLayoutInfoTable.begin() + cursorPosition, endIt = characterLayoutInfoTable.end(); it < endIt; ++it )
141 if( ( *it ).mIsVisible )
143 return cursorPosition;
149 return cursorPosition;
153 * Whether the given position plus the cursor size offset is inside the given boundary.
155 * @param[in] position The given position.
156 * @param[in] cursorSize The cursor size.
157 * @param[in] controlSize The given boundary.
159 * @return whether the given position is inside the given boundary.
161 bool IsPositionInsideBoundaries( const Vector3& position, const Size& cursorSize, const Vector3& controlSize )
163 return ( position.x >= -Math::MACHINE_EPSILON_1000 ) &&
164 ( position.x <= controlSize.width + Math::MACHINE_EPSILON_1000 ) &&
165 ( position.y - cursorSize.height >= -Math::MACHINE_EPSILON_1000 ) &&
166 ( position.y <= controlSize.height + Math::MACHINE_EPSILON_1000 );
170 * Splits a text in two halves.
172 * If the text's number of characters is odd, firstHalf has one more character.
174 * @param[in] text The text to be split.
175 * @param[out] firstHalf The first half of the text.
176 * @param[out] secondHalf The second half of the text.
178 void SplitText( const Toolkit::MarkupProcessor::StyledTextArray& text,
179 Toolkit::MarkupProcessor::StyledTextArray& firstHalf,
180 Toolkit::MarkupProcessor::StyledTextArray& secondHalf )
185 const std::size_t textLength = text.size();
186 const std::size_t half = ( textLength / 2 ) + ( textLength % 2 );
188 firstHalf.insert( firstHalf.end(), text.begin(), text.begin() + half );
189 secondHalf.insert( secondHalf.end(), text.begin() + half, text.end() );
192 } // end of namespace
200 const Property::Index TextInput::HIGHLIGHT_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX;
201 const Property::Index TextInput::CUT_AND_PASTE_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+1;
202 const Property::Index TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+2;
204 const Property::Index TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+3;
205 const Property::Index TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+4;
206 const Property::Index TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+5;
207 const Property::Index TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+6;
208 const Property::Index TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+7;
209 const Property::Index TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+8;
211 const Property::Index TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY = Internal::TextInput::TEXTINPUT_PROPERTY_START_INDEX+9;
221 return Toolkit::TextInput::New();
224 TypeRegistration typeRegistration( typeid(Toolkit::TextInput), typeid(Toolkit::Control), Create );
226 SignalConnectorType signalConnector1( typeRegistration, Toolkit::TextInput::SIGNAL_START_INPUT, &TextInput::DoConnectSignal );
227 SignalConnectorType signalConnector2( typeRegistration, Toolkit::TextInput::SIGNAL_END_INPUT, &TextInput::DoConnectSignal );
228 SignalConnectorType signalConnector3( typeRegistration, Toolkit::TextInput::SIGNAL_STYLE_CHANGED, &TextInput::DoConnectSignal );
229 SignalConnectorType signalConnector4( typeRegistration, Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED, &TextInput::DoConnectSignal );
230 SignalConnectorType signalConnector5( typeRegistration, Toolkit::TextInput::SIGNAL_TOOLBAR_DISPLAYED, &TextInput::DoConnectSignal );
231 SignalConnectorType signalConnector6( typeRegistration, Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES, &TextInput::DoConnectSignal );
235 PropertyRegistration property1( typeRegistration, "highlight-color", Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
236 PropertyRegistration property2( typeRegistration, "cut-and-paste-bg-color", Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
237 PropertyRegistration property3( typeRegistration, "cut-and-paste-pressed-color", Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY, Property::VECTOR4, &TextInput::SetProperty, &TextInput::GetProperty );
238 PropertyRegistration property4( typeRegistration, "cut-button-position-priority", Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
239 PropertyRegistration property5( typeRegistration, "copy-button-position-priority", Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
240 PropertyRegistration property6( typeRegistration, "paste-button-position-priority", Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
241 PropertyRegistration property7( typeRegistration, "select-button-position-priority", Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
242 PropertyRegistration property8( typeRegistration, "select-all-button-position-priority", Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
243 PropertyRegistration property9( typeRegistration, "clipboard-button-position-priority", Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY, Property::UNSIGNED_INTEGER, &TextInput::SetProperty, &TextInput::GetProperty );
246 // [TextInput::HighlightInfo] /////////////////////////////////////////////////
248 void TextInput::HighlightInfo::AddQuad( float x1, float y1, float x2, float y2 )
250 QuadCoordinates quad(x1, y1, x2, y2);
251 mQuadList.push_back( quad );
254 void TextInput::HighlightInfo::Clamp2D(const Vector2& min, const Vector2& max)
256 for(std::size_t i = 0;i < mQuadList.size(); i++)
258 QuadCoordinates& quad = mQuadList[i];
260 quad.min.Clamp(min, max);
261 quad.max.Clamp(min, max);
265 // [TextInput] ////////////////////////////////////////////////////////////////
267 Dali::Toolkit::TextInput TextInput::New()
269 // Create the implementation
270 TextInputPtr textInput(new TextInput());
271 // Pass ownership to CustomActor via derived handle
272 Dali::Toolkit::TextInput handle(*textInput);
274 textInput->Initialize();
279 TextInput::TextInput()
280 :Control( ControlBehaviour( REQUIRES_TOUCH_EVENTS | REQUIRES_STYLE_CHANGE_SIGNALS ) ),
285 mDisplayedTextView(),
286 mStyledPlaceHolderText(),
287 mMaxStringLength( DEFAULT_MAX_SIZE ),
288 mNumberOflinesLimit( DEFAULT_NUMBER_OF_LINES_LIMIT ),
289 mCursorPosition( 0 ),
290 mActualGrabHandlePosition( 0.0f, 0.0f, 0.0f ),
291 mIsSelectionHandleOneFlipped( false ),
292 mIsSelectionHandleTwoFlipped( false ),
293 mSelectionHandleOneOffset( DEFAULT_HANDLE_ONE_OFFSET ),
294 mSelectionHandleTwoOffset( DEFAULT_HANDLE_TWO_OFFSET ),
295 mSelectionHandleOneActualPosition( 0.0f, 0.0f , 0.0f ),
296 mSelectionHandleTwoActualPosition( 0.0f, 0.0f , 0.0f ),
297 mSelectionHandleOnePosition( 0 ),
298 mSelectionHandleTwoPosition( 0 ),
300 mPreEditStartPosition( 0 ),
301 mPreEditLength ( 0 ),
302 mNumberOfSurroundingCharactersDeleted( 0 ),
303 mTouchStartTime( 0 ),
305 mCurrentCopySelecton(),
308 mScrollDisplacement(),
309 mCurrentHandlePosition(),
310 mCurrentSelectionId(),
311 mCurrentSelectionHandlePosition(),
312 mRequestedSelection( 0, 0 ),
313 mSelectionHandleFlipMargin( 0.0f, 0.0f, 0.0f, 0.0f ),
314 mBoundingRectangleWorldCoordinates( 0.0f, 0.0f, 0.0f, 0.0f ),
316 mMaterialColor( LIGHTBLUE ),
317 mPopupOffsetFromText ( Vector4( 0.0f, TOP_HANDLE_TOP_OFFSET, 0.0f, BOTTOM_HANDLE_BOTTOM_OFFSET ) ),
318 mOverrideAutomaticAlignment( false ),
319 mCursorRTLEnabled( false ),
320 mClosestCursorPositionEOL ( false ),
321 mCursorBlinkStatus( true ),
322 mCursorVisibility( false ),
323 mGrabHandleVisibility( false ),
324 mIsCursorInScrollArea( true ),
325 mIsGrabHandleInScrollArea( true ),
326 mEditModeActive( false ),
327 mEditOnTouch( true ),
328 mTextSelection( true ),
329 mExceedEnabled( true ),
330 mGrabHandleEnabled( true ),
331 mIsSelectionHandleFlipEnabled( true ),
332 mPreEditFlag( false ),
333 mIgnoreCommitFlag( false ),
334 mIgnoreFirstCommitFlag( false ),
335 mSelectingText( false ),
336 mPreserveCursorPosition( false ),
337 mSelectTextOnCommit( false ),
338 mUnderlinedPriorToPreEdit ( false ),
339 mCommitByKeyInput( false ),
340 mPlaceHolderSet( false ),
341 mMarkUpEnabled( false )
343 // Updates the line height accordingly with the input style.
347 TextInput::~TextInput()
349 StopCursorBlinkTimer();
354 std::string TextInput::GetText() const
358 // Return text-view's text only if the text-input's text is not empty
359 // in order to not to return the placeholder text.
360 if( !mStyledText.empty() )
362 text = mDisplayedTextView.GetText();
368 std::string TextInput::GetMarkupText() const
370 std::string markupString;
371 MarkupProcessor::GetMarkupString( mStyledText, markupString );
376 void TextInput::SetPlaceholderText( const std::string& placeHolderText )
378 // Get the placeholder styled text array from the markup string.
379 MarkupProcessor::GetStyledTextArray( placeHolderText, mStyledPlaceHolderText, IsMarkupProcessingEnabled() );
381 if( mStyledText.empty() )
383 // Set the placeholder text only if the styled text is empty.
384 mDisplayedTextView.SetText( mStyledPlaceHolderText );
385 mPlaceHolderSet = true;
389 std::string TextInput::GetPlaceholderText()
391 // Traverses the styled placeholder array getting only the text.
392 // Note that for some languages a 'character' could be represented by more than one 'char'
394 std::string placeholderText;
395 for( MarkupProcessor::StyledTextArray::const_iterator it = mStyledPlaceHolderText.begin(), endIt = mStyledPlaceHolderText.end(); it != endIt; ++it )
397 placeholderText.append( (*it).mText.GetText() );
400 return placeholderText ;
403 void TextInput::SetInitialText(const std::string& initialText)
405 DALI_LOG_INFO(gLogFilter, Debug::General, "SetInitialText string[%s]\n", initialText.c_str() );
407 if ( mPreEditFlag ) // If in the pre-edit state and text is being set then discard text being inserted.
409 mPreEditFlag = false;
410 mIgnoreCommitFlag = true;
413 SetText( initialText );
414 PreEditReset( false ); // Reset keyboard as text changed
417 void TextInput::SetText(const std::string& initialText)
419 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText string[%s]\n", initialText.c_str() );
421 GetStyledTextArray( initialText, mStyledText, IsMarkupProcessingEnabled() );
423 if( mStyledText.empty() )
425 // If the initial text is empty, set the placeholder text.
426 mDisplayedTextView.SetText( mStyledPlaceHolderText );
427 mPlaceHolderSet = true;
431 mDisplayedTextView.SetText( mStyledText );
432 mPlaceHolderSet = false;
437 mCursorPosition = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
439 ImfManager imfManager = ImfManager::Get();
442 imfManager.SetCursorPosition( mCursorPosition );
443 imfManager.SetSurroundingText( initialText );
444 imfManager.NotifyCursorPosition();
447 if( IsScrollEnabled() )
449 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
452 ShowGrabHandleAndSetVisibility( false );
461 void TextInput::SetText( const MarkupProcessor::StyledTextArray& styleText )
463 DALI_LOG_INFO(gLogFilter, Debug::General, "SetText markup text\n" );
465 mDisplayedTextView.SetText( styleText );
466 mPlaceHolderSet = false;
468 // If text alignment hasn't been manually set by application developer, then we
469 // automatically determine the alignment based on the content of the text i.e. what
470 // language the text begins with.
471 // TODO: This should determine different alignments for each line (broken by '\n') of text.
472 if(!mOverrideAutomaticAlignment)
474 // Determine bidi direction of first character (skipping past whitespace, numbers, and symbols)
475 bool leftToRight(true);
477 if( !styleText.empty() )
479 bool breakOut(false);
481 for( MarkupProcessor::StyledTextArray::const_iterator textIter = styleText.begin(), textEndIter = styleText.end(); ( textIter != textEndIter ) && ( !breakOut ); ++textIter )
483 const Text& text = textIter->mText;
485 for( std::size_t i = 0; i < text.GetLength(); ++i )
487 Character character( text[i] );
488 if( character.GetCharacterDirection() != Character::Neutral )
490 leftToRight = ( character.GetCharacterDirection() == Character::LeftToRight );
498 // Based on this direction, either left or right align text if not manually set by application developer.
499 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(
500 ( leftToRight ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight) |
501 Toolkit::Alignment::VerticalTop ) );
502 mDisplayedTextView.SetLineJustification( leftToRight ? Toolkit::TextView::Left : Toolkit::TextView::Right);
508 void TextInput::SetMaxCharacterLength(std::size_t maxChars)
510 mMaxStringLength = maxChars;
513 void TextInput::SetNumberOfLinesLimit(std::size_t maxLines)
515 DALI_ASSERT_DEBUG( maxLines > 0 )
519 mNumberOflinesLimit = maxLines;
523 std::size_t TextInput::GetNumberOfLinesLimit() const
525 return mNumberOflinesLimit;
528 std::size_t TextInput::GetNumberOfCharacters() const
530 return mStyledText.size();
534 void TextInput::SetMaterialDiffuseColor( const Vector4& color )
536 mMaterialColor = color;
537 if ( mCustomMaterial )
539 mCustomMaterial.SetDiffuseColor( mMaterialColor );
540 mMeshData.SetMaterial( mCustomMaterial );
544 const Vector4& TextInput::GetMaterialDiffuseColor() const
546 return mMaterialColor;
551 Toolkit::TextInput::InputSignalV2& TextInput::InputStartedSignal()
553 return mInputStartedSignalV2;
556 Toolkit::TextInput::InputSignalV2& TextInput::InputFinishedSignal()
558 return mInputFinishedSignalV2;
561 Toolkit::TextInput::InputSignalV2& TextInput::CutAndPasteToolBarDisplayedSignal()
563 return mCutAndPasteToolBarDisplayedV2;
566 Toolkit::TextInput::StyleChangedSignalV2& TextInput::StyleChangedSignal()
568 return mStyleChangedSignalV2;
571 Toolkit::TextInput::TextModifiedSignalType& TextInput::TextModifiedSignal()
573 return mTextModifiedSignal;
576 Toolkit::TextInput::MaxInputCharactersReachedSignalV2& TextInput::MaxInputCharactersReachedSignal()
578 return mMaxInputCharactersReachedSignalV2;
581 Toolkit::TextInput::InputTextExceedBoundariesSignalV2& TextInput::InputTextExceedBoundariesSignal()
583 return mInputTextExceedBoundariesSignalV2;
586 bool TextInput::DoConnectSignal( BaseObject* object, ConnectionTrackerInterface* tracker, const std::string& signalName, FunctorDelegate* functor )
588 Dali::BaseHandle handle( object );
590 bool connected( true );
591 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast(handle);
593 if( Toolkit::TextInput::SIGNAL_START_INPUT == signalName )
595 textInput.InputStartedSignal().Connect( tracker, functor );
597 else if( Toolkit::TextInput::SIGNAL_END_INPUT == signalName )
599 textInput.InputFinishedSignal().Connect( tracker, functor );
601 else if( Toolkit::TextInput::SIGNAL_STYLE_CHANGED == signalName )
603 textInput.StyleChangedSignal().Connect( tracker, functor );
605 else if( Toolkit::TextInput::SIGNAL_MAX_INPUT_CHARACTERS_REACHED == signalName )
607 textInput.MaxInputCharactersReachedSignal().Connect( tracker, functor );
609 else if( Toolkit::TextInput::SIGNAL_TEXT_EXCEED_BOUNDARIES == signalName )
611 textInput.InputTextExceedBoundariesSignal().Connect( tracker, functor );
615 // signalName does not match any signal
622 void TextInput::SetEditable(bool editMode, bool setCursorOnTouchPoint, const Vector2& touchPoint)
626 // update line height before calculate the actual position.
631 if( setCursorOnTouchPoint )
633 // Sets the cursor position for the given touch point.
634 ReturnClosestIndex( touchPoint, mCursorPosition );
636 // Creates the grab handle.
637 if( IsGrabHandleEnabled() )
639 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
643 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
644 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
645 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
646 ShowGrabHandleAndSetVisibility( true );
648 // Scrolls the text-view if needed.
649 if( IsScrollEnabled() )
651 ScrollTextViewToMakeCursorVisible( cursorPosition );
657 mCursorPosition = mStyledText.size(); // Initially set cursor position to end of string.
669 bool TextInput::IsEditable() const
671 return mEditModeActive;
674 void TextInput::SetEditOnTouch( bool editOnTouch )
676 mEditOnTouch = editOnTouch;
679 bool TextInput::IsEditOnTouch() const
684 void TextInput::SetTextSelectable( bool textSelectable )
686 mTextSelection = textSelectable;
689 bool TextInput::IsTextSelectable() const
691 return mTextSelection;
694 bool TextInput::IsTextSelected() const
696 return mHighlightMeshActor;
699 void TextInput::DeSelectText()
706 void TextInput::SetGrabHandleImage(Dali::Image image )
710 CreateGrabHandle(image);
714 void TextInput::SetCursorImage(Dali::Image image, const Vector4& border )
716 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
720 mCursor.SetImage( image );
721 mCursor.SetNinePatchBorder( border );
725 Vector3 TextInput::GetSelectionHandleSize()
727 return DEFAULT_SELECTION_HANDLE_SIZE;
730 void TextInput::SetRTLCursorImage(Dali::Image image, const Vector4& border )
732 DALI_ASSERT_DEBUG ( image && "Create cursor image invalid")
736 mCursorRTL.SetImage( image);
737 mCursorRTL.SetNinePatchBorder( border );
741 void TextInput::EnableGrabHandle(bool toggle)
743 // enables grab handle with will in turn de-activate magnifier
744 mGrabHandleEnabled = toggle;
747 bool TextInput::IsGrabHandleEnabled()
749 // if false then magnifier will be shown instead.
750 return mGrabHandleEnabled;
753 void TextInput::EnableSelectionHandleFlip( bool toggle )
755 // Deprecated function. To be removed.
756 mIsSelectionHandleFlipEnabled = toggle;
759 bool TextInput::IsSelectionHandleFlipEnabled()
761 // Deprecated function, To be removed. Returns true as handle flipping always enabled by default so handles do not exceed screen.
765 void TextInput::SetSelectionHandleFlipMargin( const Vector4& margin )
767 // Deprecated function, now just stores margin for retreival, remove completely once depricated Public API removed.
768 Vector3 textInputSize = mDisplayedTextView.GetCurrentSize();
769 const Vector4 flipBoundary( -margin.x, -margin.y, textInputSize.width + margin.z, textInputSize.height + margin.w );
771 mSelectionHandleFlipMargin = margin;
774 void TextInput::SetBoundingRectangle( const Rect<float>& boundingRectangle )
776 // Convert to world coordinates and store as a Vector4 to be compatiable with Property Notifications.
777 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
779 const float originX = boundingRectangle.x - 0.5f * stageSize.width;
780 const float originY = boundingRectangle.y - 0.5f * stageSize.height;
782 const Vector4 boundary( originX,
784 originX + boundingRectangle.width,
785 originY + boundingRectangle.height );
787 mBoundingRectangleWorldCoordinates = boundary;
789 // Set Boundary for Popup so it keeps the Pop-up within the area also.
790 mPopUpPanel.SetPopupBoundary( boundingRectangle );
793 const Rect<float> TextInput::GetBoundingRectangle() const
795 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
797 const float originX = mBoundingRectangleWorldCoordinates.x + 0.5f * stageSize.width;
798 const float originY = mBoundingRectangleWorldCoordinates.y + 0.5f * stageSize.height;
800 Rect<float>boundingRect( originX, originY, mBoundingRectangleWorldCoordinates.z - mBoundingRectangleWorldCoordinates.x, mBoundingRectangleWorldCoordinates.w - mBoundingRectangleWorldCoordinates.y);
805 const Vector4& TextInput::GetSelectionHandleFlipMargin()
807 return mSelectionHandleFlipMargin;
810 void TextInput::SetTextColor( const Vector4& color )
812 mDisplayedTextView.SetColor( color );
815 void TextInput::SetActiveStyle( const TextStyle& style, const TextStyle::Mask mask )
817 if( style != mInputStyle )
820 bool emitSignal = false;
822 // mask: modify style according to mask, if different emit signal.
823 const TextStyle oldInputStyle( mInputStyle );
825 // Copy the new style.
826 mInputStyle.Copy( style, mask );
828 // if style has changed, emit signal.
829 if( oldInputStyle != mInputStyle )
834 // Updates the line height accordingly with the input style.
837 // Changing font point size will require the cursor to be re-sized
842 EmitStyleChangedSignal();
847 void TextInput::ApplyStyle( const TextStyle& style, const TextStyle::Mask mask )
849 if ( IsTextSelected() )
851 const std::size_t begin = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
852 const std::size_t end = std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition) - 1;
854 if( !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
856 ApplyStyleToRange(style, mask, mTextLayoutInfo.mCharacterLogicalToVisualMap[begin], mTextLayoutInfo.mCharacterLogicalToVisualMap[end]);
859 // Keeps the old style to be compared with the new one.
860 const TextStyle oldInputStyle( mInputStyle );
862 // Copy only those parameters from the style which are set in the mask.
863 mInputStyle.Copy( style, mask );
865 if( mInputStyle != oldInputStyle )
867 // Updates the line height accordingly with the input style.
870 EmitStyleChangedSignal();
875 void TextInput::ApplyStyleToAll( const TextStyle& style, const TextStyle::Mask mask )
877 if( !mStyledText.empty() )
879 ApplyStyleToRange( style, mask, 0, mStyledText.size() - 1 );
883 TextStyle TextInput::GetStyleAtCursor() const
887 if ( !mStyledText.empty() && ( mCursorPosition > 0 ) )
889 DALI_ASSERT_DEBUG( ( 0 <= mCursorPosition-1 ) && ( mCursorPosition-1 < mStyledText.size() ) );
890 style = mStyledText.at( mCursorPosition-1 ).mStyle;
896 if ( mInputStyle.GetFontPointSize() < Math::MACHINE_EPSILON_1000 )
898 Dali::Font defaultFont = Dali::Font::New();
899 style.SetFontPointSize( PointSize( defaultFont.GetPointSize()) );
906 TextStyle TextInput::GetStyleAt( std::size_t position ) const
908 DALI_ASSERT_DEBUG( ( 0 <= position ) && ( position <= mStyledText.size() ) );
910 if( position >= mStyledText.size() )
912 position = mStyledText.size() - 1;
915 return mStyledText.at( position ).mStyle;
918 void TextInput::SetTextAlignment( Toolkit::Alignment::Type align )
920 mDisplayedTextView.SetTextAlignment( align );
921 mOverrideAutomaticAlignment = true;
924 void TextInput::SetTextLineJustification( Toolkit::TextView::LineJustification justification )
926 mDisplayedTextView.SetLineJustification( justification );
927 mOverrideAutomaticAlignment = true;
930 void TextInput::SetFadeBoundary( const Toolkit::TextView::FadeBoundary& fadeBoundary )
932 mDisplayedTextView.SetFadeBoundary( fadeBoundary );
935 const Toolkit::TextView::FadeBoundary& TextInput::GetFadeBoundary() const
937 return mDisplayedTextView.GetFadeBoundary();
940 Toolkit::Alignment::Type TextInput::GetTextAlignment() const
942 return mDisplayedTextView.GetTextAlignment();
945 void TextInput::SetMultilinePolicy( Toolkit::TextView::MultilinePolicy policy )
947 mDisplayedTextView.SetMultilinePolicy( policy );
950 Toolkit::TextView::MultilinePolicy TextInput::GetMultilinePolicy() const
952 return mDisplayedTextView.GetMultilinePolicy();
955 void TextInput::SetWidthExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
957 mDisplayedTextView.SetWidthExceedPolicy( policy );
960 Toolkit::TextView::ExceedPolicy TextInput::GetWidthExceedPolicy() const
962 return mDisplayedTextView.GetWidthExceedPolicy();
965 void TextInput::SetHeightExceedPolicy( Toolkit::TextView::ExceedPolicy policy )
967 mDisplayedTextView.SetHeightExceedPolicy( policy );
970 Toolkit::TextView::ExceedPolicy TextInput::GetHeightExceedPolicy() const
972 return mDisplayedTextView.GetHeightExceedPolicy();
975 void TextInput::SetExceedEnabled( bool enable )
977 mExceedEnabled = enable;
980 bool TextInput::GetExceedEnabled() const
982 return mExceedEnabled;
985 void TextInput::SetBackground(Dali::Image image )
987 // TODO Should add this function and add public api to match.
990 bool TextInput::OnTouchEvent(const TouchEvent& event)
995 bool TextInput::OnKeyEvent(const KeyEvent& event)
997 switch( event.state )
1001 return OnKeyDownEvent(event);
1007 return OnKeyUpEvent(event);
1019 void TextInput::OnKeyInputFocusGained()
1021 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusGained\n" );
1023 mEditModeActive = true;
1025 mActiveLayer.RaiseToTop(); // Ensure layer holding handles is on top
1027 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1029 // Updates the line height accordingly with the input style.
1032 // Connect the signals to use in text input.
1033 VirtualKeyboard::StatusChangedSignal().Connect( this, &TextInput::KeyboardStatusChanged );
1034 VirtualKeyboard::LanguageChangedSignal().Connect( this, &TextInput::SetTextDirection );
1036 // Set the text direction if empty and connect to the signal to ensure we change direction when the language changes.
1039 GetTextLayoutInfo();
1042 SetCursorVisibility( true );
1043 StartCursorBlinkTimer();
1045 Toolkit::TextInput handle( GetOwner() );
1046 mInputStartedSignalV2.Emit( handle );
1048 ImfManager imfManager = ImfManager::Get();
1052 imfManager.EventReceivedSignal().Connect(this, &TextInput::ImfEventReceived);
1054 // Notify that the text editing start.
1055 imfManager.Activate();
1057 // When window gain lost focus, the imf manager is deactivated. Thus when window gain focus again, the imf manager must be activated.
1058 imfManager.SetRestoreAferFocusLost( true );
1060 imfManager.SetCursorPosition( mCursorPosition );
1061 imfManager.NotifyCursorPosition();
1064 mClipboard = Clipboard::Get(); // Store handle to clipboard
1066 // Now in edit mode we can accept string to paste from clipboard
1067 if( Adaptor::IsAvailable() )
1069 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1072 notifier.ContentSelectedSignal().Connect( this, &TextInput::OnClipboardTextSelected );
1077 void TextInput::OnKeyInputFocusLost()
1079 DALI_LOG_INFO(gLogFilter, Debug::General, ">>OnKeyInputFocusLost\n" );
1083 // If key input focus is lost, it removes the
1084 // underline from the last pre-edit text.
1085 RemovePreEditStyle();
1086 const std::size_t numberOfCharactersDeleted = DeletePreEdit();
1087 InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersDeleted );
1091 ImfManager imfManager = ImfManager::Get();
1094 // The text editing is finished. Therefore the imf manager don't have restore activation.
1095 imfManager.SetRestoreAferFocusLost( false );
1097 // Notify that the text editing finish.
1098 imfManager.Deactivate();
1100 imfManager.EventReceivedSignal().Disconnect(this, &TextInput::ImfEventReceived);
1102 // Disconnect signal used the text input.
1103 VirtualKeyboard::LanguageChangedSignal().Disconnect( this, &TextInput::SetTextDirection );
1105 Toolkit::TextInput handle( GetOwner() );
1106 mInputFinishedSignalV2.Emit( handle );
1107 mEditModeActive = false;
1108 mPreEditFlag = false;
1110 SetCursorVisibility( false );
1111 StopCursorBlinkTimer();
1113 ShowGrabHandleAndSetVisibility( false );
1116 // No longer in edit mode so do not want to receive string from clipboard
1117 if( Adaptor::IsAvailable() )
1119 ClipboardEventNotifier notifier( ClipboardEventNotifier::Get() );
1122 notifier.ContentSelectedSignal().Disconnect( this, &TextInput::OnClipboardTextSelected );
1124 Clipboard clipboard = Clipboard::Get();
1128 clipboard.HideClipboard();
1133 void TextInput::OnControlStageConnection()
1135 Vector2 stageSize = Dali::Stage::GetCurrent().GetSize();
1137 if ( mBoundingRectangleWorldCoordinates == Vector4::ZERO )
1139 SetBoundingRectangle( Rect<float>( 0.0f, 0.0f, stageSize.width, stageSize.height ));
1143 void TextInput::CreateActiveLayer()
1145 Actor self = Self();
1146 mActiveLayer = Layer::New();
1148 mActiveLayer.SetAnchorPoint( AnchorPoint::CENTER);
1149 mActiveLayer.SetParentOrigin( ParentOrigin::CENTER);
1150 mActiveLayer.SetPositionInheritanceMode( USE_PARENT_POSITION );
1152 self.Add( mActiveLayer );
1153 mActiveLayer.RaiseToTop();
1156 void TextInput::OnInitialize()
1158 CreateTextViewActor();
1162 // Create 2 cursors (standard LTR and RTL cursor for when text can be added at
1163 // different positions depending on language)
1164 Image mCursorImage = Image::New( DEFAULT_CURSOR );
1165 mCursor = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1166 mCursorRTL = CreateCursor( mCursorImage, DEFAULT_CURSOR_IMAGE_9_BORDER );
1168 Actor self = Self();
1169 self.Add( mCursor );
1170 self.Add( mCursorRTL );
1172 mCursorVisibility = false;
1174 CreateActiveLayer(); // todo move this so layer only created when needed.
1176 // Assign names to image actors
1177 mCursor.SetName("mainCursor");
1178 mCursorRTL.SetName("rtlCursor");
1181 void TextInput::OnControlSizeSet(const Vector3& targetSize)
1183 mDisplayedTextView.SetSize( targetSize );
1184 GetTextLayoutInfo();
1185 mActiveLayer.SetSize(targetSize);
1188 void TextInput::OnRelaidOut( Vector2 size, ActorSizeContainer& container )
1190 Relayout( mDisplayedTextView, size, container );
1191 GetTextLayoutInfo();
1196 Vector3 TextInput::GetNaturalSize()
1198 Vector3 naturalSize = mDisplayedTextView.GetNaturalSize();
1200 if( mEditModeActive && ( Vector3::ZERO == naturalSize ) )
1202 // If the natural is zero, it means there is no text. Let's return the cursor height as the natural height.
1203 naturalSize.height = mLineHeight;
1209 float TextInput::GetHeightForWidth( float width )
1211 float height = mDisplayedTextView.GetHeightForWidth( width );
1213 if( mEditModeActive && ( fabsf( height ) < Math::MACHINE_EPSILON_1000 ) )
1215 // If the height is zero, it means there is no text. Let's return the cursor height.
1216 height = mLineHeight;
1222 /*end of Virtual methods from parent*/
1224 // Private Internal methods
1226 void TextInput::OnHandlePan(Actor actor, PanGesture gesture)
1228 switch (gesture.state)
1230 case Gesture::Started:
1231 // fall through so code not duplicated
1232 case Gesture::Continuing:
1234 if (actor == mGrabArea)
1236 SetCursorVisibility( true );
1237 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1238 MoveGrabHandle( gesture.displacement );
1239 HidePopup(); // Do not show popup whilst handle is moving
1241 else if (actor == mHandleOneGrabArea)
1243 // the displacement in PanGesture is affected by the actor's rotation.
1244 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1245 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1247 MoveSelectionHandle( HandleOne, gesture.displacement );
1249 mState = StateDraggingHandle;
1252 else if (actor == mHandleTwoGrabArea)
1254 // the displacement in PanGesture is affected by the actor's rotation.
1255 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1256 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1258 MoveSelectionHandle( HandleTwo, gesture.displacement );
1260 mState = StateDraggingHandle;
1266 case Gesture::Finished:
1268 // Revert back to non-pressed selection handle images
1269 if (actor == mGrabArea)
1271 mActualGrabHandlePosition = MoveGrabHandle( gesture.displacement );
1272 SetCursorVisibility( true );
1273 SetUpPopUpSelection();
1276 if (actor == mHandleOneGrabArea)
1278 // the displacement in PanGesture is affected by the actor's rotation.
1279 mSelectionHandleOneActualPosition.x += gesture.displacement.x * mSelectionHandleOne.GetCurrentScale().x;
1280 mSelectionHandleOneActualPosition.y += gesture.displacement.y * mSelectionHandleOne.GetCurrentScale().y;
1282 mSelectionHandleOneActualPosition = MoveSelectionHandle( HandleOne, gesture.displacement );
1284 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1286 ShowPopupCutCopyPaste();
1288 if (actor == mHandleTwoGrabArea)
1290 // the displacement in PanGesture is affected by the actor's rotation.
1291 mSelectionHandleTwoActualPosition.x += gesture.displacement.x * mSelectionHandleTwo.GetCurrentScale().x;
1292 mSelectionHandleTwoActualPosition.y += gesture.displacement.y * mSelectionHandleTwo.GetCurrentScale().y;
1294 mSelectionHandleTwoActualPosition = MoveSelectionHandle( HandleTwo, gesture.displacement );
1296 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1298 ShowPopupCutCopyPaste();
1307 // Stop the flashing animation so easy to see when moved.
1308 bool TextInput::OnPressDown(Dali::Actor actor, const TouchEvent& touch)
1310 if (touch.GetPoint(0).state == TouchPoint::Down)
1312 SetCursorVisibility( true );
1313 StopCursorBlinkTimer();
1315 else if (touch.GetPoint(0).state == TouchPoint::Up)
1317 SetCursorVisibility( true );
1318 StartCursorBlinkTimer();
1323 // selection handle one
1324 bool TextInput::OnHandleOneTouched(Dali::Actor actor, const TouchEvent& touch)
1326 if (touch.GetPoint(0).state == TouchPoint::Down)
1328 mSelectionHandleOne.SetImage( mSelectionHandleOneImagePressed );
1330 else if (touch.GetPoint(0).state == TouchPoint::Up)
1332 mSelectionHandleOne.SetImage( mSelectionHandleOneImage );
1337 // selection handle two
1338 bool TextInput::OnHandleTwoTouched(Dali::Actor actor, const TouchEvent& touch)
1340 if (touch.GetPoint(0).state == TouchPoint::Down)
1342 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImagePressed );
1344 else if (touch.GetPoint(0).state == TouchPoint::Up)
1346 mSelectionHandleTwo.SetImage( mSelectionHandleTwoImage );
1351 void TextInput::OnDoubleTap(Dali::Actor actor, Dali::TapGesture tap)
1353 // If text exists then select nearest word.
1354 if ( !mStyledText.empty())
1358 ShowGrabHandleAndSetVisibility( false );
1363 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1364 // converts the pre-edit word being displayed to a committed word.
1365 if ( !mUnderlinedPriorToPreEdit )
1368 style.SetUnderline( false );
1369 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1371 mPreEditFlag = false;
1372 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1373 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1374 PreEditReset( false );
1376 mCursorPosition = 0;
1378 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1379 ReturnClosestIndex( tap.localPoint, mCursorPosition );
1381 ImfManager imfManager = ImfManager::Get();
1384 imfManager.SetCursorPosition ( mCursorPosition );
1385 imfManager.NotifyCursorPosition();
1388 std::size_t start = 0;
1389 std::size_t end = 0;
1390 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1392 SelectText( start, end );
1394 // if no text but clipboard has content then show paste option
1395 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1397 ShowPopupCutCopyPaste();
1400 // If no text and clipboard empty then do nothing
1403 // TODO: Change the function name to be more general.
1404 void TextInput::OnTextTap(Dali::Actor actor, Dali::TapGesture tap)
1406 DALI_LOG_INFO( gLogFilter, Debug::General, "OnTap mPreEditFlag[%s] mEditOnTouch[%s] mEditModeActive[%s] ", (mPreEditFlag)?"true":"false"
1407 , (mEditOnTouch)?"true":"false"
1408 , (mEditModeActive)?"true":"false");
1410 if( mHandleOneGrabArea == actor || mHandleTwoGrabArea == actor )
1415 if( mGrabArea == actor )
1417 if( mPopUpPanel.GetState() == TextInputPopup::StateHidden || mPopUpPanel.GetState() == TextInputPopup::StateHiding )
1419 SetUpPopUpSelection();
1429 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1431 // Initially don't create the grab handle.
1432 bool createGrabHandle = false;
1434 if ( !mEditModeActive )
1436 // update line height before calculate the actual position.
1439 // Only start edit mode if TextInput configured to edit on touch
1442 // Set the initial cursor position in the tap point.
1443 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1445 // Create the grab handle.
1446 // TODO Make this a re-usable function.
1447 if ( IsGrabHandleEnabled() )
1449 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1453 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1454 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1455 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1456 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1460 // Edit mode started after grab handle created to ensure the signal InputStarted is sent last.
1461 // This is used to ensure if selecting text hides the grab handle then this code is run after grab handle is created,
1462 // otherwise the Grab handle will be shown when selecting.
1469 // Show the keyboard if it was hidden.
1470 if (!VirtualKeyboard::IsVisible())
1472 VirtualKeyboard::Show();
1475 // Reset keyboard as tap event has occurred.
1476 // Set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1477 PreEditReset( true );
1479 GetTextLayoutInfo();
1481 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() ) // If string empty we do not need a grab handle.
1483 // 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.
1485 ReturnClosestIndex(tap.localPoint, mCursorPosition );
1487 DALI_LOG_INFO( gLogFilter, Debug::General, "mCursorPosition[%u]", mCursorPosition );
1489 // Notify keyboard so it can 're-capture' word for predictive text.
1490 // As we have done a reset, is this required, expect IMF keyboard to request this information.
1491 ImfManager imfManager = ImfManager::Get();
1494 imfManager.SetCursorPosition ( mCursorPosition );
1495 imfManager.NotifyCursorPosition();
1497 const TextStyle oldInputStyle( mInputStyle );
1499 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
1503 // Create the grab handle.
1504 // Grab handle is created later.
1505 createGrabHandle = true;
1507 if( oldInputStyle != mInputStyle )
1509 // Updates the line height accordingly with the input style.
1512 EmitStyleChangedSignal();
1517 if ( createGrabHandle && IsGrabHandleEnabled() )
1519 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1523 mActualGrabHandlePosition.x = cursorPosition.x; // Set grab handle to be at the cursor position
1524 mActualGrabHandlePosition.y = cursorPosition.y; // Set grab handle to be at the cursor position
1525 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1526 ShowGrabHandleAndSetVisibility( mIsGrabHandleInScrollArea );
1531 void TextInput::OnLongPress(Dali::Actor actor, Dali::LongPressGesture longPress)
1533 DALI_LOG_INFO( gLogFilter, Debug::General, "OnLongPress\n" );
1535 if(longPress.state == Dali::Gesture::Started)
1537 // Start edit mode on long press
1538 if ( !mEditModeActive )
1543 // If text exists then select nearest word.
1544 if ( !mStyledText.empty())
1548 ShowGrabHandleAndSetVisibility( false );
1553 // PreEdit will be committed here without needing a commit from IMF. Remove pre-edit underline and reset flags which
1554 // converts the pre-edit word being displayed to a committed word.
1555 if ( !mUnderlinedPriorToPreEdit )
1558 style.SetUnderline( false );
1559 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
1561 mPreEditFlag = false;
1562 mIgnoreCommitFlag = true; // Predictive word interrupted, text displayed will not change, no need to actually commit.
1563 // Reset keyboard and set true so cursor position is preserved. Otherwise cursor position will that of the committed text not new tap location.
1564 PreEditReset( false );
1566 mCursorPosition = 0;
1568 mTextLayoutInfo.mScrollOffset = mDisplayedTextView.GetScrollPosition();
1569 ReturnClosestIndex( longPress.localPoint, mCursorPosition );
1571 ImfManager imfManager = ImfManager::Get();
1574 imfManager.SetCursorPosition ( mCursorPosition );
1575 imfManager.NotifyCursorPosition();
1577 std::size_t start = 0;
1578 std::size_t end = 0;
1579 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1581 SelectText( start, end );
1584 // if no text but clipboard has content then show paste option, if no text and clipboard empty then do nothing
1585 if ( ( mClipboard && mClipboard.NumberOfItems() ) || !mStyledText.empty() )
1587 ShowPopupCutCopyPaste();
1592 void TextInput::OnClipboardTextSelected( ClipboardEventNotifier& notifier )
1594 const Text clipboardText( notifier.GetContent() );
1595 PasteText( clipboardText );
1597 SetCursorVisibility( true );
1598 StartCursorBlinkTimer();
1600 ShowGrabHandleAndSetVisibility( false );
1606 bool TextInput::OnPopupButtonPressed( Toolkit::Button button )
1608 mPopUpPanel.PressedSignal().Disconnect( this, &TextInput::OnPopupButtonPressed );
1610 const std::string& name = button.GetName();
1612 if(name == TextInputPopup::OPTION_SELECT_WORD)
1614 std::size_t start = 0;
1615 std::size_t end = 0;
1616 Dali::Toolkit::Internal::TextProcessor::FindNearestWord( mStyledText, mCursorPosition, start, end );
1618 SelectText( start, end );
1620 else if(name == TextInputPopup::OPTION_SELECT_ALL)
1622 SetCursorVisibility(false);
1623 StopCursorBlinkTimer();
1625 std::size_t end = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
1626 std::size_t start = 0;
1628 SelectText( start, end );
1630 else if(name == TextInputPopup::OPTION_CUT)
1632 bool ret = CopySelectedTextToClipboard();
1636 DeleteHighlightedText( true );
1640 SetCursorVisibility( true );
1641 StartCursorBlinkTimer();
1645 else if(name == TextInputPopup::OPTION_COPY)
1647 CopySelectedTextToClipboard();
1651 SetCursorVisibility( true );
1652 StartCursorBlinkTimer();
1656 else if(name == TextInputPopup::OPTION_PASTE)
1658 const Text retrievedString( mClipboard.GetItem( 0 ) ); // currently can only get first item in clip board, index 0;
1660 PasteText(retrievedString);
1662 SetCursorVisibility( true );
1663 StartCursorBlinkTimer();
1665 ShowGrabHandleAndSetVisibility( false );
1669 else if(name == TextInputPopup::OPTION_CLIPBOARD)
1671 // In the case of clipboard being shown we do not want to show updated pop-up after hide animation completes
1672 // Hence pass the false parameter for signalFinished.
1673 HidePopup( true, false );
1674 mClipboard.ShowClipboard();
1680 bool TextInput::OnCursorBlinkTimerTick()
1683 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1684 if ( mCursorRTLEnabled )
1686 mCursorRTL.SetVisible( mCursorVisibility && mIsCursorInScrollArea && mCursorBlinkStatus );
1688 mCursorBlinkStatus = !mCursorBlinkStatus;
1693 void TextInput::OnPopupHideFinished(TextInputPopup& popup)
1695 popup.HideFinishedSignal().Disconnect( this, &TextInput::OnPopupHideFinished );
1697 // Change Popup menu to Cut/Copy/Paste if text has been selected.
1698 if(mHighlightMeshActor && mState == StateEdit)
1700 ShowPopupCutCopyPaste();
1704 //FIXME this routine needs to be re-written as it contains too many branches.
1705 bool TextInput::OnKeyDownEvent(const KeyEvent& event)
1707 std::string keyName = event.keyPressedName;
1708 std::string keyString = event.keyPressed;
1710 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyDownEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1712 // Do not consume "Tab" and "Escape" keys.
1713 if(keyName == "Tab" || keyName == "Escape")
1715 // Escape key to end the edit mode
1721 HidePopup(); // If Pop-up shown then hides it as editing text.
1723 // Update Flag, indicates whether to update the text-input contents or not.
1724 // Any key stroke that results in a visual change of the text-input should
1725 // set this flag to true.
1728 // Whether to scroll text to cursor position.
1729 // Scroll is needed always the cursor is updated and after the pre-edit is received.
1730 bool scroll = false;
1732 if (keyName == "Return")
1734 if ( mNumberOflinesLimit > 1) // Prevents New line character / Return adding an extra line if limit set to 1
1736 bool preEditFlagPreviouslySet( mPreEditFlag );
1738 if (mHighlightMeshActor)
1740 // replaces highlighted text with new line
1741 DeleteHighlightedText( false );
1743 mCursorPosition = mCursorPosition + InsertAt( Text( NEWLINE ), mCursorPosition, 0 );
1745 // If we are in pre-edit mode then pressing enter will cause a commit. But the commit string does not include the
1746 // '\n' character so we need to ensure that the immediately following commit knows how it occurred.
1749 mCommitByKeyInput = true;
1752 // If attempting to insert a new-line brings us out of PreEdit mode, then we should not ignore the next commit.
1753 if ( preEditFlagPreviouslySet && !mPreEditFlag )
1755 mPreEditFlag = true;
1756 mIgnoreCommitFlag = false;
1766 else if ( keyName == "space" )
1768 if ( mHighlightMeshActor )
1770 // Some text is selected so erase it before adding space.
1771 DeleteHighlightedText( true );
1775 mCursorPosition = mCursorPosition + InsertAt(Text(keyString), mCursorPosition, 0);
1777 // If we are in pre-edit mode then pressing the space-bar will cause a commit. But the commit string does not include the
1778 // ' ' character so we need to ensure that the immediately following commit knows how it occurred.
1781 mCommitByKeyInput = true;
1786 else if (keyName == "BackSpace")
1788 if ( mHighlightMeshActor )
1790 // Some text is selected so erase it
1791 DeleteHighlightedText( true );
1796 if ( mCursorPosition > 0 )
1798 DeleteCharacter( mCursorPosition );
1804 else if (keyName == "Right")
1809 else if (keyName == "Left")
1811 AdvanceCursor(true);
1814 else // event is a character
1816 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
1817 if ( !keyString.empty() )
1819 if ( mHighlightMeshActor )
1821 // replaces highlighted text with new character
1822 DeleteHighlightedText( false );
1826 // Received key String
1827 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, 0 );
1833 // If key event has resulted in a change in the text/cursor, then trigger a relayout of text
1834 // as this is a costly operation.
1840 if(update || scroll)
1842 if( IsScrollEnabled() )
1844 // Calculates the new cursor position (in actor coordinates)
1845 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
1847 ScrollTextViewToMakeCursorVisible( cursorPosition );
1854 bool TextInput::OnKeyUpEvent(const KeyEvent& event)
1856 std::string keyName = event.keyPressedName;
1857 std::string keyString = event.keyPressed;
1859 DALI_LOG_INFO(gLogFilter, Debug::General, "OnKeyUpEvent keyName[%s] KeyString[%s]\n", keyName.c_str(), keyString.c_str() );
1861 // The selected text become deselected when the key code is DALI_KEY_BACK.
1862 if( IsTextSelected() && ( keyName == "XF86Stop" || keyName == "XF86Send") )
1871 void TextInput::OnTextViewScrolled( Toolkit::TextView textView, Vector2 scrollPosition )
1873 // Updates the stored scroll position.
1874 mTextLayoutInfo.mScrollOffset = textView.GetScrollPosition();
1876 const Vector3& controlSize = GetControlSize();
1877 Size cursorSize( CURSOR_THICKNESS, 0.f );
1879 // Updates the cursor and grab handle position and visibility.
1880 if( mGrabHandle || mCursor )
1882 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
1883 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition(mCursorPosition);
1885 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( cursorPosition, cursorSize, controlSize );
1887 mActualGrabHandlePosition = cursorPosition.GetVectorXY();
1891 ShowGrabHandle( mGrabHandleVisibility && mIsGrabHandleInScrollArea );
1892 mGrabHandle.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1897 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
1898 mCursor.SetPosition( mActualGrabHandlePosition + UI_OFFSET );
1902 // Updates the selection handles and highlighted text position and visibility.
1903 if( mSelectionHandleOne && mSelectionHandleTwo )
1905 const Vector3 cursorPositionOne = GetActualPositionFromCharacterPosition(mSelectionHandleOnePosition);
1906 const Vector3 cursorPositionTwo = GetActualPositionFromCharacterPosition(mSelectionHandleTwoPosition);
1907 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleOnePosition ) ).mSize.height;
1908 const bool isSelectionHandleOneVisible = IsPositionInsideBoundaries( cursorPositionOne, cursorSize, controlSize );
1909 cursorSize.height = ( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + mSelectionHandleTwoPosition ) ).mSize.height;
1910 const bool isSelectionHandleTwoVisible = IsPositionInsideBoundaries( cursorPositionTwo, cursorSize, controlSize );
1912 mSelectionHandleOneActualPosition = cursorPositionOne.GetVectorXY();
1913 mSelectionHandleTwoActualPosition = cursorPositionTwo.GetVectorXY();
1915 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
1916 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
1917 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
1918 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
1920 if( mHighlightMeshActor )
1922 mHighlightMeshActor.SetVisible( true );
1928 void TextInput::ScrollTextViewToMakeCursorVisible( const Vector3& cursorPosition )
1930 // Scroll the text to make the cursor visible.
1931 const Size cursorSize( CURSOR_THICKNESS,
1932 GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height );
1934 // Need to scroll the text to make the cursor visible and to cover the whole text-input area.
1936 const Vector3& controlSize = GetControlSize();
1938 // Calculates the new scroll position.
1939 Vector2 scrollOffset = mTextLayoutInfo.mScrollOffset;
1940 if( ( cursorPosition.x < 0.f ) || ( cursorPosition.x > controlSize.width ) )
1942 scrollOffset.x += cursorPosition.x;
1945 if( cursorPosition.y - cursorSize.height < 0.f )
1947 scrollOffset.y += ( cursorPosition.y - cursorSize.height );
1949 else if( cursorPosition.y > controlSize.height )
1951 scrollOffset.y += cursorPosition.y;
1954 // Sets the new scroll position.
1955 SetScrollPosition( Vector2::ZERO ); // TODO: need to reset to the zero position in order to make the scroll trim to work.
1956 SetScrollPosition( scrollOffset );
1959 void TextInput::StartScrollTimer()
1963 mScrollTimer = Timer::New( SCROLL_TICK_INTERVAL );
1964 mScrollTimer.TickSignal().Connect( this, &TextInput::OnScrollTimerTick );
1967 if( !mScrollTimer.IsRunning() )
1969 mScrollTimer.Start();
1973 void TextInput::StopScrollTimer()
1977 mScrollTimer.Stop();
1981 bool TextInput::OnScrollTimerTick()
1983 // TODO: need to set the new style accordingly the new handle position.
1985 if( !( mGrabHandleVisibility && mGrabHandle ) && !( mSelectionHandleOne && mSelectionHandleTwo ) )
1987 // nothing to do if all handles are invisible or doesn't exist.
1993 // Choose between the grab handle or the selection handles.
1994 Vector3& actualHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mActualGrabHandlePosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
1995 std::size_t& handlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCursorPosition : ( mCurrentSelectionId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
1996 Vector3& currentHandlePosition = ( mGrabHandleVisibility && mGrabHandle ) ? mCurrentHandlePosition : mCurrentSelectionHandlePosition;
1998 std::size_t newCursorPosition = 0;
1999 ReturnClosestIndex( actualHandlePosition.GetVectorXY(), newCursorPosition );
2001 // Whether the handle's position is different of the previous one and in the case of the selection handle,
2002 // the new selection handle's position needs to be different of the other one.
2003 const bool differentSelectionHandles = ( mGrabHandleVisibility && mGrabHandle ) ? newCursorPosition != handlePosition :
2004 ( mCurrentSelectionId == HandleOne ) ? ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleTwoPosition ) :
2005 ( newCursorPosition != handlePosition ) && ( newCursorPosition != mSelectionHandleOnePosition );
2007 if( differentSelectionHandles )
2009 handlePosition = newCursorPosition;
2011 const Vector3 actualPosition = GetActualPositionFromCharacterPosition( newCursorPosition );
2013 Vector2 scrollDelta = ( actualPosition - currentHandlePosition ).GetVectorXY();
2015 Vector2 scrollPosition = mDisplayedTextView.GetScrollPosition();
2016 scrollPosition += scrollDelta;
2017 SetScrollPosition( scrollPosition );
2019 if( mDisplayedTextView.IsScrollPositionTrimmed() )
2024 currentHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition ).GetVectorXY();
2027 actualHandlePosition.x += mScrollDisplacement.x;
2028 actualHandlePosition.y += mScrollDisplacement.y;
2033 // Public Internal Methods (public for testing purpose)
2035 void TextInput::SetUpTouchEvents()
2037 if ( !mTapDetector )
2039 mTapDetector = TapGestureDetector::New();
2040 // Attach the actors and connect the signal
2041 mTapDetector.Attach(Self());
2043 // As contains children which may register for tap the default control detector is not used.
2044 mTapDetector.DetectedSignal().Connect(this, &TextInput::OnTextTap);
2047 if ( !mDoubleTapDetector )
2049 mDoubleTapDetector = TapGestureDetector::New();
2050 mDoubleTapDetector.SetTapsRequired( 2 );
2051 mDoubleTapDetector.DetectedSignal().Connect(this, &TextInput::OnDoubleTap);
2053 // Only attach and detach the actor to the double tap detector when we enter/leave edit mode
2054 // so that we do not, unnecessarily, have a double tap request all the time
2057 if ( !mPanGestureDetector )
2059 mPanGestureDetector = PanGestureDetector::New();
2060 mPanGestureDetector.DetectedSignal().Connect(this, &TextInput::OnHandlePan);
2063 if ( !mLongPressDetector )
2065 mLongPressDetector = LongPressGestureDetector::New();
2066 mLongPressDetector.DetectedSignal().Connect(this, &TextInput::OnLongPress);
2067 mLongPressDetector.Attach(Self());
2071 void TextInput::CreateTextViewActor()
2073 mDisplayedTextView = Toolkit::TextView::New();
2074 mDisplayedTextView.SetMarkupProcessingEnabled( mMarkUpEnabled );
2075 mDisplayedTextView.SetParentOrigin(ParentOrigin::TOP_LEFT);
2076 mDisplayedTextView.SetAnchorPoint(AnchorPoint::TOP_LEFT);
2077 mDisplayedTextView.SetMultilinePolicy(Toolkit::TextView::SplitByWord);
2078 mDisplayedTextView.SetWidthExceedPolicy( Toolkit::TextView::Original );
2079 mDisplayedTextView.SetHeightExceedPolicy( Toolkit::TextView::Original );
2080 mDisplayedTextView.SetLineJustification( Toolkit::TextView::Left );
2081 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>( Toolkit::Alignment::HorizontalLeft | Toolkit::Alignment::VerticalTop ) );
2082 mDisplayedTextView.SetPosition( Vector3( 0.0f, 0.0f, DISPLAYED_TEXT_VIEW_Z_OFFSET ) );
2083 mDisplayedTextView.SetSizePolicy( Toolkit::Control::Fixed, Toolkit::Control::Fixed );
2085 mDisplayedTextView.ScrolledSignal().Connect( this, &TextInput::OnTextViewScrolled );
2087 Self().Add( mDisplayedTextView );
2090 // Start a timer to initiate, used by the cursor to blink.
2091 void TextInput::StartCursorBlinkTimer()
2093 if ( !mCursorBlinkTimer )
2095 mCursorBlinkTimer = Timer::New( CURSOR_BLINK_INTERVAL );
2096 mCursorBlinkTimer.TickSignal().Connect( this, &TextInput::OnCursorBlinkTimerTick );
2099 if ( !mCursorBlinkTimer.IsRunning() )
2101 mCursorBlinkTimer.Start();
2105 // Start a timer to initiate, used by the cursor to blink.
2106 void TextInput::StopCursorBlinkTimer()
2108 if ( mCursorBlinkTimer )
2110 mCursorBlinkTimer.Stop();
2114 void TextInput::StartEditMode()
2116 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput StartEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2118 if(!mEditModeActive)
2123 if ( mDoubleTapDetector )
2125 mDoubleTapDetector.Attach( Self() );
2129 void TextInput::EndEditMode()
2131 DALI_LOG_INFO(gLogFilter, Debug::General, "TextInput EndEditMode mEditModeActive[%s]\n", (mEditModeActive)?"true":"false" );
2133 ClearKeyInputFocus();
2135 if ( mDoubleTapDetector )
2137 mDoubleTapDetector.Detach( Self() );
2141 void TextInput::ApplyPreEditStyle( std::size_t preEditStartPosition, std::size_t preEditStringLength )
2143 if ( mPreEditFlag && ( preEditStringLength > 0 ) )
2145 mUnderlinedPriorToPreEdit = mInputStyle.IsUnderlineEnabled();
2147 style.SetUnderline( true );
2148 ApplyStyleToRange( style, TextStyle::UNDERLINE , preEditStartPosition, preEditStartPosition + preEditStringLength -1 );
2152 void TextInput::RemovePreEditStyle()
2154 if ( !mUnderlinedPriorToPreEdit )
2157 style.SetUnderline( false );
2158 SetActiveStyle( style, TextStyle::UNDERLINE );
2162 // IMF related methods
2165 ImfManager::ImfCallbackData TextInput::ImfEventReceived( Dali::ImfManager& imfManager, const ImfManager::ImfEventData& imfEvent )
2167 bool update( false );
2168 bool preeditResetRequired ( false );
2170 if (imfEvent.eventName != ImfManager::GETSURROUNDING )
2172 HidePopup(); // If Pop-up shown then hides it as editing text.
2175 switch ( imfEvent.eventName )
2177 case ImfManager::PREEDIT:
2179 mIgnoreFirstCommitFlag = false;
2181 // Some text may be selected, hiding keyboard causes an empty predictive string to be sent, we don't want to delete highlight in this case
2182 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2184 // replaces highlighted text with new character
2185 DeleteHighlightedText( false );
2188 preeditResetRequired = PreEditReceived( imfEvent.predictiveString, imfEvent.cursorOffset );
2190 if( IsScrollEnabled() )
2192 // Calculates the new cursor position (in actor coordinates)
2193 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2194 ScrollTextViewToMakeCursorVisible( cursorPosition );
2201 case ImfManager::COMMIT:
2203 if( mIgnoreFirstCommitFlag )
2205 // Do not commit in this case when keyboard sends a commit when shows for the first time (work-around for imf keyboard).
2206 mIgnoreFirstCommitFlag = false;
2210 // A Commit message is a word that has been accepted, it may have been a pre-edit word previously but now commited.
2212 // 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
2213 if ( mHighlightMeshActor && (!imfEvent.predictiveString.empty()) )
2215 // replaces highlighted text with new character
2216 DeleteHighlightedText( false );
2219 // A PreEditReset can cause a commit message to be sent, the Ignore Commit flag is used in scenarios where the word is
2220 // not needed, one such scenario is when the pre-edit word is too long to fit.
2221 if ( !mIgnoreCommitFlag )
2223 update = CommitReceived( imfEvent.predictiveString );
2227 mIgnoreCommitFlag = false; // reset ignore flag so next commit is acted upon.
2233 if( IsScrollEnabled() )
2235 // Calculates the new cursor position (in actor coordinates)
2236 const Vector3 cursorPosition = GetActualPositionFromCharacterPosition( mCursorPosition );
2238 ScrollTextViewToMakeCursorVisible( cursorPosition );
2243 case ImfManager::DELETESURROUNDING:
2245 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - delete surrounding mPreEditFlag[%s] cursor offset[%d] characters to delete[%d] position to delete[%u] \n",
2246 (mPreEditFlag)?"true":"false", imfEvent.cursorOffset, imfEvent.numberOfChars, static_cast<std::size_t>( mCursorPosition+imfEvent.cursorOffset) );
2248 mPreEditFlag = false;
2250 std::size_t toDelete = 0;
2251 std::size_t numberOfCharacters = 0;
2253 if( mHighlightMeshActor )
2255 // delete highlighted text.
2256 toDelete = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2257 numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - toDelete;
2261 if( std::abs( imfEvent.cursorOffset ) < mCursorPosition )
2263 toDelete = mCursorPosition + imfEvent.cursorOffset;
2265 if( toDelete + imfEvent.numberOfChars > mStyledText.size() )
2267 numberOfCharacters = mStyledText.size() - toDelete;
2271 numberOfCharacters = imfEvent.numberOfChars;
2274 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding pre-delete range mCursorPosition[%u] \n", mCursorPosition);
2275 DeleteRange( toDelete, numberOfCharacters );
2277 mCursorPosition = toDelete;
2278 mNumberOfSurroundingCharactersDeleted = numberOfCharacters;
2282 DALI_LOG_INFO( gLogFilter, Debug::General, "ImfEventReceived - deleteSurrounding post-delete range mCursorPosition[%u] \n", mCursorPosition);
2285 case ImfManager::GETSURROUNDING:
2287 // 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
2288 // the next key pressed. Instead the Select function sets the cursor position and surrounding text.
2289 if (! ( mHighlightMeshActor || mSelectingText ) )
2291 std::string text( GetText() );
2292 DALI_LOG_INFO( gLogFilter, Debug::General, "OnKey - surrounding text - set text [%s] and cursor[%u] \n", text.c_str(), mCursorPosition );
2294 imfManager.SetCursorPosition( mCursorPosition );
2295 imfManager.SetSurroundingText( text );
2298 if( 0 != mNumberOfSurroundingCharactersDeleted )
2300 mDisplayedTextView.RemoveTextFrom( mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2301 mNumberOfSurroundingCharactersDeleted = 0;
2303 if( mStyledText.empty() )
2305 // Styled text is empty, so set the placeholder text.
2306 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2307 mPlaceHolderSet = true;
2312 case ImfManager::VOID:
2314 DALI_ASSERT_DEBUG( false );
2318 ImfManager::ImfCallbackData callbackData( update, mCursorPosition, GetText(), preeditResetRequired );
2320 return callbackData;
2323 bool TextInput::PreEditReceived(const std::string& keyString, std::size_t cursorOffset )
2325 mPreserveCursorPosition = false; // As in pre-edit state we should have the cursor at the end of the word displayed not last touch position.
2327 DALI_LOG_INFO(gLogFilter, Debug::General, ">>PreEditReceived preserveCursorPos[%d] mCursorPos[%d] mPreEditFlag[%d]\n",
2328 mPreserveCursorPosition, mCursorPosition, mPreEditFlag );
2330 bool preeditResetRequest ( false );
2332 if( mPreEditFlag ) // Already in pre-edit state.
2334 if( mStyledText.size() >= mMaxStringLength )
2336 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived styledTextSize >= mMaxStringLength \n");
2337 // Cannot fit these characters into field, clear pre-edit.
2338 if ( !mUnderlinedPriorToPreEdit )
2341 style.SetUnderline( false );
2342 ApplyStyleToRange( style, TextStyle::UNDERLINE , mPreEditStartPosition, mPreEditStartPosition + mPreEditLength -1 );
2344 mIgnoreCommitFlag = true;
2345 preeditResetRequest = false; // this will reset the keyboard's predictive suggestions.
2346 mPreEditFlag = false;
2347 EmitMaxInputCharactersReachedSignal();
2351 // delete existing pre-edit string
2352 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2354 // Store new pre-edit string
2355 mPreEditString.SetText( keyString );
2357 if ( keyString.empty() )
2359 mPreEditFlag = false;
2360 mCursorPosition = mPreEditStartPosition;
2362 if( mStyledText.empty() )
2364 // Styled text is empty, so set the placeholder text.
2365 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2366 mPlaceHolderSet = true;
2370 mDisplayedTextView.RemoveTextFrom( mPreEditStartPosition, numberOfCharactersToReplace );
2372 GetTextLayoutInfo();
2377 // Insert new pre-edit string. InsertAt updates the size and position table.
2378 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, numberOfCharactersToReplace );
2379 // 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.
2380 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2381 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2382 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] \n", mCursorPosition);
2385 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2389 else // mPreEditFlag not set
2391 if ( !keyString.empty() ) // Imf can send an empty pre-edit followed by Backspace instead of a commit.
2393 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived Initial Pre-Edit string \n");
2394 // new pre-edit so move into pre-edit state by setting flag
2395 mPreEditFlag = true;
2396 mPreEditString.SetText( keyString ); // store new pre-edit string
2397 mPreEditStartPosition = mCursorPosition; // store starting cursor position of pre-edit so know where to re-start from
2398 mPreEditLength = InsertAt( mPreEditString, mPreEditStartPosition, 0 );
2399 // 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.
2400 mCursorPosition = mPreEditStartPosition + std::min( cursorOffset, mPreEditLength );
2401 ApplyPreEditStyle( mPreEditStartPosition, mPreEditLength );
2402 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived mCursorPosition[%u] mPreEditStartPosition[%u]\n", mCursorPosition, mPreEditStartPosition);
2403 // cursor update to keyboard is not done here as the keyboard knows the cursor position and provides the 'cursorOffset'.
2409 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReceived with empty keyString\n");
2413 return preeditResetRequest;
2416 bool TextInput::CommitReceived(const std::string& keyString )
2418 DALI_LOG_INFO(gLogFilter, Debug::General, ">>CommitReceived preserveCursorPos[%d] mPreEditStartPosition [%d] mCursorPos[%d] mPreEditFlag[%d] mIgnoreCommitFlag[%s]\n",
2419 mPreserveCursorPosition, mPreEditStartPosition, mCursorPosition, mPreEditFlag, (mIgnoreCommitFlag)?"true":"false" );
2421 bool update( false );
2423 RemovePreEditStyle();
2425 const std::size_t styledTextSize( mStyledText.size() );
2426 if( styledTextSize >= mMaxStringLength )
2428 // Cannot fit these characters into field, clear pre-edit.
2431 mIgnoreCommitFlag = true;
2432 mPreEditFlag = false;
2434 EmitMaxInputCharactersReachedSignal();
2440 // delete existing pre-edit string
2441 const std::size_t numberOfCharactersToReplace = DeletePreEdit();
2442 mPreEditFlag = false;
2444 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived mPreserveCursorPosition[%s] mPreEditStartPosition[%u]\n",
2445 (mPreserveCursorPosition)?"true":"false", mPreEditStartPosition );
2447 if ( mPreserveCursorPosition ) // PreEditReset has been called triggering this commit.
2449 // No need to update cursor position as Cursor location given by touch.
2450 InsertAt( Text( keyString ), mPreEditStartPosition, numberOfCharactersToReplace );
2451 mPreserveCursorPosition = false;
2455 // Cursor not set by touch so needs to be re-positioned to input more text
2456 mCursorPosition = mPreEditStartPosition + InsertAt( Text(keyString), mPreEditStartPosition, numberOfCharactersToReplace ); // update cursor position as InsertAt, re-draw cursor with this
2458 // If a space or enter caused the commit then our string is one longer than the string given to us by the commit key.
2459 if ( mCommitByKeyInput )
2461 mCursorPosition = std::min ( mCursorPosition + 1, mStyledText.size() );
2462 mCommitByKeyInput = false;
2468 if ( mSelectTextOnCommit )
2470 SelectText(mRequestedSelection.mStartOfSelection, mRequestedSelection.mEndOfSelection );
2475 else // mPreEditFlag not set
2477 if ( !mIgnoreCommitFlag ) // Check if this commit should be ignored.
2479 if( mStyledText.empty() && mPlaceHolderSet )
2481 // If the styled text is empty and the placeholder text is set, it needs to be cleared.
2482 mDisplayedTextView.SetText( "" );
2483 mNumberOfSurroundingCharactersDeleted = 0;
2484 mPlaceHolderSet = false;
2486 mCursorPosition = mCursorPosition + InsertAt( Text( keyString ), mCursorPosition, mNumberOfSurroundingCharactersDeleted );
2488 mNumberOfSurroundingCharactersDeleted = 0;
2493 mIgnoreCommitFlag = false; // Reset flag so future commits will not be ignored.
2498 mSelectTextOnCommit = false;
2500 DALI_LOG_INFO(gLogFilter, Debug::General, "CommitReceived << mCursorPos[%d] mPreEditFlag[%d] update[%s] \n",
2501 mCursorPosition, mPreEditFlag, (update)?"true":"false" );
2506 // End of IMF related methods
2508 std::size_t TextInput::DeletePreEdit()
2510 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeletePreEdit mPreEditFlag[%s] \n", (mPreEditFlag)?"true":"false");
2512 DALI_ASSERT_DEBUG( mPreEditFlag );
2514 const std::size_t preEditStringLength = mPreEditString.GetLength();
2515 const std::size_t styledTextSize = mStyledText.size();
2517 std::size_t endPosition = mPreEditStartPosition + preEditStringLength;
2519 // Prevents erase items outside mStyledText bounds.
2520 if( mPreEditStartPosition > styledTextSize )
2522 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. mPreEditStartPosition > mStyledText.size()" );
2523 mPreEditStartPosition = styledTextSize;
2526 if( ( endPosition > styledTextSize ) || ( endPosition < mPreEditStartPosition ) )
2528 DALI_ASSERT_DEBUG( !"TextInput::DeletePreEdit. ( endPosition > mStyledText.size() ) || ( endPosition < mPreEditStartPosition )" );
2529 endPosition = styledTextSize;
2532 mStyledText.erase( mStyledText.begin() + mPreEditStartPosition, mStyledText.begin() + endPosition );
2534 // DeletePreEdit() doesn't remove characters from the text-view because may be followed by an InsertAt() which inserts characters,
2535 // in that case, the Insert should use the returned number of deleted characters and replace the text which helps the text-view to
2537 // In case DeletePreEdit() is not followed by an InsertAt() characters must be deleted after this call.
2539 return preEditStringLength;
2542 void TextInput::PreEditReset( bool preserveCursorPosition )
2544 DALI_LOG_INFO(gLogFilter, Debug::General, "PreEditReset preserveCursorPos[%d] mCursorPos[%d] \n",
2545 preserveCursorPosition, mCursorPosition);
2547 // 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.
2548 mPreserveCursorPosition = preserveCursorPosition;
2550 // Reset incase we are in a pre-edit state.
2551 ImfManager imfManager = ImfManager::Get();
2554 imfManager.Reset(); // Will trigger a commit message
2558 void TextInput::CursorUpdate()
2562 ImfManager imfManager = ImfManager::Get();
2565 std::string text( GetText() );
2566 imfManager.SetSurroundingText( text ); // Notifying IMF of a cursor change triggers a surrounding text request so updating it now.
2567 imfManager.SetCursorPosition ( mCursorPosition );
2568 imfManager.NotifyCursorPosition();
2572 /* Delete highlighted characters redisplay*/
2573 void TextInput::DeleteHighlightedText( bool inheritStyle )
2575 DALI_LOG_INFO( gLogFilter, Debug::General, "DeleteHighlightedText handlePosOne[%u] handlePosTwo[%u]\n", mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
2577 if(mHighlightMeshActor)
2579 mCursorPosition = std::min( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2581 MarkupProcessor::StyledTextArray::iterator start = mStyledText.begin() + mCursorPosition;
2582 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition );
2584 // Get the styled text of the characters to be deleted as it may be needed if
2585 // the "exceed the text-input's boundaries" option is disabled.
2586 MarkupProcessor::StyledTextArray styledCharactersToDelete;
2588 styledCharactersToDelete.insert( styledCharactersToDelete.begin(), start, end );
2590 mStyledText.erase( start, end ); // erase range of characters
2592 // Remove text from TextView.
2594 if( mStyledText.empty() )
2596 // Styled text is empty, so set the placeholder text.
2597 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2598 mPlaceHolderSet = true;
2602 const std::size_t numberOfCharacters = std::max( mSelectionHandleOnePosition, mSelectionHandleTwoPosition ) - mCursorPosition;
2604 mDisplayedTextView.RemoveTextFrom( mCursorPosition, numberOfCharacters );
2606 // It may happen than after removing a white space or a new line character,
2607 // two words merge, this new word could be big enough to not fit in its
2608 // current line, so moved to the next one, and make some part of the text to
2609 // exceed the text-input's boundary.
2610 if( !mExceedEnabled )
2612 // Get the new text layout after removing some characters.
2613 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2615 // Get text-input's size.
2616 const Vector3& size = GetControlSize();
2618 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2619 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2621 mDisplayedTextView.InsertTextAt( mCursorPosition, styledCharactersToDelete );
2623 mStyledText.insert( mStyledText.begin() + mCursorPosition,
2624 styledCharactersToDelete.begin(),
2625 styledCharactersToDelete.end() );
2629 GetTextLayoutInfo();
2635 const TextStyle oldInputStyle( mInputStyle );
2637 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2639 if( oldInputStyle != mInputStyle )
2641 // Updates the line height accordingly with the input style.
2644 EmitStyleChangedSignal();
2650 void TextInput::DeleteRange( const std::size_t start, const std::size_t ncharacters )
2652 DALI_ASSERT_DEBUG( start <= mStyledText.size() );
2653 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2655 DALI_LOG_INFO(gLogFilter, Debug::General, ">>DeleteRange pre mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2658 if ( ( !mStyledText.empty()) && ( ( start + ncharacters ) <= mStyledText.size() ) )
2660 MarkupProcessor::StyledTextArray::iterator itStart = mStyledText.begin() + start;
2661 MarkupProcessor::StyledTextArray::iterator itEnd = mStyledText.begin() + start + ncharacters;
2663 mStyledText.erase(itStart, itEnd);
2665 // update the selection handles if they are visible.
2666 if( mHighlightMeshActor )
2668 std::size_t& minHandle = ( mSelectionHandleOnePosition <= mSelectionHandleTwoPosition ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition );
2669 std::size_t& maxHandle = ( mSelectionHandleTwoPosition > mSelectionHandleOnePosition ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition );
2671 if( minHandle >= start + ncharacters )
2673 minHandle -= ncharacters;
2675 else if( ( minHandle > start ) && ( minHandle < start + ncharacters ) )
2680 if( maxHandle >= start + ncharacters )
2682 maxHandle -= ncharacters;
2684 else if( ( maxHandle > start ) && ( maxHandle < start + ncharacters ) )
2690 // 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.
2693 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteRange<< post mStyledText[%s] mPreEditFlag[%s] \n", GetText().c_str(), (mPreEditFlag)?"true":"false");
2695 // Although mStyledText has been set to a new text string we no longer re-draw the text or notify the cursor change.
2696 // This is a performance decision as the use of this function often means the text is being replaced or just deleted.
2697 // Mean we do not re-draw the text more than we have too.
2700 /* Delete character at current cursor position and redisplay*/
2701 void TextInput::DeleteCharacter( std::size_t positionToDelete )
2703 // Ensure positionToDelete is not out of bounds.
2704 DALI_ASSERT_DEBUG( positionToDelete <= mStyledText.size() );
2705 DALI_ASSERT_DEBUG( !mStyledText.empty() );
2706 DALI_ASSERT_DEBUG( positionToDelete > 0 );
2708 DALI_LOG_INFO(gLogFilter, Debug::General, "DeleteCharacter positionToDelete[%u]", positionToDelete );
2711 if ( ( !mStyledText.empty()) && ( positionToDelete > 0 ) && positionToDelete <= mStyledText.size() ) // don't try to delete if no characters left of cursor
2713 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + positionToDelete - 1;
2715 // Get the styled text of the character to be deleted as it may be needed if
2716 // the "exceed the text-input's boundaries" option is disabled.
2717 const MarkupProcessor::StyledText styledCharacterToDelete( *it );
2719 mStyledText.erase(it); // erase the character left of positionToDelete
2721 if( mStyledText.empty() )
2723 // Styled text is empty, so set the placeholder text.
2724 mDisplayedTextView.SetText( mStyledPlaceHolderText );
2725 mPlaceHolderSet = true;
2729 mDisplayedTextView.RemoveTextFrom( positionToDelete - 1, 1 );
2731 const Character characterToDelete = styledCharacterToDelete.mText[0];
2733 // It may happen than after removing a white space or a new line character,
2734 // two words merge, this new word could be big enough to not fit in its
2735 // current line, so moved to the next one, and make some part of the text to
2736 // exceed the text-input's boundary.
2737 if( !mExceedEnabled )
2739 if( characterToDelete.IsWhiteSpace() || characterToDelete.IsNewLine() )
2741 // Get the new text layout after removing one character.
2742 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
2744 // Get text-input's size.
2745 const Vector3& size = GetControlSize();
2747 if( ( mTextLayoutInfo.mTextSize.width > size.width ) ||
2748 ( mTextLayoutInfo.mTextSize.height > size.height ) )
2750 MarkupProcessor::StyledTextArray array;
2751 array.push_back( styledCharacterToDelete );
2752 mDisplayedTextView.InsertTextAt( positionToDelete - 1, array );
2754 mStyledText.insert( mStyledText.begin() + ( positionToDelete - 1 ), styledCharacterToDelete );
2759 GetTextLayoutInfo();
2761 ShowGrabHandleAndSetVisibility( false );
2763 mCursorPosition = positionToDelete -1;
2765 const TextStyle oldInputStyle( mInputStyle );
2767 mInputStyle = GetStyleAtCursor(); // Inherit style from cursor position
2769 if( oldInputStyle != mInputStyle )
2771 // Updates the line height accordingly with the input style.
2774 EmitStyleChangedSignal();
2779 /*Insert new character into the string and (optionally) redisplay text-input*/
2780 std::size_t TextInput::InsertAt( const Text& newText, const std::size_t insertionPosition, const std::size_t numberOfCharactersToReplace )
2782 DALI_LOG_INFO(gLogFilter, Debug::General, "InsertAt insertionPosition[%u]\n", insertionPosition );
2784 // Ensure insertionPosition is not out of bounds.
2785 DALI_ASSERT_ALWAYS( insertionPosition <= mStyledText.size() );
2787 bool textExceedsMaximunNumberOfCharacters = false;
2788 bool textExceedsBoundary = false;
2789 std::size_t insertedStringLength = DoInsertAt( newText, insertionPosition, numberOfCharactersToReplace, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
2791 ShowGrabHandleAndSetVisibility( false );
2793 if( textExceedsMaximunNumberOfCharacters || textExceedsBoundary )
2797 mIgnoreCommitFlag = true;
2798 mPreEditFlag = false;
2799 // A PreEditReset( false ) should be triggered from here if the keyboards predictive suggestions must be cleared.
2800 // Although can not directly call PreEditReset() as it will cause a recursive emit loop.
2803 if( textExceedsMaximunNumberOfCharacters )
2805 EmitMaxInputCharactersReachedSignal();
2808 if( textExceedsBoundary )
2810 EmitInputTextExceedsBoundariesSignal();
2811 PreEditReset( false );
2815 return insertedStringLength;
2818 ImageActor TextInput::CreateCursor( Image cursorImage, const Vector4& border )
2824 cursor = ImageActor::New( cursorImage );
2828 cursor = ImageActor::New( Image::New( DEFAULT_CURSOR ) );
2831 cursor.SetStyle(ImageActor::STYLE_NINE_PATCH);
2832 cursor.SetNinePatchBorder( border );
2834 cursor.SetParentOrigin(ParentOrigin::TOP_LEFT);
2835 cursor.SetAnchorPoint(AnchorPoint::BOTTOM_CENTER);
2836 cursor.SetVisible(false);
2841 void TextInput::AdvanceCursor(bool reverse, std::size_t places)
2843 // As cursor is not moving due to grab handle, handle should be hidden.
2844 ShowGrabHandleAndSetVisibility( false );
2846 bool cursorPositionChanged = false;
2849 if ( mCursorPosition >= places )
2851 mCursorPosition = mCursorPosition - places;
2852 cursorPositionChanged = true;
2857 if ((mCursorPosition + places) <= mStyledText.size())
2859 mCursorPosition = mCursorPosition + places;
2860 cursorPositionChanged = true;
2864 if( cursorPositionChanged )
2866 const std::size_t cursorPositionForStyle = ( 0 == mCursorPosition ? 0 : mCursorPosition - 1 );
2868 const TextStyle oldInputStyle( mInputStyle );
2869 mInputStyle = GetStyleAt( cursorPositionForStyle ); // Inherit style from selected position.
2873 if( oldInputStyle != mInputStyle )
2875 // Updates the line height accordingly with the input style.
2878 EmitStyleChangedSignal();
2881 ImfManager imfManager = ImfManager::Get();
2884 imfManager.SetCursorPosition ( mCursorPosition );
2885 imfManager.NotifyCursorPosition();
2890 void TextInput::DrawCursor(const std::size_t nthChar)
2892 // Get height of cursor and set its size
2893 Size size( CURSOR_THICKNESS, 0.0f );
2894 if (!mTextLayoutInfo.mCharacterLayoutInfoTable.empty())
2896 size.height = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) ).height;
2900 // Measure Font so know how big text will be if no initial text to measure.
2901 size.height = mLineHeight;
2904 mCursor.SetSize(size);
2906 // If the character is italic then the cursor also tilts.
2907 mCursor.SetRotation( mInputStyle.IsItalicsEnabled() ? Degree( mInputStyle.GetItalicsAngle() - CURSOR_ANGLE_OFFSET ) : Degree( 0.f ), Vector3::ZAXIS );
2909 DALI_ASSERT_DEBUG( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
2911 if ( ( mCursorPosition <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() ) )
2913 Vector3 altPosition; // Alternate (i.e. opposite direction) cursor position.
2914 bool altPositionValid; // Alternate cursor validity flag.
2915 bool directionRTL; // Need to know direction of primary cursor (in case we have 2 cursors and need to show them differently)
2916 Vector3 position = GetActualPositionFromCharacterPosition( mCursorPosition, directionRTL, altPosition, altPositionValid );
2918 SetAltCursorEnabled( altPositionValid );
2920 if(!altPositionValid)
2922 mCursor.SetPosition( position + UI_OFFSET );
2926 size.height *= 0.5f;
2927 mCursor.SetSize(size);
2928 mCursor.SetPosition( position + UI_OFFSET - Vector3(0.0f, directionRTL ? 0.0f : size.height, 0.0f) );
2930 // TODO: change this cursor pos, to be the one where the cursor is sourced from.
2931 Size rowSize = GetRowRectFromCharacterPosition( GetVisualPosition( mCursorPosition ) );
2932 size.height = rowSize.height * 0.5f;
2933 mCursorRTL.SetSize(size);
2934 mCursorRTL.SetPosition( altPosition + UI_OFFSET - Vector3(0.0f, directionRTL ? size.height : 0.0f, 0.0f) );
2937 if( IsScrollEnabled() )
2939 // Whether cursor and grab handle are inside the boundaries of the text-input when text scroll is enabled.
2940 mIsCursorInScrollArea = mIsGrabHandleInScrollArea = IsPositionInsideBoundaries( position, size, GetControlSize() );
2945 void TextInput::SetAltCursorEnabled( bool enabled )
2947 mCursorRTLEnabled = enabled;
2948 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2951 void TextInput::SetCursorVisibility( bool visible )
2953 mCursorVisibility = visible;
2954 mCursor.SetVisible( mCursorVisibility && mIsCursorInScrollArea );
2955 mCursorRTL.SetVisible( mCursorVisibility && mCursorRTLEnabled );
2958 void TextInput::CreateGrabHandle( Dali::Image image )
2964 mGrabHandleImage = Image::New(DEFAULT_GRAB_HANDLE);
2968 mGrabHandleImage = image;
2971 mGrabHandle = ImageActor::New(mGrabHandleImage);
2972 mGrabHandle.SetParentOrigin(ParentOrigin::TOP_LEFT);
2973 mGrabHandle.SetAnchorPoint(AnchorPoint::TOP_CENTER);
2975 mGrabHandle.SetDrawMode(DrawMode::OVERLAY);
2977 ShowGrabHandleAndSetVisibility( false );
2979 CreateGrabArea( mGrabHandle );
2981 mActiveLayer.Add(mGrabHandle);
2985 void TextInput::CreateGrabArea( Actor& parent )
2987 mGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
2988 mGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
2989 mGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_GRAB_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
2990 mGrabArea.TouchedSignal().Connect(this,&TextInput::OnPressDown);
2991 mTapDetector.Attach( mGrabArea );
2992 mPanGestureDetector.Attach( mGrabArea );
2994 parent.Add(mGrabArea);
2997 Vector3 TextInput::MoveGrabHandle( const Vector2& displacement )
2999 Vector3 actualHandlePosition;
3003 mActualGrabHandlePosition.x += displacement.x;
3004 mActualGrabHandlePosition.y += displacement.y;
3006 // Grab handle should jump to the nearest character and take cursor with it
3007 std::size_t newCursorPosition = 0;
3008 ReturnClosestIndex( mActualGrabHandlePosition.GetVectorXY(), newCursorPosition );
3010 actualHandlePosition = GetActualPositionFromCharacterPosition( newCursorPosition );
3012 bool handleVisible = true;
3014 if( IsScrollEnabled() )
3016 const Vector3 controlSize = GetControlSize();
3017 const Size cursorSize = GetRowRectFromCharacterPosition( GetVisualPosition( newCursorPosition ) );
3018 // Scrolls the text if the handle is not in a visible position
3019 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3026 mCurrentHandlePosition = actualHandlePosition;
3027 mScrollDisplacement = Vector2::ZERO;
3031 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3033 mScrollDisplacement.x = -SCROLL_SPEED;
3035 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3037 mScrollDisplacement.x = SCROLL_SPEED;
3039 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3041 mScrollDisplacement.y = -SCROLL_SPEED;
3043 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3045 mScrollDisplacement.y = SCROLL_SPEED;
3051 if( handleVisible && // Only redraw cursor and do updates if position changed
3052 ( newCursorPosition != mCursorPosition ) ) // and the new position is visible (if scroll is not enabled, it's always true).
3054 mCursorPosition = newCursorPosition;
3056 mGrabHandle.SetPosition( actualHandlePosition + UI_OFFSET );
3058 const TextStyle oldInputStyle( mInputStyle );
3060 mInputStyle = GetStyleAtCursor(); //Inherit style from cursor position
3062 CursorUpdate(); // Let keyboard know the new cursor position so can 're-capture' for prediction.
3064 if( oldInputStyle != mInputStyle )
3066 // Updates the line height accordingly with the input style.
3069 EmitStyleChangedSignal();
3074 return actualHandlePosition;
3077 void TextInput::ShowGrabHandle( bool visible )
3079 if ( IsGrabHandleEnabled() )
3083 mGrabHandle.SetVisible( mGrabHandleVisibility );
3085 StartMonitoringStageForTouch();
3089 void TextInput::ShowGrabHandleAndSetVisibility( bool visible )
3091 mGrabHandleVisibility = visible;
3092 ShowGrabHandle( visible );
3095 // Callbacks connected to be Property notifications for Boundary checking.
3097 void TextInput::OnLeftBoundaryExceeded(PropertyNotification& source)
3099 mIsSelectionHandleOneFlipped = true;
3100 mSelectionHandleOne.SetScale( -1.0f, 1.0f, 1.0f );
3101 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3104 void TextInput::OnReturnToLeftBoundary(PropertyNotification& source)
3106 mIsSelectionHandleOneFlipped = false;
3107 mSelectionHandleOne.SetScale( 1.0f, 1.0f, 1.0f );
3108 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3111 void TextInput::OnRightBoundaryExceeded(PropertyNotification& source)
3113 mIsSelectionHandleTwoFlipped = true;
3114 mSelectionHandleTwo.SetScale( -1.0f, 1.0f, 1.0f );
3115 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_RIGHT);
3118 void TextInput::OnReturnToRightBoundary(PropertyNotification& source)
3120 mIsSelectionHandleTwoFlipped = false;
3121 mSelectionHandleTwo.SetScale( 1.0f, 1.0f, 1.0f );
3122 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT);
3125 // todo change PropertyNotification signal definition to include Actor. Hence won't need duplicate functions.
3126 void TextInput::OnHandleOneLeavesBoundary( PropertyNotification& source)
3128 mSelectionHandleOne.SetOpacity(0.0f);
3131 void TextInput::OnHandleOneWithinBoundary(PropertyNotification& source)
3133 mSelectionHandleOne.SetOpacity(1.0f);
3136 void TextInput::OnHandleTwoLeavesBoundary( PropertyNotification& source)
3138 mSelectionHandleTwo.SetOpacity(0.0f);
3141 void TextInput::OnHandleTwoWithinBoundary(PropertyNotification& source)
3143 mSelectionHandleTwo.SetOpacity(1.0f);
3146 // End of Callbacks connected to be Property notifications for Boundary checking.
3148 void TextInput::SetUpHandlePropertyNotifications()
3150 /* Property notifications for handles exceeding the boundary and returning back within boundary */
3152 Vector3 handlesize = GetSelectionHandleSize();
3154 // Exceeding horizontal boundary
3155 PropertyNotification leftNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.x + handlesize.x) );
3156 leftNotification.NotifySignal().Connect( this, &TextInput::OnLeftBoundaryExceeded );
3158 PropertyNotification rightNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.z - handlesize.x ) );
3159 rightNotification.NotifySignal().Connect( this, &TextInput::OnRightBoundaryExceeded );
3161 // Within horizontal boundary
3162 PropertyNotification leftLeaveNotification = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_X, GreaterThanCondition( mBoundingRectangleWorldCoordinates.x + 2*handlesize.x ) );
3163 leftLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToLeftBoundary );
3165 PropertyNotification rightLeaveNotification = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_X, LessThanCondition( mBoundingRectangleWorldCoordinates.z - 2*handlesize.x ) );
3166 rightLeaveNotification.NotifySignal().Connect( this, &TextInput::OnReturnToRightBoundary );
3168 // Exceeding vertical boundary
3169 PropertyNotification verticalExceedNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3170 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3171 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3172 verticalExceedNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneLeavesBoundary );
3174 PropertyNotification verticalExceedNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3175 OutsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3176 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3177 verticalExceedNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoLeavesBoundary );
3179 // Within vertical boundary
3180 PropertyNotification verticalWithinNotificationOne = mSelectionHandleOne.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3181 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3182 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3183 verticalWithinNotificationOne.NotifySignal().Connect( this, &TextInput::OnHandleOneWithinBoundary );
3185 PropertyNotification verticalWithinNotificationTwo = mSelectionHandleTwo.AddPropertyNotification( Actor::WORLD_POSITION_Y,
3186 InsideCondition( mBoundingRectangleWorldCoordinates.y + handlesize.y,
3187 mBoundingRectangleWorldCoordinates.w - handlesize.y ) );
3188 verticalWithinNotificationTwo.NotifySignal().Connect( this, &TextInput::OnHandleTwoWithinBoundary );
3191 void TextInput::CreateSelectionHandles( std::size_t start, std::size_t end, Dali::Image handleOneImage, Dali::Image handleTwoImage )
3193 mSelectionHandleOnePosition = start;
3194 mSelectionHandleTwoPosition = end;
3196 if ( !mSelectionHandleOne )
3198 // create normal and pressed images
3199 mSelectionHandleOneImage = Image::New( DEFAULT_SELECTION_HANDLE_ONE );
3200 mSelectionHandleOneImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_ONE_PRESSED );
3202 mSelectionHandleOne = ImageActor::New( mSelectionHandleOneImage );
3203 mSelectionHandleOne.SetName("SelectionHandleOne");
3204 mSelectionHandleOne.SetParentOrigin( ParentOrigin::TOP_LEFT );
3205 mSelectionHandleOne.SetAnchorPoint( AnchorPoint::TOP_RIGHT ); // Change to BOTTOM_RIGHT if Look'n'Feel requires handle above text.
3206 mIsSelectionHandleOneFlipped = false;
3207 mSelectionHandleOne.SetDrawMode( DrawMode::OVERLAY ); // ensure grab handle above text
3209 mHandleOneGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3210 mHandleOneGrabArea.SetName("SelectionHandleOneGrabArea");
3212 mHandleOneGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3213 mHandleOneGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3215 mTapDetector.Attach( mHandleOneGrabArea );
3216 mPanGestureDetector.Attach( mHandleOneGrabArea );
3218 mHandleOneGrabArea.TouchedSignal().Connect(this,&TextInput::OnHandleOneTouched);
3220 mSelectionHandleOne.Add( mHandleOneGrabArea );
3221 mActiveLayer.Add( mSelectionHandleOne );
3224 if ( !mSelectionHandleTwo )
3226 // create normal and pressed images
3227 mSelectionHandleTwoImage = Image::New( DEFAULT_SELECTION_HANDLE_TWO );
3228 mSelectionHandleTwoImagePressed = Image::New( DEFAULT_SELECTION_HANDLE_TWO_PRESSED );
3230 mSelectionHandleTwo = ImageActor::New( mSelectionHandleTwoImage );
3231 mSelectionHandleTwo.SetName("SelectionHandleTwo");
3232 mSelectionHandleTwo.SetParentOrigin( ParentOrigin::TOP_LEFT );
3233 mSelectionHandleTwo.SetAnchorPoint( AnchorPoint::TOP_LEFT );
3234 mIsSelectionHandleTwoFlipped = false;
3235 mSelectionHandleTwo.SetDrawMode(DrawMode::OVERLAY); // ensure grab handle above text
3237 mHandleTwoGrabArea = Actor::New(); // Area that Grab handle responds to, larger than actual handle so easier to move
3238 mHandleTwoGrabArea.SetName("SelectionHandleTwoGrabArea");
3239 mHandleTwoGrabArea.ApplyConstraint( Constraint::New<Vector3>( Actor::SIZE, ParentSource( Actor::SIZE ), RelativeToConstraint( DEFAULT_SELECTION_HANDLE_RELATIVE_SIZE ) ) ); // grab area to be larger than text actor
3240 mHandleTwoGrabArea.SetPositionInheritanceMode( Dali::USE_PARENT_POSITION );
3242 mTapDetector.Attach( mHandleTwoGrabArea );
3243 mPanGestureDetector.Attach( mHandleTwoGrabArea );
3245 mHandleTwoGrabArea.TouchedSignal().Connect(this, &TextInput::OnHandleTwoTouched);
3247 mSelectionHandleTwo.Add( mHandleTwoGrabArea );
3249 mActiveLayer.Add( mSelectionHandleTwo );
3252 SetUpHandlePropertyNotifications();
3254 // update table as text may have changed.
3255 GetTextLayoutInfo();
3257 mSelectionHandleOneActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleOnePosition );
3258 mSelectionHandleTwoActualPosition = GetActualPositionFromCharacterPosition( mSelectionHandleTwoPosition );
3260 mSelectionHandleOne.SetPosition( mSelectionHandleOneActualPosition + UI_OFFSET + mSelectionHandleOneOffset );
3261 mSelectionHandleTwo.SetPosition( mSelectionHandleTwoActualPosition + UI_OFFSET + mSelectionHandleTwoOffset );
3263 // Calculates and set the visibility if the scroll mode is enabled.
3264 bool isSelectionHandleOneVisible = true;
3265 bool isSelectionHandleTwoVisible = true;
3266 if( IsScrollEnabled() )
3268 const Vector3& controlSize( GetControlSize() );
3269 isSelectionHandleOneVisible = IsPositionInsideBoundaries( mSelectionHandleOneActualPosition, Size::ZERO, controlSize );
3270 isSelectionHandleTwoVisible = IsPositionInsideBoundaries( mSelectionHandleTwoActualPosition, Size::ZERO, controlSize );
3271 mSelectionHandleOne.SetVisible( isSelectionHandleOneVisible );
3272 mSelectionHandleTwo.SetVisible( isSelectionHandleTwoVisible );
3275 CreateHighlight(); // function will only create highlight if not already created.
3278 Vector3 TextInput::MoveSelectionHandle( SelectionHandleId handleId, const Vector2& displacement )
3280 Vector3 actualHandlePosition;
3282 if ( mSelectionHandleOne && mSelectionHandleTwo )
3284 const Vector3& controlSize = GetControlSize();
3286 Size cursorSize( CURSOR_THICKNESS, 0.f );
3288 // Get a reference of the wanted selection handle (handle one or two).
3289 Vector3& actualSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOneActualPosition : mSelectionHandleTwoActualPosition;
3291 // Get a reference for the current position of the handle and a copy of its pair
3292 std::size_t& currentSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3293 const std::size_t pairSelectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleTwoPosition : mSelectionHandleOnePosition;
3295 // Get a handle of the selection handle actor
3296 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3298 // Selection handles should jump to the nearest character
3299 std::size_t newHandlePosition = 0;
3300 ReturnClosestIndex( actualSelectionHandlePosition.GetVectorXY(), newHandlePosition );
3302 actualHandlePosition = GetActualPositionFromCharacterPosition( newHandlePosition );
3304 bool handleVisible = true;
3306 if( IsScrollEnabled() )
3308 mCurrentSelectionId = handleId;
3310 cursorSize.height = GetRowRectFromCharacterPosition( GetVisualPosition( newHandlePosition ) ).height;
3311 // Restricts the movement of the grab handle inside the boundaries of the text-input.
3312 handleVisible = IsPositionInsideBoundaries( actualHandlePosition,
3319 mCurrentSelectionHandlePosition = actualHandlePosition;
3320 mScrollDisplacement = Vector2::ZERO;
3324 if( ( actualHandlePosition.x < SCROLL_THRESHOLD ) && ( displacement.x <= 0.f ) )
3326 mScrollDisplacement.x = -SCROLL_SPEED;
3328 else if( ( actualHandlePosition.x > controlSize.width - SCROLL_THRESHOLD ) && ( displacement.x >= 0.f ) )
3330 mScrollDisplacement.x = SCROLL_SPEED;
3332 if( ( actualHandlePosition.y < SCROLL_THRESHOLD ) && ( displacement.y <= 0.f ) )
3334 mScrollDisplacement.y = -SCROLL_SPEED;
3336 else if( ( actualHandlePosition.y > controlSize.height - SCROLL_THRESHOLD ) && ( displacement.y >= 0.f ) )
3338 mScrollDisplacement.y = SCROLL_SPEED;
3344 if ( handleVisible && // Ensure the handle is visible.
3345 ( newHandlePosition != pairSelectionHandlePosition ) && // Ensure handle one is not the same position as handle two.
3346 ( newHandlePosition != currentSelectionHandlePosition ) ) // Ensure the handle has moved.
3348 currentSelectionHandlePosition = newHandlePosition;
3350 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3351 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3355 if ( handleId == HandleOne )
3357 const TextStyle oldInputStyle( mInputStyle );
3359 // Set Active Style to that of first character in selection
3360 if( mSelectionHandleOnePosition < mStyledText.size() )
3362 mInputStyle = ( mStyledText.at( mSelectionHandleOnePosition ) ).mStyle;
3365 if( oldInputStyle != mInputStyle )
3367 // Updates the line height accordingly with the input style.
3370 EmitStyleChangedSignal();
3376 return actualHandlePosition; // Returns Handle position passed in if new value not assigned.
3379 void TextInput::SetSelectionHandlePosition(SelectionHandleId handleId)
3382 const std::size_t selectionHandlePosition = ( handleId == HandleOne ) ? mSelectionHandleOnePosition : mSelectionHandleTwoPosition;
3383 ImageActor selectionHandleActor = ( handleId == HandleOne ) ? mSelectionHandleOne : mSelectionHandleTwo;
3385 if ( selectionHandleActor )
3387 const Vector3 actualHandlePosition = GetActualPositionFromCharacterPosition( selectionHandlePosition );
3388 Vector3 selectionHandleOffset = ( handleId == HandleOne ) ? mSelectionHandleOneOffset : mSelectionHandleTwoOffset;
3389 selectionHandleActor.SetPosition( actualHandlePosition + UI_OFFSET + selectionHandleOffset );
3391 if( IsScrollEnabled() )
3393 const Size cursorSize( CURSOR_THICKNESS,
3394 GetRowRectFromCharacterPosition( GetVisualPosition( selectionHandlePosition ) ).height );
3395 selectionHandleActor.SetVisible( IsPositionInsideBoundaries( actualHandlePosition,
3397 GetControlSize() ) );
3402 std::size_t TextInput::GetVisualPosition(std::size_t logicalPosition) const
3404 // Note: we're allowing caller to request a logical position of size (i.e. end of string)
3405 // For now the visual position of end of logical string will be end of visual string.
3406 DALI_ASSERT_DEBUG( logicalPosition <= mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3408 return logicalPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size() ? mTextLayoutInfo.mCharacterLogicalToVisualMap[logicalPosition] : mTextLayoutInfo.mCharacterLogicalToVisualMap.size();
3411 void TextInput::GetVisualTextSelection(std::vector<bool>& selectedVisualText, std::size_t startSelection, std::size_t endSelection)
3413 std::vector<int>::iterator it = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin();
3414 std::vector<int>::iterator startSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::min(startSelection, endSelection);
3415 std::vector<int>::iterator endSelectionIt = mTextLayoutInfo.mCharacterLogicalToVisualMap.begin() + std::max(startSelection, endSelection);
3416 std::vector<int>::iterator end = mTextLayoutInfo.mCharacterLogicalToVisualMap.end();
3418 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size() );
3420 // Deselect text prior to startSelectionIt
3421 for(;it!=startSelectionIt;++it)
3423 selectedVisualText[*it] = false;
3426 // Select text from startSelectionIt -> endSelectionIt
3427 for(;it!=endSelectionIt;++it)
3429 selectedVisualText[*it] = true;
3432 // Deselect text after endSelection
3435 selectedVisualText[*it] = false;
3438 selectedVisualText.resize( mTextLayoutInfo.mCharacterLogicalToVisualMap.size(), false );
3441 // Calculate the dimensions of the quads they will make the highlight mesh
3442 TextInput::HighlightInfo TextInput::CalculateHighlightInfo()
3444 // At the moment there is no public API to modify the block alignment option.
3445 const bool blockAlignEnabled = true;
3447 mNewHighlightInfo.mQuadList.clear(); // clear last quad information.
3449 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
3451 Toolkit::TextView::CharacterLayoutInfoContainer::iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3452 Toolkit::TextView::CharacterLayoutInfoContainer::iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
3454 // Get vector of flags representing characters that are selected (true) vs unselected (false).
3455 std::vector<bool> selectedVisualText;
3456 GetVisualTextSelection(selectedVisualText, mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
3457 std::vector<bool>::iterator selectedIt(selectedVisualText.begin());
3458 std::vector<bool>::iterator selectedEndIt(selectedVisualText.end());
3460 SelectionState selectionState = SelectionNone; ///< Current selection status of cursor over entire text.
3461 float rowLeft = 0.0f;
3462 float rowRight = 0.0f;
3463 // Keep track of the TextView's min/max extents. Should be able to query this from TextView.
3464 float maxRowLeft = std::numeric_limits<float>::max();
3465 float maxRowRight = 0.0f;
3467 Toolkit::TextView::CharacterLayoutInfoContainer::iterator lastIt = it;
3469 // Scan through entire text.
3472 // selectionState: None when not in selection, Started when in selection, and Ended when reached end of selection.
3474 Toolkit::TextView::CharacterLayoutInfo& charInfo(*it);
3475 bool charSelected( false );
3476 if( selectedIt != selectedEndIt )
3478 charSelected = *selectedIt++;
3481 if(selectionState == SelectionNone)
3485 selectionState = SelectionStarted;
3486 rowLeft = charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3487 rowRight = rowLeft + charInfo.mSize.width;
3490 else if(selectionState == SelectionStarted)
3492 // break selection on:
3493 // 1. new line causing selection break. (\n or wordwrap)
3494 // 2. character not selected.
3495 if(charInfo.mPosition.y - lastIt->mPosition.y > CHARACTER_THRESHOLD ||
3498 // finished selection.
3499 // TODO: TextView should have a table of visual rows, and each character a reference to the row
3500 // that it resides on. That way this enumeration is not necessary.
3502 if(lastIt->mIsNewLineChar)
3504 // 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.
3505 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3507 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3508 maxRowLeft = std::min(maxRowLeft, min.x);
3509 maxRowRight = std::max(maxRowRight, max.x);
3510 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3511 float rowTop = rowBottom - rowSize.height;
3513 // Still selected, and block-align mode then set rowRight to max, so it can be clamped afterwards
3514 if(charSelected && blockAlignEnabled)
3516 rowRight = std::numeric_limits<float>::max();
3518 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3520 selectionState = SelectionNone;
3522 // Still selected? start a new selection
3525 // if block-align mode then set rowLeft to min, so it can be clamped afterwards
3526 rowLeft = blockAlignEnabled ? 0.0f : charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x;
3527 rowRight = ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width;
3528 selectionState = SelectionStarted;
3533 // build up highlight(s) with this selection data.
3534 rowLeft = std::min( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x, rowLeft );
3535 rowRight = std::max( ( charInfo.mPosition.x - mTextLayoutInfo.mScrollOffset.x ) + charInfo.mSize.width, rowRight );
3542 // If reached end, and still on selection, then close selection.
3545 if(selectionState == SelectionStarted)
3547 // finished selection.
3549 if(lastIt->mIsNewLineChar)
3551 lastIt = std::max( mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), lastIt - 1 );
3553 const Size rowSize( GetRowRectFromCharacterPosition( lastIt - mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), min, max ) );
3554 maxRowLeft = std::min(maxRowLeft, min.x);
3555 maxRowRight = std::max(maxRowRight, max.x);
3556 float rowBottom = lastIt->mPosition.y - mTextLayoutInfo.mScrollOffset.y;
3557 float rowTop = rowBottom - rowSize.height;
3558 mNewHighlightInfo.AddQuad( rowLeft, rowTop, rowRight, rowBottom );
3562 // Get the top left and bottom right corners.
3563 const Toolkit::TextView::CharacterLayoutInfo& firstCharacter( *mTextLayoutInfo.mCharacterLayoutInfoTable.begin() );
3564 const Vector2 topLeft( maxRowLeft, firstCharacter.mPosition.y - firstCharacter.mSize.height );
3565 const Vector2 bottomRight( topLeft.x + mTextLayoutInfo.mTextSize.width, topLeft.y + mTextLayoutInfo.mTextSize.height );
3567 // Clamp quads so they appear to clip to borders of the whole text.
3568 mNewHighlightInfo.Clamp2D( topLeft, bottomRight );
3570 // For block-align align Further Clamp quads to max left and right extents
3571 if(blockAlignEnabled)
3573 // BlockAlign: Will adjust highlight to block:
3575 // H[ello] (top row right = max of all rows right)
3576 // [--this-] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3577 // [is some] (middle rows' left = min of all rows left, middle rows' right = max of all rows right)
3578 // [text] (bottom row left = min of all rows left)
3579 // (common in SMS messaging selection)
3581 // As opposed to the default which is tight text highlighting.
3586 // (common in regular text editors/web browser selection)
3588 mNewHighlightInfo.Clamp2D( Vector2(maxRowLeft, topLeft.y), Vector2(maxRowRight, bottomRight.y ) );
3591 // Finally clamp quads again so they don't exceed the boundry of the control.
3592 const Vector3& controlSize = GetControlSize();
3593 mNewHighlightInfo.Clamp2D( Vector2::ZERO, Vector2(controlSize.x, controlSize.y) );
3596 return mNewHighlightInfo;
3599 void TextInput::UpdateHighlight()
3601 // Construct a Mesh with a texture to be used as the highlight 'box' for selected text
3603 // Example scenarios where mesh is made from 3, 1, 2, 2 ,3 or 3 quads.
3605 // [ TOP ] [ TOP ] [TOP ] [ TOP ] [ TOP ] [ TOP ]
3606 // [ MIDDLE ] [BOTTOM] [BOTTOM] [ MIDDLE ] [ MIDDLE ]
3607 // [ BOTTOM] [ MIDDLE ] [ MIDDLE ]
3608 // [BOTTOM] [ MIDDLE ]
3611 // Each quad is created as 2 triangles.
3612 // Middle is just 1 quad regardless of its size.
3626 if ( mHighlightMeshActor )
3628 // vertex and triangle buffers should always be present if MeshActor is alive.
3629 HighlightInfo newHighlightInfo = CalculateHighlightInfo();
3630 MeshData::VertexContainer vertices;
3631 Dali::MeshData::FaceIndices faceIndices;
3633 if( !newHighlightInfo.mQuadList.empty() )
3635 std::vector<QuadCoordinates>::iterator iter = newHighlightInfo.mQuadList.begin();
3636 std::vector<QuadCoordinates>::iterator endIter = newHighlightInfo.mQuadList.end();
3638 // vertex position defaults to (0 0 0)
3639 MeshData::Vertex vertex;
3640 // set normal for all vertices as (0 0 1) pointing outward from TextInput Actor.
3643 for(std::size_t v = 0; iter != endIter; ++iter,v+=4 )
3645 // Add each quad geometry (a sub-selection) to the mesh data.
3655 QuadCoordinates& quad = *iter;
3657 vertex.x = quad.min.x;
3658 vertex.y = quad.min.y;
3659 vertices.push_back( vertex );
3662 vertex.x = quad.max.x;
3663 vertex.y = quad.min.y;
3664 vertices.push_back( vertex );
3666 // bottom-left (v+2)
3667 vertex.x = quad.min.x;
3668 vertex.y = quad.max.y;
3669 vertices.push_back( vertex );
3671 // bottom-right (v+3)
3672 vertex.x = quad.max.x;
3673 vertex.y = quad.max.y;
3674 vertices.push_back( vertex );
3676 // triangle A (3, 1, 0)
3677 faceIndices.push_back( v + 3 );
3678 faceIndices.push_back( v + 1 );
3679 faceIndices.push_back( v );
3681 // triangle B (0, 2, 3)
3682 faceIndices.push_back( v );
3683 faceIndices.push_back( v + 2 );
3684 faceIndices.push_back( v + 3 );
3686 mMeshData.SetFaceIndices( faceIndices );
3689 BoneContainer bones(0); // passed empty as bones not required
3690 mMeshData.SetData( vertices, faceIndices, bones, mCustomMaterial );
3691 mHighlightMesh.UpdateMeshData(mMeshData);
3696 void TextInput::ClearPopup()
3698 mPopUpPanel.Clear();
3701 void TextInput::AddPopupOptions()
3703 mPopUpPanel.AddPopupOptions();
3706 void TextInput::SetPopupPosition(const Vector3& position)
3708 mPopUpPanel.Self().SetPosition( position );
3709 mPopUpPanel.SetTailPosition( position );
3712 void TextInput::HidePopup(bool animate, bool signalFinished )
3714 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
3716 mPopUpPanel.Hide( animate );
3718 if( animate && signalFinished )
3720 mPopUpPanel.HideFinishedSignal().Connect( this, &TextInput::OnPopupHideFinished );
3725 void TextInput::ShowPopup(bool animate)
3729 if(mHighlightMeshActor && mState == StateEdit)
3733 // When text is selected, show popup above top handle (and text), or below bottom handle.
3734 // topHandle: referring to the top most point of the handle or the top line of selection.
3735 if ( mSelectionHandleTwoActualPosition.y > mSelectionHandleOneActualPosition.y )
3737 topHandle = mSelectionHandleOneActualPosition;
3738 rowSize= GetRowRectFromCharacterPosition( mSelectionHandleOnePosition );
3742 topHandle = mSelectionHandleTwoActualPosition;
3743 rowSize = GetRowRectFromCharacterPosition( mSelectionHandleTwoPosition );
3745 topHandle.y += -mPopupOffsetFromText.y - rowSize.height;
3746 position = Vector3(topHandle.x, topHandle.y, 0.0f);
3748 // bottomHandle: referring to the bottom most point of the handle or the bottom line of selection.
3749 Vector3 bottomHandle;
3750 bottomHandle.y = std::max ( mSelectionHandleTwoActualPosition.y , mSelectionHandleOneActualPosition.y );
3751 bottomHandle.y += GetSelectionHandleSize().y + mPopupOffsetFromText.w;
3752 mPopUpPanel.SetAlternativeOffset(Vector2( mBoundingRectangleWorldCoordinates.x, bottomHandle.y - topHandle.y));
3754 float xPosition = ( fabsf( topHandle.x - bottomHandle.x ) )*0.5f + std::min( topHandle.x , bottomHandle.x );
3756 position.x = xPosition;
3760 // When no text is selected, show popup at world position of grab handle or cursor
3761 position = GetActualPositionFromCharacterPosition( mCursorPosition );
3762 const Size rowSize = GetRowRectFromCharacterPosition( mCursorPosition );
3763 position.y -= ( mPopupOffsetFromText.y + rowSize.height );
3764 // if can't be positioned above, then position below row.
3765 Vector2 alternativePopUpPosition( mBoundingRectangleWorldCoordinates.x, position.y ); // default if no grab handle
3768 // If grab handle enabled then position pop-up below the grab handle.
3769 alternativePopUpPosition.y = rowSize.height + mGrabHandle.GetCurrentSize().height + mPopupOffsetFromText.w +50.0f;
3771 mPopUpPanel.SetAlternativeOffset( alternativePopUpPosition );
3774 // reposition popup above the desired cursor posiiton.
3775 Vector3 textViewSize = mDisplayedTextView.GetCurrentSize();
3776 textViewSize.z = 0.0f;
3777 // World position = world position of local position i.e. top-left corner of TextView
3778 Vector3 worldPosition = mDisplayedTextView.GetCurrentWorldPosition() - ( textViewSize * 0.5f ) + position;
3780 SetPopupPosition( worldPosition );
3783 mPopUpPanel.Show(animate);
3784 StartMonitoringStageForTouch();
3786 mPopUpPanel.PressedSignal().Connect( this, &TextInput::OnPopupButtonPressed );
3789 void TextInput::ShowPopupCutCopyPaste()
3793 mPopUpPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3794 // Check the selected text is whole text or not.
3795 if( IsTextSelected() && ( mStyledText.size() != GetSelectedText().size() ) )
3797 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3800 if ( !mStyledText.empty() )
3803 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCopy, true );
3804 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCut, true );
3807 if( mClipboard && mClipboard.NumberOfItems() )
3809 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsPaste, true );
3810 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3815 mPopUpPanel.Hide(false);
3819 void TextInput::SetUpPopUpSelection()
3822 mPopUpPanel.CreateOrderedListOfOptions(); // todo Move this so only run when order has changed
3823 // If no text exists then don't offer to select
3824 if ( !mStyledText.empty() )
3826 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelectAll, true );
3827 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsSelect, true );
3828 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsCut, true );
3830 // if clipboard has valid contents then offer paste option
3831 if( mClipboard && mClipboard.NumberOfItems() )
3833 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsPaste, true );
3834 mPopUpPanel.TogglePopUpButtonOnOff( TextInputPopup::ButtonsClipboard, true );
3839 mPopUpPanel.Hide(false);
3842 bool TextInput::ReturnClosestIndex(const Vector2& source, std::size_t& closestIndex )
3847 std::vector<Toolkit::TextView::CharacterLayoutInfo> matchedCharacters;
3848 bool lastRightToLeftChar(false); /// RTL state of previous character encountered (character on the left of touch point)
3849 bool rightToLeftChar(false); /// RTL state of current character encountered (character on the right of touch point)
3850 float glyphIntersection(0.0f); /// Glyph intersection, the point between the two nearest characters touched.
3852 const Vector2 sourceScrollOffset( source + mTextLayoutInfo.mScrollOffset );
3854 if ( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
3856 float closestYdifference = std::numeric_limits<float>::max();
3857 std::size_t lineOffset = 0; /// Keep track of position of the first character on the matched line of interest.
3858 std::size_t numberOfMatchedCharacters = 0;
3860 // 1. Find closest character line to y part of source, create vector of all entries in that Y position
3861 // TODO: There should be an easy call to enumerate through each visual line, instead of each character on all visual lines.
3863 for( std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin(), endIt = mTextLayoutInfo.mCharacterLayoutInfoTable.end(); it != endIt; ++it )
3865 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3866 float baselinePosition = info.mPosition.y - info.mDescender;
3868 if( info.mIsVisible )
3870 // store difference between source y point and the y position of the current character
3871 float currentYdifference = fabsf( sourceScrollOffset.y - ( baselinePosition ) );
3873 if( currentYdifference < closestYdifference )
3875 // closest so far; store this difference and clear previous matchedCharacters as no longer closest
3876 lineOffset = it - mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
3877 closestYdifference = currentYdifference;
3878 matchedCharacters.clear();
3879 numberOfMatchedCharacters = 0; // reset count
3882 // add all characters that are on the same Y axis (within the CHARACTER_THRESHOLD) to the matched array.
3883 if( fabsf( closestYdifference - currentYdifference ) < CHARACTER_THRESHOLD )
3885 // ignore new line character.
3886 if( !info.mIsNewLineChar )
3888 matchedCharacters.push_back( info );
3889 numberOfMatchedCharacters++;
3893 } // End of loop checking each character's y position in the character layout table
3895 // Check if last character is a newline, if it is
3896 // then need pretend there is an imaginary line afterwards,
3897 // and check if user is touching below previous line.
3898 const Toolkit::TextView::CharacterLayoutInfo& lastInfo( mTextLayoutInfo.mCharacterLayoutInfoTable[mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1] );
3900 if( ( lastInfo.mIsVisible ) && ( lastInfo.mIsNewLineChar ) && ( sourceScrollOffset.y > lastInfo.mPosition.y ) )
3902 closestIndex = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
3906 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator it = matchedCharacters.begin();
3907 std::vector<Toolkit::TextView::CharacterLayoutInfo>::const_iterator endIt = matchedCharacters.end();
3909 bool matched( false );
3911 // 2 Iterate through matching list of y positions and find closest matching X position.
3912 for( ; it != endIt; ++it )
3914 const Toolkit::TextView::CharacterLayoutInfo& info( *it );
3916 if( info.mIsVisible )
3918 // stop when on left side of character's center.
3919 const float characterMidPointPosition = info.mPosition.x + ( info.mSize.width * 0.5f ) ;
3920 if( sourceScrollOffset.x < characterMidPointPosition )
3922 if(info.mIsRightToLeftCharacter)
3924 rightToLeftChar = true;
3926 glyphIntersection = info.mPosition.x;
3931 lastRightToLeftChar = info.mIsRightToLeftCharacter;
3937 rightToLeftChar = lastRightToLeftChar;
3940 std::size_t matchCharacterIndex = it - matchedCharacters.begin();
3941 closestIndex = lineOffset + matchCharacterIndex;
3943 mClosestCursorPositionEOL = false; // reset
3944 if ( it == endIt && !matched )
3946 mClosestCursorPositionEOL = true; // Reached end of matched characters in closest line but no match so cursor should be after last character.
3949 // For RTL characters, need to adjust closestIndex by 1 (as the inequality above would be reverse)
3950 if( rightToLeftChar && lastRightToLeftChar )
3952 --closestIndex; // (-1 = numeric_limits<std::size_t>::max())
3957 // closestIndex is the visual index, need to convert it to the logical index
3958 if( !mTextLayoutInfo.mCharacterVisualToLogicalMap.empty() )
3960 if( closestIndex < mTextLayoutInfo.mCharacterVisualToLogicalMap.size() )
3962 // Checks for situations where user is touching between LTR and RTL
3963 // characters. To identify if the user means the end of a LTR string
3964 // or the beginning of an RTL string, and vice versa.
3965 if( closestIndex > 0 )
3967 if( rightToLeftChar && !lastRightToLeftChar )
3972 // A: In this touch range, the user is indicating that they wish to place
3973 // the cursor at the end of the LTR text.
3974 // B: In this touch range, the user is indicating that they wish to place
3975 // the cursor at the end of the RTL text.
3977 // Result of touching A area:
3978 // [.....LTR]|[RTL......]+
3980 // |: primary cursor (for typing LTR chars)
3981 // +: secondary cursor (for typing RTL chars)
3983 // Result of touching B area:
3984 // [.....LTR]+[RTL......]|
3986 // |: primary cursor (for typing RTL chars)
3987 // +: secondary cursor (for typing LTR chars)
3989 if( sourceScrollOffset.x < glyphIntersection )
3994 else if( !rightToLeftChar && lastRightToLeftChar )
3996 if( sourceScrollOffset.x < glyphIntersection )
4003 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap[closestIndex];
4004 // If user touched a left-side of RTL char, and the character on the left was an LTR then position logical cursor
4005 // one further ahead
4006 if( rightToLeftChar && !lastRightToLeftChar )
4011 else if( closestIndex == numeric_limits<std::size_t>::max() ) // -1 RTL (after last arabic character on line)
4013 closestIndex = mTextLayoutInfo.mCharacterVisualToLogicalMap.size();
4015 else if( mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterVisualToLogicalMap[ closestIndex - 1 ] ].mIsRightToLeftCharacter ) // size() LTR (after last european character on line)
4024 float TextInput::GetLineJustificationPosition() const
4026 const Vector3& size = mDisplayedTextView.GetCurrentSize();
4027 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4028 float alignmentOffset = 0.f;
4030 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4031 if( alignment & Toolkit::Alignment::HorizontalLeft )
4033 alignmentOffset = 0.f;
4035 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4037 alignmentOffset = 0.5f * ( size.width - mTextLayoutInfo.mTextSize.width );
4039 else if( alignment & Toolkit::Alignment::HorizontalRight )
4041 alignmentOffset = size.width - mTextLayoutInfo.mTextSize.width;
4044 Toolkit::TextView::LineJustification justification = mDisplayedTextView.GetLineJustification();
4045 float justificationOffset = 0.f;
4047 switch( justification )
4049 case Toolkit::TextView::Left:
4051 justificationOffset = 0.f;
4054 case Toolkit::TextView::Center:
4056 justificationOffset = 0.5f * mTextLayoutInfo.mTextSize.width;
4059 case Toolkit::TextView::Right:
4061 justificationOffset = mTextLayoutInfo.mTextSize.width;
4064 case Toolkit::TextView::Justified:
4066 justificationOffset = 0.f;
4071 DALI_ASSERT_ALWAYS( false );
4075 return alignmentOffset + justificationOffset;
4078 Vector3 TextInput::PositionCursorAfterWordWrap( std::size_t characterPosition ) const
4080 /* Word wrap occurs automatically in TextView when the exceed policy moves a word to the next line when not enough space on current.
4081 A newline character is not inserted in this case */
4083 DALI_ASSERT_DEBUG( !(characterPosition <= 0 ));
4085 Vector3 cursorPosition;
4087 Toolkit::TextView::CharacterLayoutInfo currentCharInfo;
4089 if ( characterPosition == mTextLayoutInfo.mCharacterLayoutInfoTable.size() )
4091 // end character so use
4092 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1 ];
4093 cursorPosition = Vector3(currentCharInfo.mPosition.x + currentCharInfo.mSize.width, currentCharInfo.mPosition.y, currentCharInfo.mPosition.z) ;
4097 currentCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition ];
4100 Toolkit::TextView::CharacterLayoutInfo previousCharInfo = mTextLayoutInfo.mCharacterLayoutInfoTable[ characterPosition - 1];
4102 // If previous character on a different line then use current characters position
4103 if ( fabsf( (currentCharInfo.mPosition.y - currentCharInfo.mDescender ) - ( previousCharInfo.mPosition.y - previousCharInfo.mDescender) ) > Math::MACHINE_EPSILON_1000 )
4105 if ( mClosestCursorPositionEOL )
4107 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4111 cursorPosition = Vector3(currentCharInfo.mPosition);
4116 // Previous character is on same line so use position of previous character plus it's width.
4117 cursorPosition = Vector3(previousCharInfo.mPosition.x + previousCharInfo.mSize.width, previousCharInfo.mPosition.y, previousCharInfo.mPosition.z) ;
4120 return cursorPosition;
4123 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition) const
4125 bool direction(false);
4126 Vector3 alternatePosition;
4127 bool alternatePositionValid(false);
4129 return GetActualPositionFromCharacterPosition( characterPosition, direction, alternatePosition, alternatePositionValid );
4132 Vector3 TextInput::GetActualPositionFromCharacterPosition(std::size_t characterPosition, bool& directionRTL, Vector3& alternatePosition, bool& alternatePositionValid ) const
4134 Vector3 cursorPosition( 0.f, 0.f, 0.f );
4136 alternatePositionValid = false;
4137 directionRTL = false;
4139 if( !mTextLayoutInfo.mCharacterLayoutInfoTable.empty() && !mTextLayoutInfo.mCharacterLogicalToVisualMap.empty() )
4141 std::size_t visualCharacterPosition;
4143 // When cursor is not at beginning, consider possibility of
4144 // showing 2 cursors. (whereas at beginning we only ever show one cursor)
4145 if(characterPosition > 0)
4147 // Cursor position should be the end of the last character.
4148 // If the last character is LTR, then the end is on the right side of the glyph.
4149 // If the last character is RTL, then the end is on the left side of the glyph.
4150 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition - 1 ];
4152 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4154 visualCharacterPosition = FindVisibleCharacter( Left, visualCharacterPosition );
4157 Toolkit::TextView::CharacterLayoutInfo info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4158 if( ( visualCharacterPosition > 0 ) && info.mIsNewLineChar && !IsScrollEnabled() )
4160 // Prevents the cursor to exceed the boundary if the last visible character is a 'new line character' and the scroll is not enabled.
4161 const Vector3& size = GetControlSize();
4163 if( info.mPosition.y + info.mSize.height - mDisplayedTextView.GetLineHeightOffset() > size.height )
4165 --visualCharacterPosition;
4167 info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4170 if(!info.mIsNewLineChar)
4172 cursorPosition = PositionCursorAfterWordWrap( characterPosition ); // Get position of cursor/handles taking in account auto word wrap.
4176 // When cursor points to first character on new line, position cursor at the start of this glyph.
4177 if(characterPosition < mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4179 std::size_t visualCharacterNextPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4180 const Toolkit::TextView::CharacterLayoutInfo& infoNext = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterNextPosition ];
4181 const float start( infoNext.mIsRightToLeftCharacter ? infoNext.mSize.width : 0.0f );
4183 cursorPosition.x = infoNext.mPosition.x + start;
4184 cursorPosition.y = infoNext.mPosition.y;
4188 // If cursor points to the end of text, then can only position
4189 // cursor where the new line starts based on the line-justification position.
4190 cursorPosition.x = GetLineJustificationPosition();
4192 if(characterPosition == mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4194 // If this is after the last character, then we can assume that the new cursor
4195 // should be exactly one row below the current row.
4197 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition - 1));
4198 cursorPosition.y = info.mPosition.y + rowRect.height;
4202 // If this is not after last character, then we can use this row's height.
4203 // should be exactly one row below the current row.
4205 const Size rowRect(GetRowRectFromCharacterPosition(characterPosition));
4206 cursorPosition.y = info.mPosition.y + rowRect.height;
4211 directionRTL = info.mIsRightToLeftCharacter;
4213 // 1. When the cursor is neither at the beginning or the end,
4214 // we can show multiple cursors under situations when the cursor is
4215 // between RTL and LTR text...
4216 if(characterPosition != mTextLayoutInfo.mCharacterLogicalToVisualMap.size())
4218 std::size_t visualCharacterAltPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[characterPosition] - 1;
4220 DALI_ASSERT_ALWAYS(visualCharacterAltPosition < mTextLayoutInfo.mCharacterLayoutInfoTable.size());
4221 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterAltPosition ];
4223 if(!info.mIsRightToLeftCharacter && infoAlt.mIsRightToLeftCharacter)
4225 // Stuation occurs when cursor is at the end of English text (LTR) and beginning of Arabic (RTL)
4226 // Text: [...LTR...]|[...RTL...]
4228 // Alternate cursor pos: ^
4229 // In which case we need to display an alternate cursor for the RTL text.
4231 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4232 alternatePosition.y = infoAlt.mPosition.y;
4233 alternatePositionValid = true;
4235 else if(info.mIsRightToLeftCharacter && !infoAlt.mIsRightToLeftCharacter)
4237 // Situation occurs when cursor is at end of the Arabic text (LTR) and beginning of English (RTL)
4238 // Text: |[...RTL...] [...LTR....]
4240 // Alternate cursor pos: ^
4241 // In which case we need to display an alternate cursor for the RTL text.
4243 alternatePosition.x = infoAlt.mPosition.x;
4244 alternatePosition.y = infoAlt.mPosition.y;
4245 alternatePositionValid = true;
4250 // 2. When the cursor is at the end of the text,
4251 // and we have multi-directional text,
4252 // we can also consider showing mulitple cursors.
4253 // The rule here is:
4254 // If first and last characters on row are different
4255 // Directions, then two cursors need to be displayed.
4257 // Get first logical glyph on row
4258 std::size_t startCharacterPosition = GetRowStartFromCharacterPosition( characterPosition - 1 );
4260 std::size_t visualCharacterStartPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ startCharacterPosition ];
4261 const Toolkit::TextView::CharacterLayoutInfo& infoStart= mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterStartPosition ];
4263 if(info.mIsRightToLeftCharacter && !infoStart.mIsRightToLeftCharacter)
4265 // For text Starting as LTR and ending as RTL. End cursor position is as follows:
4266 // Text: [...LTR...]|[...RTL...]
4268 // Alternate cursor pos: ^
4269 // In which case we need to display an alternate cursor for the RTL text, this cursor
4270 // should be at the end of the given line.
4272 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1 ];
4273 alternatePosition.x = infoAlt.mPosition.x + infoAlt.mSize.width;
4274 alternatePosition.y = infoAlt.mPosition.y;
4275 alternatePositionValid = true;
4277 else if(!info.mIsRightToLeftCharacter && infoStart.mIsRightToLeftCharacter) // starting RTL
4279 // For text Starting as RTL and ending as LTR. End cursor position is as follows:
4280 // Text: |[...RTL...] [...LTR....]
4282 // Alternate cursor pos: ^
4283 // In which case we need to display an alternate cursor for the RTL text.
4285 const Toolkit::TextView::CharacterLayoutInfo& infoAlt = mTextLayoutInfo.mCharacterLayoutInfoTable[ startCharacterPosition ];
4286 alternatePosition.x = infoAlt.mPosition.x;
4287 alternatePosition.y = infoAlt.mPosition.y;
4288 alternatePositionValid = true;
4291 } // characterPosition > 0
4292 else if(characterPosition == 0)
4294 // When the cursor position is at the beginning, it should be at the start of the current character.
4295 // If the current character is LTR, then the start is on the right side of the glyph.
4296 // If the current character is RTL, then the start is on the left side of the glyph.
4297 visualCharacterPosition = mTextLayoutInfo.mCharacterLogicalToVisualMap[ characterPosition ];
4299 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + visualCharacterPosition ) ).mIsVisible )
4301 visualCharacterPosition = FindVisibleCharacter( Right, visualCharacterPosition );
4304 const Toolkit::TextView::CharacterLayoutInfo& info = mTextLayoutInfo.mCharacterLayoutInfoTable[ visualCharacterPosition ];
4305 const float start(info.mIsRightToLeftCharacter ? info.mSize.width : 0.0f);
4307 cursorPosition.x = info.mPosition.x + start;
4308 cursorPosition.y = info.mPosition.y;
4309 directionRTL = info.mIsRightToLeftCharacter;
4314 // If the character table is void, place the cursor accordingly the text alignment.
4315 const Vector3& size = GetControlSize();
4317 Toolkit::Alignment::Type alignment = mDisplayedTextView.GetTextAlignment();
4318 float alignmentOffset = 0.f;
4320 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4321 if( alignment & Toolkit::Alignment::HorizontalLeft )
4323 alignmentOffset = 0.f;
4325 else if( alignment & Toolkit::Alignment::HorizontalCenter )
4327 alignmentOffset = 0.5f * ( size.width );
4329 else if( alignment & Toolkit::Alignment::HorizontalRight )
4331 alignmentOffset = size.width;
4334 // Work out cursor 'x' position when there are any character accordingly with the text view alignment settings.
4335 cursorPosition.x = alignmentOffset;
4337 // Work out cursor 'y' position when there are any character accordingly with the text view alignment settings.
4338 if( alignment & Toolkit::Alignment::VerticalTop )
4340 cursorPosition.y = mLineHeight;
4342 else if( alignment & Toolkit::Alignment::VerticalCenter )
4344 cursorPosition.y = 0.5f * ( size.height + mLineHeight );
4346 else if( alignment & Toolkit::Alignment::VerticalBottom )
4348 cursorPosition.y = size.height;
4352 cursorPosition.x -= mTextLayoutInfo.mScrollOffset.x;
4353 cursorPosition.y -= mTextLayoutInfo.mScrollOffset.y;
4354 if( alternatePositionValid )
4356 alternatePosition.x -= mTextLayoutInfo.mScrollOffset.x;
4357 alternatePosition.y -= mTextLayoutInfo.mScrollOffset.y;
4360 return cursorPosition;
4363 std::size_t TextInput::GetRowStartFromCharacterPosition(std::size_t logicalPosition) const
4365 // scan string from current position to beginning of current line to note direction of line
4366 while(logicalPosition)
4369 std::size_t visualPosition = GetVisualPosition(logicalPosition);
4370 if(mTextLayoutInfo.mCharacterLayoutInfoTable[visualPosition].mIsNewLineChar)
4377 return logicalPosition;
4380 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition) const
4384 return GetRowRectFromCharacterPosition( characterPosition, min, max );
4387 Size TextInput::GetRowRectFromCharacterPosition(std::size_t characterPosition, Vector2& min, Vector2& max) const
4389 // if we have no text content, then return position 0,0 with width 0, and height the same as cursor height.
4390 if( mTextLayoutInfo.mCharacterLayoutInfoTable.empty() )
4392 min = Vector2::ZERO;
4393 max = Vector2(0.0f, mLineHeight);
4397 // TODO: This info should be readily available from text-view, we should not have to search hard for it.
4398 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator begin = mTextLayoutInfo.mCharacterLayoutInfoTable.begin();
4399 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator end = mTextLayoutInfo.mCharacterLayoutInfoTable.end();
4401 // If cursor is pointing to end of line, then start from last character.
4402 characterPosition = std::min( characterPosition, static_cast<std::size_t>(mTextLayoutInfo.mCharacterLayoutInfoTable.size() - 1) );
4404 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4406 // 0. Find first a visible character. Draw a cursor beyound text-input bounds is not wanted.
4407 if( !it->mIsVisible )
4409 characterPosition = FindVisibleCharacter( Left, characterPosition );
4410 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4413 // Scan characters left and right of cursor, stopping when end of line/string reached or
4414 // y position greater than threshold of reference line.
4416 // 1. scan left until we reach the beginning or a different line.
4417 Toolkit::TextView::CharacterLayoutInfoContainer::const_iterator validCharIt = it;
4418 float referenceLine = it->mPosition.y - CHARACTER_THRESHOLD;
4419 // min-x position is the left-most char's left (x)
4420 // max-x position is the right-most char's right (x)
4421 // min-y position is the minimum of all character's top (y)
4422 // max-y position is the maximum of all character's bottom (y+height)
4423 min.y = validCharIt->mPosition.y;
4424 max.y = validCharIt->mPosition.y + validCharIt->mSize.y;
4429 min.y = std::min(min.y, validCharIt->mPosition.y);
4430 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4439 if( (it->mPosition.y < referenceLine) ||
4440 (it->mIsNewLineChar) ||
4447 // info refers to the first character on this line.
4448 min.x = validCharIt->mPosition.x;
4450 // 2. scan right until we reach end or a different line
4451 it = mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + characterPosition;
4452 referenceLine = it->mPosition.y + CHARACTER_THRESHOLD;
4456 if( (it->mPosition.y > referenceLine) ||
4457 (it->mIsNewLineChar) ||
4464 min.y = std::min(min.y, validCharIt->mPosition.y);
4465 max.y = std::max(max.y, validCharIt->mPosition.y + validCharIt->mSize.y);
4470 DALI_ASSERT_DEBUG ( validCharIt != end && "validCharIt invalid")
4472 if ( validCharIt != end )
4474 // info refers to the last character on this line.
4475 max.x = validCharIt->mPosition.x + validCharIt->mSize.x;
4478 return Size( max.x - min.x, max.y - min.y );
4481 bool TextInput::WasTouchedCheck( const Actor& touchedActor ) const
4483 Actor popUpPanel = mPopUpPanel.GetRootActor();
4485 if ( ( touchedActor == Self() ) || ( touchedActor == popUpPanel ) )
4491 Dali::Actor parent( touchedActor.GetParent() );
4495 return WasTouchedCheck( parent );
4502 void TextInput::StartMonitoringStageForTouch()
4504 Stage stage = Stage::GetCurrent();
4505 stage.TouchedSignal().Connect( this, &TextInput::OnStageTouched );
4508 void TextInput::EndMonitoringStageForTouch()
4510 Stage stage = Stage::GetCurrent();
4511 stage.TouchedSignal().Disconnect( this, &TextInput::OnStageTouched );
4514 void TextInput::OnStageTouched(const TouchEvent& event)
4516 if( event.GetPointCount() > 0 )
4518 if ( TouchPoint::Down == event.GetPoint(0).state )
4520 const Actor touchedActor(event.GetPoint(0).hitActor);
4522 bool popUpShown( false );
4524 if ( ( mPopUpPanel.GetState() == TextInputPopup::StateShowing ) || ( mPopUpPanel.GetState() == TextInputPopup::StateShown ) )
4529 bool textInputTouched = (touchedActor && WasTouchedCheck( touchedActor ));
4531 if ( ( mHighlightMeshActor || popUpShown ) && !textInputTouched )
4533 EndMonitoringStageForTouch();
4534 HidePopup( true, false );
4537 if ( ( IsGrabHandleEnabled() && mGrabHandle ) && !textInputTouched )
4539 EndMonitoringStageForTouch();
4540 ShowGrabHandleAndSetVisibility( false );
4546 void TextInput::SelectText(std::size_t start, std::size_t end)
4548 DALI_LOG_INFO(gLogFilter, Debug::General, "SelectText mEditModeActive[%s] grabHandle[%s] start[%u] end[%u] size[%u]\n", mEditModeActive?"true":"false",
4549 IsGrabHandleEnabled()?"true":"false",
4550 start, end, mTextLayoutInfo.mCharacterLayoutInfoTable.size() );
4551 DALI_ASSERT_ALWAYS( start <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText start out of max range" );
4552 DALI_ASSERT_ALWAYS( end <= mTextLayoutInfo.mCharacterLayoutInfoTable.size() && "TextInput::SelectText end out of max range" );
4554 StartMonitoringStageForTouch();
4556 if ( mEditModeActive ) // Only allow text selection when in edit mode
4558 // When replacing highlighted text keyboard should ignore current word at cursor hence notify keyboard that the cursor is at the start of the highlight.
4559 mSelectingText = true;
4561 mCursorPosition = std::min( start, end ); // Set cursor position to start of highlighted text.
4563 ImfManager imfManager = ImfManager::Get();
4566 imfManager.SetCursorPosition ( mCursorPosition );
4567 imfManager.SetSurroundingText( GetText() );
4568 imfManager.NotifyCursorPosition();
4570 // As the imfManager has been notified of the new cursor position we do not need to reset the pre-edit as it will be updated instead.
4572 // Hide grab handle when selecting.
4573 ShowGrabHandleAndSetVisibility( false );
4575 if( start != end ) // something to select
4577 SetCursorVisibility( false );
4578 StopCursorBlinkTimer();
4580 CreateSelectionHandles(start, end);
4583 const TextStyle oldInputStyle( mInputStyle );
4584 mInputStyle = GetStyleAt( mCursorPosition ); // Inherit style from selected position.
4586 if( oldInputStyle != mInputStyle )
4588 // Updates the line height accordingly with the input style.
4591 EmitStyleChangedSignal();
4597 mSelectingText = false;
4601 MarkupProcessor::StyledTextArray TextInput::GetSelectedText()
4603 MarkupProcessor::StyledTextArray currentSelectedText;
4605 if ( IsTextSelected() )
4607 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4608 MarkupProcessor::StyledTextArray::iterator end = mStyledText.begin() + std::max(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4610 for(; it != end; ++it)
4612 MarkupProcessor::StyledText& styledText( *it );
4613 currentSelectedText.push_back( styledText );
4616 return currentSelectedText;
4619 void TextInput::ApplyStyleToRange(const TextStyle& style, const TextStyle::Mask mask, const std::size_t begin, const std::size_t end)
4621 const std::size_t beginIndex = std::min( begin, end );
4622 const std::size_t endIndex = std::max( begin, end );
4625 MarkupProcessor::SetTextStyleToRange( mStyledText, style, mask, beginIndex, endIndex );
4627 // Create a styled text array used to replace the text into the text-view.
4628 MarkupProcessor::StyledTextArray text;
4629 text.insert( text.begin(), mStyledText.begin() + beginIndex, mStyledText.begin() + endIndex + 1 );
4631 mDisplayedTextView.ReplaceTextFromTo( beginIndex, ( endIndex - beginIndex ) + 1, text );
4632 GetTextLayoutInfo();
4634 if( IsScrollEnabled() )
4636 // Need to set the scroll position as the text's size may have changed.
4637 ScrollTextViewToMakeCursorVisible( Vector3( mTextLayoutInfo.mScrollOffset.x, mTextLayoutInfo.mScrollOffset.y, 0.f ) );
4640 ShowGrabHandleAndSetVisibility( false );
4646 // Set Handle positioning as the new style may have repositioned the characters.
4647 SetSelectionHandlePosition(HandleOne);
4648 SetSelectionHandlePosition(HandleTwo);
4651 void TextInput::KeyboardStatusChanged(bool keyboardShown)
4653 // Just hide the grab handle when keyboard is hidden.
4654 if (!keyboardShown )
4656 ShowGrabHandleAndSetVisibility( false );
4658 // If the keyboard is not now being shown, then hide the popup panel
4659 mPopUpPanel.Hide( true );
4663 // Removes highlight and resumes edit mode state
4664 void TextInput::RemoveHighlight()
4666 DALI_LOG_INFO(gLogFilter, Debug::General, "RemoveHighlight\n");
4668 if ( mHighlightMeshActor )
4670 if ( mSelectionHandleOne )
4672 mActiveLayer.Remove( mSelectionHandleOne );
4673 mSelectionHandleOne.Reset();
4674 mSelectionHandleOneOffset.x = 0.0f;
4676 if ( mSelectionHandleTwo )
4678 mActiveLayer.Remove( mSelectionHandleTwo );
4679 mSelectionHandleTwo.Reset();
4680 mSelectionHandleTwoOffset.x = 0.0f;
4683 mNewHighlightInfo.mQuadList.clear();
4685 Self().Remove( mHighlightMeshActor );
4687 SetCursorVisibility( true );
4688 StartCursorBlinkTimer();
4690 mHighlightMeshActor.Reset();
4691 // NOTE: We cannot dereference mHighlightMesh, due
4692 // to a bug in how the scene-graph MeshRenderer uses the Mesh data incorrectly.
4697 mSelectionHandleOnePosition = 0;
4698 mSelectionHandleTwoPosition = 0;
4701 void TextInput::CreateHighlight()
4703 if ( !mHighlightMeshActor )
4705 mMeshData = MeshData( );
4706 mMeshData.SetHasNormals( true );
4708 mCustomMaterial = Material::New("CustomMaterial");
4709 mCustomMaterial.SetDiffuseColor( mMaterialColor );
4711 mMeshData.SetMaterial( mCustomMaterial );
4713 mHighlightMesh = Mesh::New( mMeshData );
4715 mHighlightMeshActor = MeshActor::New( mHighlightMesh );
4716 mHighlightMeshActor.SetName( "HighlightMeshActor" );
4717 mHighlightMeshActor.SetInheritShaderEffect( false );
4718 mHighlightMeshActor.SetParentOrigin( ParentOrigin::TOP_LEFT );
4719 mHighlightMeshActor.SetAnchorPoint( AnchorPoint::TOP_LEFT );
4720 mHighlightMeshActor.SetPosition( 0.0f, 0.0f, DISPLAYED_HIGHLIGHT_Z_OFFSET );
4721 mHighlightMeshActor.SetAffectedByLighting(false);
4723 Self().Add(mHighlightMeshActor);
4728 bool TextInput::CopySelectedTextToClipboard()
4730 mCurrentCopySelecton.clear();
4732 mCurrentCopySelecton = GetSelectedText();
4734 std::string stringToStore;
4736 /* Create a StyledTextArray from the selected region so can use the MarkUpProcessor to produce
4737 * a marked up string.
4739 MarkupProcessor::StyledTextArray selectedText(mCurrentCopySelecton.begin(),mCurrentCopySelecton.end());
4740 MarkupProcessor::GetPlainString( selectedText, stringToStore );
4741 bool success = mClipboard.SetItem( stringToStore );
4745 void TextInput::PasteText( const Text& text )
4747 // Update Flag, indicates whether to update the text-input contents or not.
4748 // Any key stroke that results in a visual change of the text-input should
4749 // set this flag to true.
4750 bool update = false;
4751 if( mHighlightMeshActor )
4753 /* if highlighted, delete entire text, and position cursor at start of deleted text. */
4754 mCursorPosition = std::min(mSelectionHandleOnePosition, mSelectionHandleTwoPosition);
4756 ImfManager imfManager = ImfManager::Get();
4759 imfManager.SetCursorPosition( mCursorPosition );
4760 imfManager.NotifyCursorPosition();
4762 DeleteHighlightedText( true );
4766 bool textExceedsMaximunNumberOfCharacters = false;
4767 bool textExceedsBoundary = false;
4769 std::size_t insertedStringLength = DoInsertAt( text, mCursorPosition, 0, textExceedsMaximunNumberOfCharacters, textExceedsBoundary );
4771 mCursorPosition += insertedStringLength;
4772 ImfManager imfManager = ImfManager::Get();
4775 imfManager.SetCursorPosition ( mCursorPosition );
4776 imfManager.NotifyCursorPosition();
4779 update = update || ( insertedStringLength > 0 );
4785 if( insertedStringLength < text.GetLength() )
4787 EmitMaxInputCharactersReachedSignal();
4790 if( textExceedsBoundary )
4792 EmitInputTextExceedsBoundariesSignal();
4796 void TextInput::SetTextDirection()
4798 // Put the cursor to the right if we are empty and an RTL language is being used.
4799 if ( mStyledText.empty() )
4801 VirtualKeyboard::TextDirection direction( VirtualKeyboard::GetTextDirection() );
4803 // Get the current text alignment preserving the vertical alignment. Also preserve the horizontal center
4804 // alignment as we do not want to set the text direction if we've been asked to be in the center.
4806 // TODO: Should split SetTextAlignment into two APIs to better handle this (sometimes apps just want to
4807 // set vertical alignment but are being forced to set the horizontal alignment as well with the
4809 int alignment( mDisplayedTextView.GetTextAlignment() &
4810 ( Toolkit::Alignment::VerticalTop |
4811 Toolkit::Alignment::VerticalCenter |
4812 Toolkit::Alignment::VerticalBottom |
4813 Toolkit::Alignment::HorizontalCenter ) );
4814 Toolkit::TextView::LineJustification justification( mDisplayedTextView.GetLineJustification() );
4816 // If our alignment is in the center, then do not change.
4817 if ( !( alignment & Toolkit::Alignment::HorizontalCenter ) )
4819 alignment |= ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::Alignment::HorizontalLeft : Toolkit::Alignment::HorizontalRight;
4822 // If our justification is in the center, then do not change.
4823 if ( justification != Toolkit::TextView::Center )
4825 justification = ( direction == VirtualKeyboard::LeftToRight ) ? Toolkit::TextView::Left : Toolkit::TextView::Right;
4828 mDisplayedTextView.SetTextAlignment( static_cast<Toolkit::Alignment::Type>(alignment) );
4829 mDisplayedTextView.SetLineJustification( justification );
4833 void TextInput::UpdateLineHeight()
4835 Dali::Font font = Dali::Font::New( FontParameters( mInputStyle.GetFontName(), mInputStyle.GetFontStyle(), mInputStyle.GetFontPointSize() ) );
4836 mLineHeight = font.GetLineHeight();
4838 // 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.
4840 const bool shrink = mDisplayedTextView && ( Toolkit::TextView::ShrinkToFit == mDisplayedTextView.GetHeightExceedPolicy() ) && mStyledText.empty();
4842 if( !mExceedEnabled || shrink )
4844 mLineHeight = std::min( mLineHeight, GetControlSize().height );
4848 std::size_t TextInput::FindVisibleCharacter( const FindVisibleCharacterDirection direction , const std::size_t cursorPosition ) const
4850 std::size_t position = 0;
4852 const std::size_t tableSize = mTextLayoutInfo.mCharacterLayoutInfoTable.size();
4858 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4860 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4862 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4868 position = FindVisibleCharacterRight( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4869 if( !( *( mTextLayoutInfo.mCharacterLayoutInfoTable.begin() + ( tableSize == position ? position - 1 : position ) ) ).mIsVisible )
4871 position = FindVisibleCharacterLeft( cursorPosition, mTextLayoutInfo.mCharacterLayoutInfoTable );
4877 position = FindVisibleCharacterLeft( 0, mTextLayoutInfo.mCharacterLayoutInfoTable );
4882 DALI_ASSERT_ALWAYS( !"TextInput::FindVisibleCharacter() Unknown direction." );
4889 void TextInput::SetSortModifier( float depthOffset )
4891 if(mDisplayedTextView)
4893 mDisplayedTextView.SetSortModifier(depthOffset);
4897 void TextInput::SetSnapshotModeEnabled( bool enable )
4899 if(mDisplayedTextView)
4901 mDisplayedTextView.SetSnapshotModeEnabled( enable );
4905 bool TextInput::IsSnapshotModeEnabled() const
4907 bool snapshotEnabled = false;
4909 if(mDisplayedTextView)
4911 snapshotEnabled = mDisplayedTextView.IsSnapshotModeEnabled();
4914 return snapshotEnabled;
4917 void TextInput::SetMarkupProcessingEnabled( bool enable )
4919 mMarkUpEnabled = enable;
4922 bool TextInput::IsMarkupProcessingEnabled() const
4924 return mMarkUpEnabled;
4927 void TextInput::SetScrollEnabled( bool enable )
4929 if( mDisplayedTextView )
4931 mDisplayedTextView.SetScrollEnabled( enable );
4936 // Don't set cursor's and handle's visibility to false if they are outside the
4937 // boundaries of the text-input.
4938 mIsCursorInScrollArea = true;
4939 mIsGrabHandleInScrollArea = true;
4940 if( mSelectionHandleOne && mSelectionHandleTwo )
4942 mSelectionHandleOne.SetVisible( true );
4943 mSelectionHandleTwo.SetVisible( true );
4945 if( mHighlightMeshActor )
4947 mHighlightMeshActor.SetVisible( true );
4953 bool TextInput::IsScrollEnabled() const
4955 bool scrollEnabled = false;
4957 if( mDisplayedTextView )
4959 scrollEnabled = mDisplayedTextView.IsScrollEnabled();
4962 return scrollEnabled;
4965 void TextInput::SetScrollPosition( const Vector2& position )
4967 if( mDisplayedTextView )
4969 mDisplayedTextView.SetScrollPosition( position );
4973 Vector2 TextInput::GetScrollPosition() const
4975 Vector2 scrollPosition;
4977 if( mDisplayedTextView )
4979 scrollPosition = mDisplayedTextView.GetScrollPosition();
4982 return scrollPosition;
4985 std::size_t TextInput::DoInsertAt( const Text& text, const std::size_t position, const std::size_t numberOfCharactersToReplace, bool& textExceedsMaximunNumberOfCharacters, bool& textExceedsBoundary )
4987 // determine number of characters that we can write to style text buffer, this is the insertStringLength
4988 std::size_t insertedStringLength = std::min( text.GetLength(), mMaxStringLength - mStyledText.size() );
4989 textExceedsMaximunNumberOfCharacters = insertedStringLength < text.GetLength();
4991 // Add style to the new input text.
4992 MarkupProcessor::StyledTextArray textToInsert;
4993 for( std::size_t i = 0; i < insertedStringLength; ++i )
4995 const MarkupProcessor::StyledText newStyledCharacter( text[i], mInputStyle );
4996 textToInsert.push_back( newStyledCharacter );
4999 //Insert text to the TextView.
5000 const bool emptyTextView = mStyledText.empty();
5001 if( emptyTextView && mPlaceHolderSet )
5003 // There is no text set so call to TextView::SetText() is needed in order to clear the placeholder text.
5004 mDisplayedTextView.SetText( textToInsert );
5008 if( 0 == numberOfCharactersToReplace )
5010 mDisplayedTextView.InsertTextAt( position, textToInsert );
5014 mDisplayedTextView.ReplaceTextFromTo( position, numberOfCharactersToReplace, textToInsert );
5017 mPlaceHolderSet = false;
5019 if( textToInsert.empty() )
5021 // If no text has been inserted, GetTextLayoutInfo() need to be called to check whether mStyledText has some text.
5022 GetTextLayoutInfo();
5026 // GetTextLayoutInfo() can't be used here as mStyledText is not updated yet.
5027 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5030 textExceedsBoundary = false;
5032 if( !mExceedEnabled )
5034 const Vector3& size = GetControlSize();
5036 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5038 // If new text does not fit within TextView
5039 mDisplayedTextView.RemoveTextFrom( position, insertedStringLength );
5040 // previously inserted text has been removed. Call GetTextLayoutInfo() to check whether mStyledText has some text.
5041 GetTextLayoutInfo();
5042 textExceedsBoundary = true;
5043 insertedStringLength = 0;
5046 if( textExceedsBoundary )
5048 // Add the part of the text which fits on the text-input.
5050 // Split the text which doesn't fit in two halves.
5051 MarkupProcessor::StyledTextArray firstHalf;
5052 MarkupProcessor::StyledTextArray secondHalf;
5053 SplitText( textToInsert, firstHalf, secondHalf );
5055 // Clear text. This text will be filled with the text inserted.
5056 textToInsert.clear();
5058 // Where to insert the text.
5059 std::size_t positionToInsert = position;
5061 bool end = text.GetLength() <= 1;
5064 // Insert text and check ...
5065 const std::size_t textLength = firstHalf.size();
5066 mDisplayedTextView.InsertTextAt( positionToInsert, firstHalf );
5067 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5069 if( ( mTextLayoutInfo.mTextSize.width > size.width ) || ( mTextLayoutInfo.mTextSize.height > size.height ) )
5071 // Inserted text doesn't fit.
5073 // Remove inserted text
5074 mDisplayedTextView.RemoveTextFrom( positionToInsert, textLength );
5075 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5077 // The iteration finishes when only one character doesn't fit.
5078 end = textLength <= 1;
5082 // Prepare next two halves for next iteration.
5083 MarkupProcessor::StyledTextArray copyText = firstHalf;
5084 SplitText( copyText, firstHalf, secondHalf );
5091 // store text to be inserted in mStyledText.
5092 textToInsert.insert( textToInsert.end(), firstHalf.begin(), firstHalf.end() );
5094 // Increase the inserted characters counter.
5095 insertedStringLength += textLength;
5097 // Prepare next two halves for next iteration.
5098 MarkupProcessor::StyledTextArray copyText = secondHalf;
5099 SplitText( copyText, firstHalf, secondHalf );
5101 // Update where next text has to be inserted
5102 positionToInsert += textLength;
5108 if( textToInsert.empty() && emptyTextView )
5110 // No character has been added and the text-view was empty.
5111 // Set the placeholder text.
5112 mDisplayedTextView.SetText( mStyledPlaceHolderText );
5113 mPlaceHolderSet = true;
5117 MarkupProcessor::StyledTextArray::iterator it = mStyledText.begin() + position;
5118 mStyledText.insert( it, textToInsert.begin(), textToInsert.end() );
5119 mPlaceHolderSet = false;
5122 return insertedStringLength;
5125 void TextInput::GetTextLayoutInfo()
5127 if( mStyledText.empty() )
5129 // The text-input has no text, clear the text-view's layout info.
5130 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5134 if( mDisplayedTextView )
5136 mDisplayedTextView.GetTextLayoutInfo( mTextLayoutInfo );
5140 // There is no text-view.
5141 mTextLayoutInfo = Toolkit::TextView::TextLayoutInfo();
5146 void TextInput::SetOffsetFromText( const Vector4& offset )
5148 mPopupOffsetFromText = offset;
5151 const Vector4& TextInput::GetOffsetFromText() const
5153 return mPopupOffsetFromText;
5156 void TextInput::SetProperty( BaseObject* object, Property::Index propertyIndex, const Property::Value& value )
5158 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5162 TextInput& textInputImpl( GetImpl( textInput ) );
5164 switch ( propertyIndex )
5166 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5168 textInputImpl.SetMaterialDiffuseColor( value.Get< Vector4 >() );
5171 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5173 textInputImpl.mPopUpPanel.SetCutPastePopUpColor( value.Get< Vector4 >() );
5176 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5178 textInputImpl.mPopUpPanel.SetCutPastePopUpPressedColor( value.Get< Vector4 >() );
5181 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5183 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCut, value.Get<unsigned int>() );
5186 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5188 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsCopy, value.Get<unsigned int>() );
5191 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5193 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsPaste, value.Get<unsigned int>() );
5196 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5198 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelect, value.Get<unsigned int>() );
5201 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5203 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll, value.Get<unsigned int>() );
5206 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5208 textInputImpl.mPopUpPanel.SetButtonPriorityPosition( TextInputPopup::ButtonsClipboard, value.Get<unsigned int>() );
5211 case Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY:
5213 textInputImpl.SetOffsetFromText( value.Get< Vector4 >() );
5220 Property::Value TextInput::GetProperty( BaseObject* object, Property::Index propertyIndex )
5222 Property::Value value;
5224 Toolkit::TextInput textInput = Toolkit::TextInput::DownCast( Dali::BaseHandle( object ) );
5228 TextInput& textInputImpl( GetImpl( textInput ) );
5230 switch ( propertyIndex )
5232 case Toolkit::TextInput::HIGHLIGHT_COLOR_PROPERTY:
5234 value = textInputImpl.GetMaterialDiffuseColor();
5237 case Toolkit::TextInput::CUT_AND_PASTE_COLOR_PROPERTY:
5239 value = textInputImpl.mPopUpPanel.GetCutPastePopUpColor();
5242 case Toolkit::TextInput::CUT_AND_PASTE_PRESSED_COLOR_PROPERTY:
5244 value = textInputImpl.mPopUpPanel.GetCutPastePopUpPressedColor();
5247 case Toolkit::TextInput::CUT_BUTTON_POSITION_PRIORITY_PROPERTY:
5249 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCut );
5252 case Toolkit::TextInput::COPY_BUTTON_POSITION_PRIORITY_PROPERTY:
5254 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsCopy );
5257 case Toolkit::TextInput::PASTE_BUTTON_POSITION_PRIORITY_PROPERTY:
5259 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsPaste );
5262 case Toolkit::TextInput::SELECT_BUTTON_POSITION_PRIORITY_PROPERTY:
5264 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelect );
5267 case Toolkit::TextInput::SELECT_ALL_BUTTON_POSITION_PRIORITY_PROPERTY:
5269 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsSelectAll );
5272 case Toolkit::TextInput::CLIPBOARD_BUTTON_POSITION_PRIORITY_PROPERTY:
5274 value = textInputImpl.mPopUpPanel.GetButtonPriorityPosition( TextInputPopup::ButtonsClipboard );
5277 case Toolkit::TextInput::POP_UP_OFFSET_FROM_TEXT_PROPERTY:
5279 value = textInputImpl.GetOffsetFromText();
5287 void TextInput::EmitStyleChangedSignal()
5289 // emit signal if input style changes.
5290 Toolkit::TextInput handle( GetOwner() );
5291 mStyleChangedSignalV2.Emit( handle, mInputStyle );
5294 void TextInput::EmitTextModified()
5296 // emit signal when text changes.
5297 Toolkit::TextInput handle( GetOwner() );
5298 mTextModifiedSignal.Emit( handle );
5302 void TextInput::EmitMaxInputCharactersReachedSignal()
5304 // emit signal if max characters is reached during text input.
5305 DALI_LOG_INFO(gLogFilter, Debug::General, "EmitMaxInputCharactersReachedSignal \n");
5307 Toolkit::TextInput handle( GetOwner() );
5308 mMaxInputCharactersReachedSignalV2.Emit( handle );
5311 void TextInput::EmitInputTextExceedsBoundariesSignal()
5313 // Emit a signal when the input text exceeds the boundaries of the text input.
5315 Toolkit::TextInput handle( GetOwner() );
5316 mInputTextExceedBoundariesSignalV2.Emit( handle );
5319 } // namespace Internal
5321 } // namespace Toolkit